Code running slow after creating custom namespace

I’ve been working on an asset to help with 2D world generation. It worked perfectly, however, I was required to use namespaces to make my code easier to understand.

After converting everything to use a namespace, it works but is very slow.

Before using a namespace, I was able to generate a world in under a second, but now it takes one to five seconds to generate.

Here’s what my code looks like:

namespace EasyWorldGen2D
{
    public class WorldGenFunctions
    {

        public static void SetUp(Transform objectTransform)
        {
            Tilemap tilemap = new Tilemap();
            tilemap = objectTransform.GetComponent<Tilemap>();

            if (objectTransform.GetComponentInChildren<Tilemap>() == null)
            {
               CreateNewGrid(tilemap, objectTransform);
            }
            else
            {
                tilemap = objectTransform.GetComponentInChildren<Grid>().GetComponentInChildren<Tilemap>();
            }

        }

        public static void CreateNewGrid(Tilemap objTilemap, Transform objTransform)
        {

            var grid = new GameObject("Grid").AddComponent<Grid>();
            objTilemap = new GameObject("Tilemap").AddComponent<Tilemap>();
            objTilemap.gameObject.AddComponent<TilemapRenderer>();

            grid.cellLayout = GridLayout.CellLayout.Rectangle;
            objTilemap.GetComponent<TilemapRenderer>().sortOrder = TilemapRenderer.SortOrder.BottomLeft;
            objTilemap.transform.SetParent(grid.transform);

            grid.transform.SetParent(objTransform);
        }



        public static void Generate(Coord[,] coord, Tiles tilesToUse, TileAndGenerationSettings settings, Tilemap tilemap ,Enum noiseType, int seed, float scaleFactor, int noiseModifier, bool isIsland)
        {

            Debug.Log("Generating");

            for (int x = 0; x < coord.GetLength(0); x++)
            {
                for (int y = 0; y < coord.GetLength(1); y++)
                {
                    coord[x, y] = new Coord();
                    Coord newCoordinate = coord[x, y];

                    float noiseValue_1 = new float();
                    float noiseValue_2 = new float();

                    if(noiseType.ToString() == "Simplex")
                    {
                        noiseValue_1 = noise.snoise(new float2((x / scaleFactor) + (seed * noiseModifier), (y / scaleFactor) + (seed * noiseModifier)));
                        noiseValue_2 = noise.snoise(new float2((x / scaleFactor) + (seed * 2 * noiseModifier), (y / scaleFactor) + (seed * 2 * noiseModifier)));
                    }
                    else if(noiseType.ToString() == "Perlin")
                    {
                        noiseValue_1 = noise.cnoise(new float2((x / scaleFactor) + (seed * noiseModifier), (y / scaleFactor) + (seed * noiseModifier)));
                        noiseValue_2 = noise.cnoise(new float2((x / scaleFactor) + (seed * 2 * noiseModifier), (y / scaleFactor) + (seed * 2 * noiseModifier)));
                    }
                    else if(noiseType.ToString() == "Both")
                    {
                        noiseValue_1 = noise.cnoise(new float2((x / scaleFactor) + (seed * noiseModifier), (y / scaleFactor) + (seed * noiseModifier)));
                        noiseValue_2 = noise.snoise(new float2((x / scaleFactor) + (seed * noiseModifier), (y / scaleFactor) + (seed * noiseModifier)));
                    }


                    //average both
                    float noiseValue = (noiseValue_1 + noiseValue_2) / 2f;

                    Vector3Int tileLocation = new Vector3Int(x, y);

                    if (isIsland)
                    {
                        noiseValue = MakeIsland(settings, noiseValue, new Vector2(x, y));
                    }

                    SetTiles(newCoordinate, tilesToUse, settings, tilemap, tileLocation, noiseValue);
                }
            }
        }

        private static void SetTiles(Coord coordinate, Tiles m_tiles, TileAndGenerationSettings settings, Tilemap objectTilemap, Vector3Int tileLocation, float noiseValue)
        {
            coordinate.isHighestValue = noiseValue < settings.highestValue_size && noiseValue > settings.secondHighestValue_size;
            coordinate.isSecondHighestValue = noiseValue < settings.secondHighestValue_size && noiseValue > settings.secondLowestValue_size;
            coordinate.isSecondLowestValue = noiseValue < settings.secondLowestValue_size && noiseValue > settings.lowestValue_size;
            coordinate.isLowestValue = noiseValue < settings.lowestValue_size;


            if (coordinate.isHighestValue)
            {
                Debug.Log(m_tiles.highestValue);
                objectTilemap.SetTile(tileLocation, m_tiles.highestValue);

            }
            else if (coordinate.isSecondHighestValue)
            {
                Debug.Log(m_tiles.secondHighestValue);
                objectTilemap.SetTile(tileLocation, m_tiles.secondHighestValue);

            }
            else if (coordinate.isSecondLowestValue)
            {
                Debug.Log(m_tiles.secondLowestValue);
                objectTilemap.SetTile(tileLocation, m_tiles.secondLowestValue);

            }
            else if (coordinate.isLowestValue)
            {
                Debug.Log(m_tiles.lowestValue);
                objectTilemap.SetTile(tileLocation, m_tiles.lowestValue);

            }

        }

        private static float MakeIsland(TileAndGenerationSettings settings, float noiseValue, Vector2 tilePosition)
        {

            //Find Center
            float center_width = settings.width / 2;
            float center_height = settings.height / 2;

            Vector2 center = new Vector2(center_width, center_height);
            float distance = Vector2.Distance(tilePosition, center);
            float avgSize_withdamping = (settings.width + settings.height) / (float)settings.islandFalloutSize;

            if (distance > 0)
            {
                noiseValue = ((noiseValue * avgSize_withdamping) - distance) / avgSize_withdamping;
            }

            return noiseValue;
        }

        public static int GenerateSeed(TileAndGenerationSettings settings)
        {
            int seed;

            if (settings.isUsingRandomSeed)
            {
                //print("Generating Random Seed");
                seed = UnityEngine.Random.Range(1000, 9999);
                settings.seed = seed;
            }
            else
            {
                //print("not random");
                seed = settings.seed;
            }

            return seed;
        }

        public static void Clear(Transform objTransform)
        {
            try
            {
                Debug.Log("Clearing");
                objTransform.GetComponentInChildren<Grid>().GetComponentInChildren<Tilemap>();
            }
            catch
            {
                Debug.Log("Could't clear");
            }
            return;
        }

    }
    public class Coord
    {

        public bool isHighestValue;
        public bool isSecondHighestValue;
        public bool isSecondLowestValue;
        public bool isLowestValue;
    }

    public class Tiles
    {
        public RuleTile highestValue;
        public RuleTile secondHighestValue;
        public RuleTile secondLowestValue;
        public RuleTile lowestValue;
    }

    public class TileAndGenerationSettings
    {
        public int width;
        public int height;
        public float islandFalloutSize;

        public bool isUsingRandomSeed;
        public int seed;

        public float highestValue_size;
        public float lowestValue_size;
        public float secondHighestValue_size;
        public float secondLowestValue_size;
    }
}

The above code is called in a separate script:

[ExecuteAlways]

public class WorldGen : MonoBehaviour
{
    Coord[,] coord;

    public int width = 100, height = 100;
    public float scaleFactor = 25;
    public int noiseModifier = 1;

    public enum NoiseType { Simplex, Perlin, Both}
    public NoiseType noiseType;

    public int _seed;
    public bool randomSeed;

    [SerializeField] RuleTile highestValue;
    [SerializeField] RuleTile secondHighestValue;
    [SerializeField] RuleTile secondLowestValue;
    [SerializeField] RuleTile lowestValue;



    public float highestValue_size = 1, secondHighestValue_size = 0.3f, secondLowestValue_size = 0.2f, lowestValue_size = -0.2f;

    public bool isIsland;
    public float islandFalloutSize;
    


    private void Update()
    {
        coord = new Coord[width, height];
    }

    public void UseAllGenerateMethods()
    {
        WorldGenFunctions.SetUp(transform);
        WorldGenFunctions.Clear(transform);

        Tiles tilesToUse = Tiles_Settings();
        TileAndGenerationSettings settings = TileSettings_Settings();

        int seed = WorldGenFunctions.GenerateSeed(settings);
        _seed = seed;

        WorldGenFunctions.Generate(coord, tilesToUse, settings, transform.GetComponentInChildren<Tilemap>(), noiseType, seed, scaleFactor, noiseModifier, isIsland);
    }

    private Tiles Tiles_Settings()
    {
        Tiles tiles = new Tiles();
        tiles.lowestValue = lowestValue;
        tiles.highestValue = highestValue;
        tiles.secondHighestValue = secondHighestValue;
        tiles.secondLowestValue = secondLowestValue;

        return tiles;
    }

    private TileAndGenerationSettings TileSettings_Settings()
    {
        TileAndGenerationSettings tileSettings = new TileAndGenerationSettings();

        tileSettings.width = width;
        tileSettings.height = height;
        tileSettings.islandFalloutSize = islandFalloutSize;

        tileSettings.isUsingRandomSeed = randomSeed;
        tileSettings.seed = _seed;

        tileSettings.highestValue_size = highestValue_size;
        tileSettings.lowestValue_size = lowestValue_size;
        tileSettings.secondHighestValue_size = secondHighestValue_size;
        tileSettings.secondLowestValue_size = secondLowestValue_size;

        return tileSettings;
    }

}


Is there anything in my code that could be causing an issue?

That doesn’t sound right. Namespaces are purely organizational and should never affect the way your code works. Are you sure you didn’t make any other changes?

I strongly recommend you profile your code to find out what is slowing you down.

That said, there’s a couple of things I’d look into:

  • if(noiseType.ToString() == "Simplex") - this is very inefficient because you’re converting an enum to a string (in each if statement separately!) and comparing two strings. Enums are actually represented by a simple an integer number, and it’s much faster to compare them directly. (You need to pass the enum as the actual NoiseType type and not the base Enum)
  • Debug.Log can be surprisingly slow
  • WorldGen creates a new array in every Update
  • The Coord struct is a good candidate for turning it into a struct instead of a class (you’ll need to pass it around using ref though)

There’s more you can do, but this should be a good start.

2 Likes

The reason I converted it to a string was so that I didn’t need to do anything unnecessarily complex to get the “NoiseType” from the WorldGen script.
So you’re saying that if I pass in an int, like: if(noiseType == 1) that would be more efficient?

Yes, enums/ints should be faster to compare and it will avoid making unnecessary memory allocations (casting NoiseType to System.Enum allocates an object, noiseType.ToString() allocates a new string).

Enum-based function parameters are more robust and discoverable than opaque strings/ints. Don’t avoid using them!

1 Like

Okay! I did this, fixed the coord[,] in update, and removed Debug.Log! And it works great!

I had no idea Debug.Log was slow. Why is that?

Nice!

AFAIR think the slowest part is the creation of the stack trace. You can actually disable stack traces, but they’re way too useful to live without. It’s a good idea to remove excessive logs after debugging and avoid logging stuff every frame, or in loops with lots of iterations.

1 Like

The stacktrace, as mentioned by apkdev, is likely the big culprit. They’re also heavy on file access, because they need to get written immediately rather than just placed in a buffer which the OS can handle more optimally.

1 Like

It’s worth mentioning again that the profiler would have immediately told you where the slowdown was coming from, removing all guess work.

2 Likes

I just googled what the profiler is. I had no idea that Unity had a built in window for this!

Ah, is print faster? Is that put in a buffer?

It just calls Debug.Log: UnityCsReference/Runtime/Export/Scripting/MonoBehaviour.bindings.cs at master · Unity-Technologies/UnityCsReference · GitHub

Ok, and a stack trace is a list of all the methods called right? So Debug.Log basically keeps track of all the methods called and just picks one to print in the console? If we don’t call debug.log, the methods aren’t kept track off

It doesn’t necessarily keep track of it, it just has to gather up all that information when the method is called, which has a certain amount of overhead. Overhead that adds up when called a large number of times within the same frame. The same thing happens when you throw a regular C# exception as well.

Worth noting that in (normal) builds Debug.Log doesn’t record the stack trace, it just prints the text instead. Exceptions will still record/print the whole stack trace.

1 Like

You are required because you want to submit to the Asset Store?

I just want to point out that there’s a gazillion tilemap generators already out there so you ought to have something really unique to offer.

For background:

I was just trying to create a world generator for practice. It was just a side project that I was working on to hone my programming skills.

After I finished, I added a bunch of customizability and a custom editor to help me in the future to make it easier and quick to use and implement in future projects.

I realized that it was really useful, so I decided to put it on the asset store, just in case someone else could find it useful.

However, I never started this project with the intention of putting it on the asset store. I just decided to as I saw it useful in the end.

This entire thing has been a learning experience, including namespaces. If the asset doesn’t do well, I don’t really mind— again, this entire thing started as a learning experience.

1 Like