• Advertisement
Sign in to follow this  

Unity Optimizing Generation

This topic is 664 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

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
Advertisement

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

Found it! Using the profiler in deep profile mode, I figured out my noise being used for cave generation and ore gen was using 6 octaves. Cut it down to one octave, 60fps.

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?

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];

 

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
How would I hold all the necessary data within one of these block types? I'm not using much yet, but in the future I'm planning storage blocks, machines, and more.

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
Sign in to follow this  

  • Advertisement
  • Advertisement
  • Popular Tags

  • Advertisement
  • Popular Now

  • Similar Content

    • By GytisDev
      Hello,
      without going into any details I am looking for any articles or blogs or advice about city building and RTS games in general. I tried to search for these on my own, but would like to see your input also. I want to make a very simple version of a game like Banished or Kingdoms and Castles,  where I would be able to place like two types of buildings, make farms and cut trees for resources while controlling a single worker. I have some problem understanding how these games works in the back-end: how various data can be stored about the map and objects, how grids works, implementing work system (like a little cube (human) walks to a tree and cuts it) and so on. I am also pretty confident in my programming capabilities for such a game. Sorry if I make any mistakes, English is not my native language.
      Thank you in advance.
    • By Ovicior
      Hey,
      So I'm currently working on a rogue-like top-down game that features melee combat. Getting basic weapon stats like power, weight, and range is not a problem. I am, however, having a problem with coming up with a flexible and dynamic system to allow me to quickly create unique effects for the weapons. I want to essentially create a sort of API that is called when appropriate and gives whatever information is necessary (For example, I could opt to use methods called OnPlayerHit() or IfPlayerBleeding() to implement behavior for each weapon). The issue is, I've never actually made a system as flexible as this.
      My current idea is to make a base abstract weapon class, and then have calls to all the methods when appropriate in there (OnPlayerHit() would be called whenever the player's health is subtracted from, for example). This would involve creating a sub-class for every weapon type and overriding each method to make sure the behavior works appropriately. This does not feel very efficient or clean at all. I was thinking of using interfaces to allow for the implementation of whatever "event" is needed (such as having an interface for OnPlayerAttack(), which would force the creation of a method that is called whenever the player attacks something).
       
      Here's a couple unique weapon ideas I have:
      Explosion sword: Create explosion in attack direction.
      Cold sword: Chance to freeze enemies when they are hit.
      Electric sword: On attack, electricity chains damage to nearby enemies.
       
      I'm basically trying to create a sort of API that'll allow me to easily inherit from a base weapon class and add additional behaviors somehow. One thing to know is that I'm on Unity, and swapping the weapon object's weapon component whenever the weapon changes is not at all a good idea. I need some way to contain all this varying data in one Unity component that can contain a Weapon field to hold all this data. Any ideas?
       
      I'm currently considering having a WeaponController class that can contain a Weapon class, which calls all the methods I use to create unique effects in the weapon (Such as OnPlayerAttack()) when appropriate.
    • By Vu Chi Thien
      Hi fellow game devs,
      First, I would like to apologize for the wall of text.
      As you may notice I have been digging in vehicle simulation for some times now through my clutch question posts. And thanks to the generous help of you guys, especially @CombatWombat I have finished my clutch model (Really CombatWombat you deserve much more than a post upvote, I would buy you a drink if I could ha ha). 
      Now the final piece in my vehicle physic model is the differential. For now I have an open-differential model working quite well by just outputting torque 50-50 to left and right wheel. Now I would like to implement a Limited Slip Differential. I have very limited knowledge about LSD, and what I know about LSD is through readings on racer.nl documentation, watching Youtube videos, and playing around with games like Assetto Corsa and Project Cars. So this is what I understand so far:
      - The LSD acts like an open-diff when there is no torque from engine applied to the input shaft of the diff. However, in clutch-type LSD there is still an amount of binding between the left and right wheel due to preload spring.
      - When there is torque to the input shaft (on power and off power in 2 ways LSD), in ramp LSD, the ramp will push the clutch patch together, creating binding force. The amount of binding force depends on the amount of clutch patch and ramp angle, so the diff will not completely locked up and there is still difference in wheel speed between left and right wheel, but when the locking force is enough the diff will lock.
      - There also something I'm not sure is the amount of torque ratio based on road resistance torque (rolling resistance I guess)., but since I cannot extract rolling resistance from the tire model I'm using (Unity wheelCollider), I think I would not use this approach. Instead I'm going to use the speed difference in left and right wheel, similar to torsen diff. Below is my rough model with the clutch type LSD:
      speedDiff = leftWheelSpeed - rightWheelSpeed; //torque to differential input shaft. //first treat the diff as an open diff with equal torque to both wheels inputTorque = gearBoxTorque * 0.5f; //then modify torque to each wheel based on wheel speed difference //the difference in torque depends on speed difference, throttleInput (on/off power) //amount of locking force wanted at different amount of speed difference, //and preload force //torque to left wheel leftWheelTorque = inputTorque - (speedDiff * preLoadForce + lockingForce * throttleInput); //torque to right wheel rightWheelTorque = inputTorque + (speedDiff * preLoadForce + lockingForce * throttleInput); I'm putting throttle input in because from what I've read the amount of locking also depends on the amount of throttle input (harder throttle -> higher  torque input -> stronger locking). The model is nowhere near good, so please jump in and correct me.
      Also I have a few questions:
      - In torsen/geared LSD, is it correct that the diff actually never lock but only split torque based on bias ratio, which also based on speed difference between wheels? And does the bias only happen when the speed difference reaches the ratio (say 2:1 or 3:1) and below that it will act like an open diff, which basically like an open diff with an if statement to switch state?
      - Is it correct that the amount of locking force in clutch LSD depends on amount of input torque? If so, what is the threshold of the input torque to "activate" the diff (start splitting torque)? How can I get the amount of torque bias ratio (in wheelTorque = inputTorque * biasRatio) based on the speed difference or rolling resistance at wheel?
      - Is the speed at the input shaft of the diff always equals to the average speed of 2 wheels ie (left + right) / 2?
      Please help me out with this. I haven't found any topic about this yet on gamedev, and this is my final piece of the puzzle. Thank you guys very very much.
    • By Estra
      Memory Trees is a PC game and Life+Farming simulation game. Harvest Moon and Rune Factory , the game will be quite big. I believe that this will take a long time to finish
      Looking for
      Programmer
      1 experience using Unity/C++
      2 have a portfolio of Programmer
      3 like RPG game ( Rune rune factory / zelda series / FF series )
      4 Have responsibility + Time Management
      and friendly easy working with others Programmer willing to use Skype for communication with team please E-mail me if you're interested
      Split %: Revenue share. We can discuss. Fully Funded servers and contents
      and friendly easy working with others willing to use Skype for communication with team please E-mail me if you're interested
      we can talk more detail in Estherfanworld@gmail.com Don't comment here
      Thank you so much for reading
      More about our game
      Memory Trees : forget me not

      Thank you so much for reading
      Ps.Please make sure that you have unity skill and Have responsibility + Time Management,
      because If not it will waste time not one but both of us
       

  • Advertisement