Sign in to follow this  
SpikeViper

Unity Optimizing Generation

Recommended Posts

I've almost optimized everything I can imagine, but being more of a novice there's probably some tricks that I don't yet know of. Here's some pieces of code I believe could be faster, and I'll explain what I've done for each one.

 

Generation:

 

Starting Generation:

    // Use this for initialization
    public IEnumerator Generate()
    {

        blocks = new Block[16, 16, 16];

        posx = (int)Position.x;
        posy = (int)Position.y;
        posz = (int)Position.z;


        loadthread = UnityThreadHelper.TaskDistributor.Dispatch(() => blocks = GeneratePlanet.Generate("rock", planet.planetSize, planet.planetSeed, planet.cave, planet.cracked, posx, posy, posz));

        while (loadthread.IsSucceeded == false)
        {
            yield return new WaitForSeconds(0.1f);
        }


        loadthread = UnityThreadHelper.TaskDistributor.Dispatch(() => blocks = GeneratePlanet.GenerateOres(blocks, planet.planetSize, planet.planetSeed, posx, posy, posz));

        while (loadthread.IsSucceeded == false)
        {
            yield return new WaitForSeconds(0.1f);
        }

        Generated = true;
        UpdatePlanetChunk();

    }

- Used threading to increase performance

- Used Coroutines to spread out processing

 

Actual Generation (Kinda Complicated! ...And the most intensive code):

   public static  Block[,,] Generate(string PlanetType, int planetSize, int seed, bool cave, bool cracked, int posx, int posy, int posz)
    {

        Block[,,] blocks = new Block[16, 16, 16]; 

        int PlanetSize = planetSize;

        int r =  PlanetSize / 2;
        int offsetx = PlanetSize / 2;
        int offsety = PlanetSize / 2;
        int offsetz = PlanetSize / 2;

        for (int x = 0; x < 16; x++)
        {
            for (int y = 0; y < 16; y++)
            {
                for (int z = 0; z < 16; z++)
                {

                    if (blocks[x, y, z] == null)
                    {
                        blocks[x, y, z] = new BlockEmpty();
                    }

                }

            }

        }

        for (int tx = 0; tx < 16; tx++)
        {
            for (int ty = 0; ty < 16; ty++)
            {
                for (int tz = 0; tz < 16; tz++)
                {

                        if (Mathf.Sqrt(Mathf.Pow(tx + (posx * 16) - r, 2) + Mathf.Pow(ty + (posy * 16) - r, 2) + Mathf.Pow(tz + (posz * 16) - r, 2)) <= r - PlanetSize / 2.5)
                        {
                            blocks[tx, ty, tz] = new BlockCore();
                        }

                        else if (Mathf.Sqrt(Mathf.Pow(tx + (posx * 16) - r, 2) + Mathf.Pow(ty + (posy * 16) - r, 2) + Mathf.Pow(tz + (posz * 16) - r, 2)) <= r - PlanetSize / 3.5)
                        {
                            blocks[tx, ty, tz] = new BlockBedrock();
                        }

                        else if (Mathf.Sqrt(Mathf.Pow(tx + (posx * 16) - r, 2) + Mathf.Pow(ty + (posy * 16) - r, 2) + Mathf.Pow(tz + (posz * 16) - r, 2)) <= r - PlanetSize / 5)
                        {
                            blocks[tx, ty, tz] = new BlockSubstone();
                        }

                        else if (Mathf.Sqrt(Mathf.Pow(tx + (posx * 16) - r, 2) + Mathf.Pow(ty + (posy * 16) - r, 2) + Mathf.Pow(tz + (posz * 16) - r, 2)) <= r - PlanetSize / 6)
                        {
                            blocks[tx, ty, tz] = new BlockStone();
                        }


                    }

                }
            }


        



        if (cracked == true)
        {

            var CrackGen = new RidgeNoise(seed)
            {
                Frequency = (float)0.03,
                Lacunarity = 2,
                OctaveCount = 2,

                Exponent = 2,
                Offset = 1,
                Gain = (float)1,

            } * 1;


            for (int x = 0; x < 16; x++)
            {
                for (int y = 0; y < 16; y++)
                {
                    for (int z = 0; z < 16; z++)
                    {



                        float value = CrackGen.GetValue(new Vector3(x + (posx * 16), y + (posy * 16), z + (posz * 16)));

                        if (value > 1.00)
                        {
                            blocks[x, y, z] = new BlockEmpty();
                        }
                    }
                }
            }

        }


        if (cave == true)
        {

            var CaveGen = new BillowNoise(seed);

            CaveGen.Frequency = 0.08f;
            CaveGen.Persistence = 0.4f;

            for (int x = 0; x < 16; x++)
            {
                for (int y = 0; y < 16; y++)
                {
                    for (int z = 0; z < 16; z++)
                    {



                        float value = CaveGen.GetValue(new Vector3(x + (posx * 16), y + (posy * 16), z + (posz * 16)));

                        if (value > 0)
                        {
                            blocks[x, y, z] = new BlockEmpty();
                        }
                    }
                }
            }

        }

        return blocks;


    }


    public static Block[,,] GenerateOres(Block[,,] blocks, int planetSize, int seed, int posx, int posy, int posz)
    {

        var OreGen = new BillowNoise(seed);

        OreGen.Frequency = 0.16f;
        OreGen.Persistence = 0.4f;

        List<Block> OreTypes = new List<Block>();
        OreTypes.Add(new BlockOreUranium());

        foreach (Block ore in OreTypes)
        {


            for (int x = 0; x < 16; x++)
            {
                for (int y = 0; y < 16; y++)
                {
                    for (int z = 0; z < 16; z++)
                    {



                        float value = OreGen.GetValue(new Vector3(x + (posx * 16), y + (posy * 16), z + (posz * 16)));

                        if (value > ore.Rarity() && blocks[x, y, z].BlockName() == ore.BaseBlock())
                        {
                            blocks[x, y, z] = ore;

                        }
                    }
                }
            }
        }

        return blocks;

    }

- Used else-ifs to reduce testing of variables

- Changed to a chunk system to generate one bit at a time

 

If anyone sees any tips to improve this, thanks!

 

Share this post


Link to post
Share on other sites

You need to use a profiler for this.  What does your profiler tell you?  Where are you actually spending your time?  Is this really the bottleneck?

 

Using threads by itself distributes loads among processors but does not actually reduce the work, typically it causes a slight increase in total work but a division in wall clock time. Distributing your workload can help and is one of the easiest changes to make, but it is also one of the smallest ways to improve code.

 

After you've used your profiler to determine exactly where the actual bottlenecks are, use Unity's profiler commands Profiler.BeginSample() and Profiler.EndSample() to help isolate exactly where the problems are. Is the problem coming from a large number of calls that can be eliminated? Is it coming from unnecessary processing being done? Is it work that can be done with a different algorithm?

 

After you've profiled it, and used the profiler to isolate an exact section needing change, make the change and profile some more to make sure it actually improved the situation.

 

 

Without reviewing it in a profiler everything about it is guesswork. The change might have an improvement, or it may have no effect at all. 

Share this post


Link to post
Share on other sites

I agree with Frob, but that said, just looking at it, there are a couple of things that I can guess at:

 

Your sqrt is unneccessary, you can square both sides of the equation, and you can move the calculation of the various radii your checking against out into variables before the for loops.  And then you can give them meaningful names too =)  EDIT: If its not clear, I'm referring to (r - PlanetSize / 6)^2, which could be renamed minStoneRadiusSquared.

 

Also, you'll probably want to invert your for loop order, and do z, y, x, it's more memory friendly.  If you are wondering why, imagine if you had a single array that was of length 16*16*16, and think of how you're jumping around in it as you travel through your innermost for loops.

 

EDIT:  Though taking a deeper look, it looks like you're just trying to make blocks of a band of a sphere set type, which seems like something you could make separate iterations, without any radial checks at all, especially since those are going to be constant per planetsize, and your dealing with integers.  (Which is why you should really deeply consider frob's questions)

Edited by ferrous

Share this post


Link to post
Share on other sites

You need to use a profiler for this.  What does your profiler tell you?  Where are you actually spending your time?  Is this really the bottleneck?

 

Using threads by itself distributes loads among processors but does not actually reduce the work, typically it causes a slight increase in total work but a division in wall clock time. Distributing your workload can help and is one of the easiest changes to make, but it is also one of the smallest ways to improve code.

 

After you've used your profiler to determine exactly where the actual bottlenecks are, use Unity's profiler commands Profiler.BeginSample() and Profiler.EndSample() to help isolate exactly where the problems are. Is the problem coming from a large number of calls that can be eliminated? Is it coming from unnecessary processing being done? Is it work that can be done with a different algorithm?

 

After you've profiled it, and used the profiler to isolate an exact section needing change, make the change and profile some more to make sure it actually improved the situation.

 

 

Without reviewing it in a profiler everything about it is guesswork. The change might have an improvement, or it may have no effect at all. 

Currently, 82.9% of my lag (during lag spikes) is due to PlanetChunk.Generate() [Coroutine: MoveNext]. I'll try to pinpoint it further and post more, but I am sure it is the generation code.

Share this post


Link to post
Share on other sites

I agree with Frob, but that said, just looking at it, there are a couple of things that I can guess at:

 

Your sqrt is unneccessary, you can square both sides of the equation, and you can move the calculation of the various radii your checking against out into variables before the for loops.  And then you can give them meaningful names too =)  EDIT: If its not clear, I'm referring to (r - PlanetSize / 6)^2, which could be renamed minStoneRadiusSquared.

 

Also, you'll probably want to invert your for loop order, and do z, y, x, it's more memory friendly.  If you are wondering why, imagine if you had a single array that was of length 16*16*16, and think of how you're jumping around in it as you travel through your innermost for loops.

 

EDIT:  Though taking a deeper look, it looks like you're just trying to make blocks of a band of a sphere set type, which seems like something you could make separate iterations, without any radial checks at all, especially since those are going to be constant per planetsize, and your dealing with integers.  (Which is why you should really deeply consider frob's questions)

Those ideas actually did increase the speed quite a bit, and cut out some lag spikes.

Share this post


Link to post
Share on other sites

Yeah, pull anything you can farther up out of a loop if you can, for example:

Mathf.Pow(ty + (posy * 16) - r, 2) (which in addition to being overkill expensive, especially for integers, only changes when ty changes, yet you are calculating it every time in your inner most loop, so instead of only doing it 16*16 times, you are executing that code 16*16*16 times.

 

I also don't think your multithreaded code is helpful at the moment, you might want to strip it temporarily, especially if Unity's profiler is having a hard time with it.

 

And again, I think you should probably rethink how your iterating.  For example, your cracked code.  Rather than iterating over every block, why not randomly generate how many empty squares you think their should be.  Then randomly generate a set of indices for the number of empty squares, index into those locations and set the block to empty.   Instead of iterating over 16*16*16 squares and mostly doing nothing, you're looping only as many times as you have empty squares.

 

EDIT: Horrible pseudo code:

 

int numCrackedSquares = Random.GetValue()

for(int i = 0; i < numCrackedSquares; ++i)

{

int x = Random.GetInt()

int y = Random.GetInt()

int z = Random.GetInt()

 

blocks[x,y,z] = new BlockEmpty();

}

Edited by ferrous

Share this post


Link to post
Share on other sites

This bit...

 

if (Mathf.Sqrt(Mathf.Pow(tx + (posx * 16) - r, 2) + Mathf.Pow(ty + (posy * 16) - r, 2) + Mathf.Pow(tz + (posz * 16) - r, 2)) <= r - PlanetSize / 2.5)
{
blocks[tx, ty, tz] = new BlockCore();
}

else if (Mathf.Sqrt(Mathf.Pow(tx + (posx * 16) - r, 2) + Mathf.Pow(ty + (posy * 16) - r, 2) + Mathf.Pow(tz + (posz * 16) - r, 2)) <= r - PlanetSize / 3.5)
{
blocks[tx, ty, tz] = new BlockBedrock();
}

else if (Mathf.Sqrt(Mathf.Pow(tx + (posx * 16) - r, 2) + Mathf.Pow(ty + (posy * 16) - r, 2) + Mathf.Pow(tz + (posz * 16) - r, 2)) <= r - PlanetSize / 5)
{
blocks[tx, ty, tz] = new BlockSubstone();
}

else if (Mathf.Sqrt(Mathf.Pow(tx + (posx * 16) - r, 2) + Mathf.Pow(ty + (posy * 16) - r, 2) + Mathf.Pow(tz + (posz * 16) - r, 2)) <= r - PlanetSize / 6)
{
blocks[tx, ty, tz] = new BlockStone();
}
 

Could be 

 

var posFactor = Mathf.Sqrt(Mathf.Pow(tx + (posx * 16) - r, 2) + Mathf.Pow(ty + (posy * 16) - r, 2) + Mathf.Pow(tz + (posz * 16) - r, 2));

if (posFactor <= r - PlanetSize / 2.5)
{
blocks[tx, ty, tz] = new BlockCore();
}

else if (posFactor <= r - PlanetSize / 3.5)
{
blocks[tx, ty, tz] = new BlockBedrock();
}

else if (posFactor <= r - PlanetSize / 5)
{
blocks[tx, ty, tz] = new BlockSubstone();
}

else if (posFactor <= r - PlanetSize / 6)
{
blocks[tx, ty, tz] = new BlockStone();
}

 

Otherwise exactly the same maths could be done upto 4 times per iteration each time giving the same result. Think DRY... Don't Repeat Yourself :)

Share this post


Link to post
Share on other sites

This bit...

 

if (Mathf.Sqrt(Mathf.Pow(tx + (posx * 16) - r, 2) + Mathf.Pow(ty + (posy * 16) - r, 2) + Mathf.Pow(tz + (posz * 16) - r, 2)) <= r - PlanetSize / 2.5)
{
blocks[tx, ty, tz] = new BlockCore();
}

else if (Mathf.Sqrt(Mathf.Pow(tx + (posx * 16) - r, 2) + Mathf.Pow(ty + (posy * 16) - r, 2) + Mathf.Pow(tz + (posz * 16) - r, 2)) <= r - PlanetSize / 3.5)
{
blocks[tx, ty, tz] = new BlockBedrock();
}

else if (Mathf.Sqrt(Mathf.Pow(tx + (posx * 16) - r, 2) + Mathf.Pow(ty + (posy * 16) - r, 2) + Mathf.Pow(tz + (posz * 16) - r, 2)) <= r - PlanetSize / 5)
{
blocks[tx, ty, tz] = new BlockSubstone();
}

else if (Mathf.Sqrt(Mathf.Pow(tx + (posx * 16) - r, 2) + Mathf.Pow(ty + (posy * 16) - r, 2) + Mathf.Pow(tz + (posz * 16) - r, 2)) <= r - PlanetSize / 6)
{
blocks[tx, ty, tz] = new BlockStone();
}
 

Could be 

 

var posFactor = Mathf.Sqrt(Mathf.Pow(tx + (posx * 16) - r, 2) + Mathf.Pow(ty + (posy * 16) - r, 2) + Mathf.Pow(tz + (posz * 16) - r, 2));

if (posFactor <= r - PlanetSize / 2.5)
{
blocks[tx, ty, tz] = new BlockCore();
}

else if (posFactor <= r - PlanetSize / 3.5)
{
blocks[tx, ty, tz] = new BlockBedrock();
}

else if (posFactor <= r - PlanetSize / 5)
{
blocks[tx, ty, tz] = new BlockSubstone();
}

else if (posFactor <= r - PlanetSize / 6)
{
blocks[tx, ty, tz] = new BlockStone();
}

 

Otherwise exactly the same maths could be done upto 4 times per iteration each time giving the same result. Think DRY... Don't Repeat Yourself :)

I actually did catch and fix that :)

Now the only remaining lag is in things like adding objects to a list... I don't think I can make that faster, though.

Share this post


Link to post
Share on other sites

 

 

I'm curious, what is the definition of your Block type?

 

By that, do you mean my main block class?

 

I mean your class named "Block".
 

Block[,,] blocks = new Block[16, 16, 16];
 

 

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[System.Serializable]

public class Block
{
    public enum Direction { north, east, south, west, up, down };

    public struct Tile { public int x; public int y;}
    const float tileSize = 0.0625f;


    //Base block constructor
    public Block()
    {

    }

    public virtual MeshData Blockdata
     (PlanetChunk planetchunk, int x, int y, int z, MeshData meshData)
    {

        meshData.useRenderDataForCol = true;

        if (!planetchunk.GetBlock(x, y + 1, z).IsSolid(Direction.down))
        {
            meshData = FaceDataUp(planetchunk, x, y, z, meshData);
        }

        if (!planetchunk.GetBlock(x, y - 1, z).IsSolid(Direction.up))
        {
            meshData = FaceDataDown(planetchunk, x, y, z, meshData);
        }

        if (!planetchunk.GetBlock(x, y, z + 1).IsSolid(Direction.south))
        {
            meshData = FaceDataNorth(planetchunk, x, y, z, meshData);
        }

        if (!planetchunk.GetBlock(x, y, z - 1).IsSolid(Direction.north))
        {
            meshData = FaceDataSouth(planetchunk, x, y, z, meshData);
        }

        if (!planetchunk.GetBlock(x + 1, y, z).IsSolid(Direction.west))
        {
            meshData = FaceDataEast(planetchunk, x, y, z, meshData);
        }

        if (!planetchunk.GetBlock(x - 1, y, z).IsSolid(Direction.east))
        {
            meshData = FaceDataWest(planetchunk, x, y, z, meshData);
        }


        
        if (Light() == true)
        {

            if (planetchunk.GetBlock(x - 1, y, z).IsSolid(Direction.east) == false || planetchunk.GetBlock(x + 1, y, z).IsSolid(Direction.west) == false || planetchunk.GetBlock(x, y, z - 1).IsSolid(Direction.north) == false ||
                planetchunk.GetBlock(x, y, z + 1).IsSolid(Direction.south) == false || planetchunk.GetBlock(x, y - 1, z).IsSolid(Direction.up) == false || planetchunk.GetBlock(x, y + 1, z).IsSolid(Direction.down) == false)
            {
                meshData.AddLight(x, y, z, LightColor(), LightRange(), LightIntensity());
            }
           
        }

        return meshData;

    }

    protected virtual MeshData FaceDataUp
        (PlanetChunk planetchunk, int x, int y, int z, MeshData meshData)
    {
        meshData.AddVertex(new Vector3(x - 0.5f, y + 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y + 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y + 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y + 0.5f, z - 0.5f));

        meshData.AddQuadTriangles();
        meshData.uv.AddRange(FaceUVs(Direction.up));
        return meshData;
    }

    protected virtual MeshData FaceDataDown
        (PlanetChunk planetchunk, int x, int y, int z, MeshData meshData)
    {
        meshData.AddVertex(new Vector3(x - 0.5f, y - 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y - 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y - 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y - 0.5f, z + 0.5f));

        meshData.AddQuadTriangles();
        meshData.uv.AddRange(FaceUVs(Direction.down));
        return meshData;
    }

    protected virtual MeshData FaceDataNorth
        (PlanetChunk planetchunk, int x, int y, int z, MeshData meshData)
    {
        meshData.AddVertex(new Vector3(x + 0.5f, y - 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y + 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y + 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y - 0.5f, z + 0.5f));

        meshData.AddQuadTriangles();
        meshData.uv.AddRange(FaceUVs(Direction.north));
        return meshData;
    }

    protected virtual MeshData FaceDataEast
        (PlanetChunk planetchunk, int x, int y, int z, MeshData meshData)
    {
        meshData.AddVertex(new Vector3(x + 0.5f, y - 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y + 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y + 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y - 0.5f, z + 0.5f));

        meshData.AddQuadTriangles();
        meshData.uv.AddRange(FaceUVs(Direction.east));
        return meshData;
    }

    protected virtual MeshData FaceDataSouth
        (PlanetChunk planetchunk, int x, int y, int z, MeshData meshData)
    {
        meshData.AddVertex(new Vector3(x - 0.5f, y - 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y + 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y + 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y - 0.5f, z - 0.5f));

        meshData.AddQuadTriangles();
        meshData.uv.AddRange(FaceUVs(Direction.south));
        return meshData;
    }

    protected virtual MeshData FaceDataWest
        (PlanetChunk planetchunk, int x, int y, int z, MeshData meshData)
    {
        meshData.AddVertex(new Vector3(x - 0.5f, y - 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y + 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y + 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y - 0.5f, z - 0.5f));
            

            meshData.AddQuadTriangles();
        meshData.uv.AddRange(FaceUVs(Direction.west));
        return meshData;
    }

    public virtual Tile TexturePosition(Direction direction)
    {
        Tile tile = new Tile();
        tile.x = 0;
        tile.y = 0;

        return tile;
    }

    public virtual Vector2[] FaceUVs(Direction direction)
    {
        Vector2[] UVs = new Vector2[4];
        Tile tilePos = TexturePosition(direction);

        UVs[0] = new Vector2(tileSize * tilePos.x + tileSize,
            tileSize * tilePos.y);
        UVs[1] = new Vector2(tileSize * tilePos.x + tileSize,
            tileSize * tilePos.y + tileSize);
        UVs[2] = new Vector2(tileSize * tilePos.x,
            tileSize * tilePos.y + tileSize);
        UVs[3] = new Vector2(tileSize * tilePos.x,
            tileSize * tilePos.y);

        return UVs;
    }

    public virtual bool IsSolid(Direction direction)
    {
        switch (direction)
        {
            case Direction.north:
                return true;
            case Direction.east:
                return true;
            case Direction.south:
                return true;
            case Direction.west:
                return true;
            case Direction.up:
                return true;
            case Direction.down:
                return true;
        }

        return false;
    }


    public virtual int Temperature()
    {
        // -100 to 100
        return 0;
    }

    public virtual bool Light()
    {
        return false; 
    }

    public virtual Color32 LightColor()
    {
        return new Color32(0, 0, 0, 0);
    }

    public virtual float LightRange()
    {
        return 0;
    }

    public virtual float LightIntensity()
    {
        return 0;
    }

    public virtual int Density()
    {
        // 0 to 100
        return 0;
    }

    public virtual string BaseBlock()
    {
        //For ores, the block it replaces
        return "null";
    }

    public virtual float Rarity()
    {
        //For ore, the rarity. Normally between -1 and 1. Bigger is rarer.
        return 0;
    }

    public virtual float Illumination()
    {
        //How much light the block gives out, 0 to 255
         return 0.0f;
    }

    public virtual bool ore()
    {
        return false;
    }

    public virtual string BlockName()
    {
        return "Undefined!";
    }


}

Share this post


Link to post
Share on other sites

Thanks. So, it's a class. I wonder if it could be a struct, or just a simple enum.

Your generation program is probably still spending time allocating all of these objects, which each contain little to no data. Do any of the child classes have instance data they actually need? I don't see anything passed into their constructors.

If I was focused on generation speed, I would switch away from this object-oriented design for individual blocks. You could still create a data-oriented design that looks up a block processor based on its type and has all those same virtual functions in it. I suppose, theoretically, that might be slower to use, except you'll be scanning through a much smaller amount of memory when processing blocks. You always have to measure these things, of course. If blocks need some instance data, you could pack an enum for block type and an id and/or seed into a struct, and use the seed to figure out whatever other data the block needs on the fly.

As a half-measure, if the individual objects for any particular block type are all the same, you only need one instance of that class. Instead of assigning new blocks, you could reuse the same block instance over and over. For example, why does the system need more than one copy of BlockEmpty, ever? Of course, if you switch to a struct, this no longer applies.

Share this post


Link to post
Share on other sites
Constant allocation and deallocation is a big no, for something this big.

It appears for every single block, you're new'ing and delete'ing. If you're aiming for world modification, shifting, etc. - create your initial data structure at once, then assign the block types.

Yes, new'ing each block works, but if in one scene you have 8,000,000 blocks, that's a lot of allocation and deallocation. But 8,000,000 new operations, and, let's just say, 400,000 delete operations, it's just flat overkill. Eg;

if( someCondition ) {
blocks[myIndex].nType = BlockTypes.AIR;
}

Also, I'd say use a 1D array instead of a 3D array.

Share this post


Link to post
Share on other sites
You have your block struct, eg;
struct Block
{
byte nSunlight;
ulong nColor;
EBlockType nType;
}

Then you have your array,
int nCacheWidth = (nCacheRange * 2) + 1;
int nCacheDepth = nCacheWidth;
Block* pBlocks = new Block[nCacheWidth * nCacheDepth * nChunkHeight];

Now with the nType in the Block struct, specify AIR to 'delete' a block. nSunlight I used to use it as a value between 0-100, with 100 being brightest. nColor holds 4 bytes for each color (ARGB).

Accessing each index would be,
long tmpX = nCacheDepth * nChunkHeight;
long tmpZ = nChunkHeight;

long x = pos.x % nCacheWidth;
long z = pos.z % nCacheDepth;
long OFFSET = x * tmpX + z * tmpZ + pos.y;
blocks[OFFSET].nType = EBlockType.AIR;

I typed that from memory on my phone so excuse any errors. I also prefer this way as it makes it so much simpler if you/user wants bigger or smaller view/cache ranges (loading more chunks).

Share this post


Link to post
Share on other sites

There is yet one more problem. The last laggy piece of code is

toLoad.Sort(delegate (Vector4 first, Vector4 second) { return first.w.CompareTo(second.w); });

Is there a faster way to sort objects? This one line is generating 50% of my lag.

Share this post


Link to post
Share on other sites

Also, you'll probably want to invert your for loop order, and do z, y, x, it's more memory friendly.  If you are wondering why, imagine if you had a single array that was of length 16*16*16, and think of how you're jumping around in it as you travel through your innermost for loops.

 

This is always a good place to look, but OP's code seems consitent in that X is both the outside loop and the most-significant ordinal (array indexer?). In other words, the X and Z "labels" are consistently swapped, but they don't seem to be mismatched in the way that usually causes the cache to be thrashed. The speedup seen was likely entirely due to getting rid of sqrt() -- or I'm not reading the code with the comprehension I think I am :)

Share this post


Link to post
Share on other sites

Also, you'll probably want to invert your for loop order, and do z, y, x, it's more memory friendly.  If you are wondering why, imagine if you had a single array that was of length 16*16*16, and think of how you're jumping around in it as you travel through your innermost for loops.

 
This is always a good place to look, but OP's code seems consitent in that X is both the outside loop and the most-significant ordinal (array indexer?). In other words, the X and Z "labels" are consistently swapped, but they don't seem to be mismatched in the way that usually causes the cache to be thrashed. The speedup seen was likely entirely due to getting rid of sqrt() -- or I'm not reading the code with the comprehension I think I am :)
That part of the code is great now - I even added a chunk pooling system for less garbage collection. My remaining problem is the list.Sort function, as stated above. If you know any faster way to sort it (or better type of data to use) that would be great!

Share this post


Link to post
Share on other sites

What I would actually recommend is combining those first two 3-axis loops. There's a few things you're doing here that seem strange.

 

Firstly, in your first loop (x, y, z) you test for null objects before assigning each index a new BlockEmpty() -- since you just allocated 'blocks' they should all be null. Since you don't ever revisit any array element, you always test, always find null, and always assign a new BlockEmpty().

 

Secondly, now in your second loop (tx, ty, tz), you've already gotten good advice to eliminate the sqrt(), and to hoise out the repeated expression -- and you've probably converted to a switch statement -- if you haven't, you should. The inefficiency here is that you're replacing a lot of those BlockEmpty()'s you just assigned into 'blocks' with these new BlockCore()s, BlockBedrock()s, and BlockStone()s -- BlockEmpty()'s that get replaced are never used, they just take time to created, and leave pressure on the garbage collector when you replace them.

 

So, by combining those two loops and making BlockEmpty() the default case, you can avoid creating any extraneous Blocks currently in those first two loops, plus the code will be simpler.

 

 

taking that even further, you overwrite some of those blocks later with cracks, caves, and ore. A possible further refinement would be to place empty (due to crack or cave) and ore blocks into 'blocks' array first (above the loop I recommend combining) and then add a test in the combined-loop just around the switch so that new blocks (from the switch) are only placed where the array element is currently null (that is, not already made a crack, cave, or ore).

 

Allocating instances of blocks and also iterating over the blocks array is probably the most expensive things you're doing here I would hazard a guess (surely now that the sqrts are eliminated), so reducing those as best you can is probably going to give best results.

 

All that said, if its working at acceptable speed now, its not always a good thing to keep rat-holing yourself over optimizing this bit of code.

Share this post


Link to post
Share on other sites

What I would actually recommend is combining those first two 3-axis loops. There's a few things you're doing here that seem strange.
 
Firstly, in your first loop (x, y, z) you test for null objects before assigning each index a new BlockEmpty() -- since you just allocated 'blocks' they should all be null. Since you don't ever revisit any array element, you always test, always find null, and always assign a new BlockEmpty().
 
Secondly, now in your second loop (tx, ty, tz), you've already gotten good advice to eliminate the sqrt(), and to hoise out the repeated expression -- and you've probably converted to a switch statement -- if you haven't, you should. The inefficiency here is that you're replacing a lot of those BlockEmpty()'s you just assigned into 'blocks' with these new BlockCore()s, BlockBedrock()s, and BlockStone()s -- BlockEmpty()'s that get replaced are never used, they just take time to created, and leave pressure on the garbage collector when you replace them.
 
So, by combining those two loops and making BlockEmpty() the default case, you can avoid creating any extraneous Blocks currently in those first two loops, plus the code will be simpler.
 
 
taking that even further, you overwrite some of those blocks later with cracks, caves, and ore. A possible further refinement would be to place empty (due to crack or cave) and ore blocks into 'blocks' array first (above the loop I recommend combining) and then add a test in the combined-loop just around the switch so that new blocks (from the switch) are only placed where the array element is currently null (that is, not already made a crack, cave, or ore).
 
Allocating instances of blocks and also iterating over the blocks array is probably the most expensive things you're doing here I would hazard a guess (surely now that the sqrts are eliminated), so reducing those as best you can is probably going to give best results.
 
All that said, if its working at acceptable speed now, its not always a good thing to keep rat-holing yourself over optimizing this bit of code.


I've already done all of those things, and also made BlockTypes so blocks don't need to be deleted, just have their type changed. The last line of code that isn't optimized is the one where I sort my list.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this  

  • Forum Statistics

    • Total Topics
      628277
    • Total Posts
      2981774
  • Similar Content

    • By ForgedInteractive


      Who We Are
      We are Forged Interactive, a small team of like-minded game developers with the sole purpose of making games we love! We're a team of artists, animators, programmers, level designers, writers, composers, producers, and other creative minds. We want to make games that you, the modern gamer want to play! We hope to build a community that enjoys our games as much as we love creating them. With your feedback and support we will be able to achieve that.

      About the Game
      GAME NAME is a fun, action-packed army builder with unique characters, challenges and engaging levels. Set forth on an adventure to protect friends, family and countrymen from new adversaries. Once defeated your enemies turn coat and join you in your adventures. Players can enjoy a range of troops and abilities based on their gameplay style which become more important as maps introduce more challenging terrain, enemies and bosses. Strong orc knights, dangerous shamans, and even a dragon are out on the prowl. Knowing when to fight and when to run, and how to manage your army is essential. Your actions alone decide the fate of this world.

      Previous Work by Team
      Although we are working towards our first game as a team, our team members themselves have past experience in the industry.
      This includes members who have worked on titles including:
      Final Fantasy Kingsglaive, FIFA, Xcom 2 and Civilization.

      Who are we looking for? 3D Modellers Concept Artists Marketing Specialists Level Designer

      What do we expect? Reference work or portfolio. Examples what have you already done and what projects you have worked on academic or otherwise. The ability to commit to the project on a regular basis. If you are going on a two-week trip, we don't mind, but it would be good if you could commit 10+ hours to the project each week. Willingness to work with a royalty based compensation model, you will be paid when the game launches. Openness to learning new tools and techniques
      What can we offer? Continuous support and availability from our side. You have the ability to give design input, and creative say in the development of the game. Shown in credits on websites, in-game and more. Insight and contacts from within the Industry.
      Contact
      If you are interested in knowing more or joining. Please email or PM us on Skype. Myself or Colin will reply to you within 48 hours.

      E-mail: Recruitment@ForgedInteractive.com
      Skype: ForgedInteractive

      Regards,
      David and Colin

      Follow us on:

      Facebook: https://www.facebook.com/ForgedInteractive/
      Twitter: @ForgedInteract
      Youtube: https://www.youtube.com/channel/UCpK..._as=subscriber
      Reddit: https://www.reddit.com/user/Forged_Interactive/
    • By Eck
      I just saw their courses were knocked down to $10 each and figured I'd share the info here. They have stuff for Unity, Unreal, drawing, business, etc. I haven't used their stuff before, but the previews I looked at seemed pretty good and there is a user review system as well.
      https://www.udemy.com/courses/search/?q=Unity&src=ukw
      - Eck
       
    • By zizulot
      first and only logo , for now
    • By sidbhati32
      I am working on a game in which we control a rectangular box at the bottom of the screen. Three sphere which has alphabets in it fall down. When the game starts, a word is generated from the predefined list of words(which I'll give) and we are supposed to touch the correct sphere having the alphabet based on that word. The question is how to detect if I have touched the correct sphere. 
      secondly, if I have touched a correct sphere before and there is no recurrence of that alphabet in that word then during the second wave the game should not proceed if I touch the same alphabet again.
      Looking forward to your answers, i have to submit this project in a couple of days. please help! (Working on Unity 3D)
      Thanks
    • By NDraskovic
      Hey guys,   As the title says, I'm trying to control a desktop game by using my mobile phone as a controller.  I created two scenes, one that acts as a server, other as a client.    Server has this code: void Start () {         Test = "Nothing yet happened";         NetworkServer.Listen(25000);         NetworkServer.RegisterHandler(888, ServerReceiveMessage);     }         private void ServerReceiveMessage(NetworkMessage message)     {                 StringMessage msg = new StringMessage();         msg.value = message.ReadMessage<StringMessage>().value;         if (!String.IsNullOrEmpty(msg.value))         {             Test = "Message received";             string[] deltas = msg.value.Split('|');             Horizontal = Convert.ToSingle(deltas[0]);             Vertical = Convert.ToSingle(deltas[1]);             TestScript.MoveForward(Vertical);             TestScript.RotateAroundY(Horizontal);         }         else         {             Test = "Nothing received";         }     }  
        And client this:  private void Connect()     {              client.Connect(IPAddress, 25000);           }     void Start () {         client = new NetworkClient();         Connect();            }         void Update () {    #if UNITY_ANDROID         MobileTouches = Input.touches;         if (MobileTouches.Length > 0)         {             for (int i = 0; i < MobileTouches.Length; i++)             {                 if (MobileTouches[i].phase == TouchPhase.Moved)                 {                     Horizontal = MobileTouches[i].deltaPosition.x;                     Vertical = MobileTouches[i].deltaPosition.y;                 }else if(MobileTouches[i].phase == TouchPhase.Stationary)                 {                     Connect();                                  }             }         } #elif UNITY_EDITOR               Horizontal = Input.GetAxis("Horizontal");         Vertical = Input.GetAxis("Vertical"); #endif         thumb.Translate(Vector3.up * Vertical * Time.deltaTime);         thumb.Translate(Vector3.right * Horizontal * Time.deltaTime);         SendControllerInfo();     }     static public void SendControllerInfo()     {         if (client.isConnected)         {             StringMessage msg = new StringMessage();             msg.value = Horizontal + "|" + Vertical;             client.Send(888, msg);         }     }  
        Ip address is hard coded, I just replaced it with the "IpAddress" variable. The code itself builds fine, and when I try to run in on a desktop computer, it works as expected (just a simple movement of an object on the server screen). However when I try to publish the client scene to a mobile device (Android), it doesn't connect to the server. They are both connected to the same network. Can anyone tell me what the problem might be?   Thanks
  • Popular Now