Search Unity

Saving/Loading Large arrays in runtime, large lag problems (C#)

Discussion in 'Scripting' started by Thecheatgamer1, Nov 1, 2014.

  1. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    I have been toying around with my voxel terrain generation and because the game i am planning to create is a city simulator and will have multiplayer so will require large scale maps, i wanted to add chunk loading to the game so everyone who joined a game didnt have to load every single chunk, when i do save or load these pieces of voxel terrain, it laggs for about 10 seconds before running perfectly again

    This is what it looks like


    And these are some of the chunk files that have been unloaded and saved



    And here is my code

    Terrain Mesh Handler

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System;
    5. using System.Runtime.Serialization.Formatters.Binary;
    6. using System.IO;
    7. using System.Xml.Serialization;
    8.  
    9. public class TerrainMeshHandler : MonoBehaviour {
    10.  
    11.     private TerrainHandler terrain;
    12.  
    13.     public Material material;
    14.     public VoxelChunk[,] chunks;
    15.  
    16.     private bool currentlySaving = false;
    17.     private bool currentlyLoading = false;
    18.  
    19.     private PerlinNoise sufacePerlin;
    20.  
    21.     void Start () {
    22.         terrain = GetComponent<TerrainHandler>();
    23.  
    24.         chunks = new VoxelChunk[terrain.worldSize / terrain.chunkSize, terrain.worldSize / terrain.chunkSize];
    25.  
    26.         sufacePerlin = new PerlinNoise(terrain.seed);
    27.  
    28.         VoxelUtils.SetTarget(0F);
    29.         VoxelUtils.SetWindingOrder(2, 1, 0);
    30.         VoxelUtils.SetModeToTetrahedrons();
    31.     }
    32.  
    33.     public void LoadChunksAroundPlayer(Vector3 playerPos, int viewDistance) {
    34.         viewDistance = viewDistance * terrain.chunkSize;
    35.         for(int x = 0; x < chunks.GetLength(0); x++) {
    36.             for(int z = 0; z < chunks.GetLength(1); z++) {
    37.                 float dist = Vector2.Distance(new Vector2(x * terrain.chunkSize, z * terrain.chunkSize), new Vector2(playerPos.x, playerPos.z));
    38.                 if (dist < viewDistance){
    39.                     if (chunks[x, z] == null && currentlyLoading == false) {
    40.                         currentlyLoading = true;
    41.                         LoadChunk(x, z);
    42.                         currentlyLoading = false;
    43.                     }
    44.                 } else if (dist > viewDistance + terrain.chunkSize) {
    45.                     if(chunks[x, z] != null && currentlySaving == false) {
    46.                         currentlySaving = true;
    47.                         UnLoadChunk(x, z);
    48.                         currentlySaving = false;
    49.                     }
    50.                 }
    51.             }
    52.         }
    53.     }
    54.  
    55.     public void LoadChunk(int x, int z) {
    56.         if (!Directory.Exists(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/")) {
    57.             Directory.CreateDirectory(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/");
    58.         }
    59.         //Create the voxel object
    60.         chunks[x, z] = new VoxelChunk(new Vector3(x * terrain.voxelSize, 0, z * terrain.voxelSize), terrain.voxelSize, terrain.voxelSize * 10, terrain.voxelSize, terrain.worldSurfaceLevel);
    61.         if (!File.Exists(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/" + "Chunk - X" + x + " Z" + z + ".bin")) {
    62.             //Create the voxel data
    63.             chunks[x, z].CreateVoxels(sufacePerlin);
    64.             //Smooth the voxels, is optional but I think it looks nicer
    65.             chunks[x, z].SmoothVoxels();
    66.             //Create the normals. This will create smoothed normal.
    67.             //This is optional and if not called the unsmoothed mesh normals will be used
    68.             chunks[x, z].CalculateNormals();
    69.             //Creates the mesh form voxel data using the marching cubes plugin and creates the mesh collider
    70.             chunks[x, z].CreateMesh(material);
    71.         } else {
    72.             LoadData(x, z);
    73.         }
    74.     }
    75.  
    76.     public void UnLoadChunk(int x, int z) {
    77.         SaveData(x, z);
    78.         Destroy(chunks[x, z].m_mesh);
    79.         chunks[x, z] = null;
    80.     }
    81.  
    82.     public void LoadData(int x, int z) {
    83.         BinaryFormatter bf = new BinaryFormatter();
    84.         FileStream fs = File.Open(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/" + "Chunk - X" + x + " Z" + z + ".bin", FileMode.Open);
    85.         ChunkSaveData chunkData = (ChunkSaveData)bf.Deserialize(fs);
    86.         fs.Close();
    87.  
    88.         chunks[x, z].m_voxels = chunkData.m_voxels;
    89.  
    90.         chunks[x, z].m_normals = new Vector3[chunkData.m_normals.GetLength(0), chunkData.m_normals.GetLength(1), chunkData.m_normals.GetLength(2)];
    91.         for (int a = 0; a < chunkData.m_normals.GetLength(0); a++) {
    92.             for (int b = 0; b < chunkData.m_normals.GetLength(1); b++) {
    93.                 for (int c = 0; c < chunkData.m_normals.GetLength(2); c++) {
    94.                     chunks[x, z].m_normals[a, b, c] = chunkData.m_normals[a, b, c].Vector3;
    95.                 }
    96.             }
    97.         }
    98.         chunks[x, z].m_pos = chunkData.m_pos.Vector3;
    99.         chunks[x, z].m_surfaceLevel = chunkData. m_surfaceLevel;
    100.         chunks[x, z].CalculateNormals();
    101.         chunks[x, z].CreateMesh(material);
    102.     }
    103.  
    104.     /*
    105.      * Saving Chunks lags
    106.      */
    107.  
    108.     public void SaveData(int x, int z) {
    109.         BinaryFormatter bf = new BinaryFormatter();
    110.         FileStream fs = File.Create(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/" + "Chunk - X" + x + " Z" + z + ".dat");
    111.         ChunkSaveData chunkData = new ChunkSaveData();
    112.         chunkData.m_voxels = chunks[x, z].m_voxels;
    113.  
    114.         chunkData.m_normals = new SerialVec3[chunks[x, z].m_normals.GetLength(0), chunks[x, z].m_normals.GetLength(1), chunks[x, z].m_normals.GetLength(2)];
    115.         for (int a = 0; a < chunks[x, z].m_normals.GetLength(0); a++) {
    116.             for (int b = 0; b < chunks[x, z].m_normals.GetLength(1); b++) {
    117.                 for (int c = 0; c < chunks[x, z].m_normals.GetLength(2); c++) {
    118.                     chunkData.m_normals[a, b, c] = new SerialVec3(chunks[x, z].m_normals[a, b, c]);
    119.                 }
    120.             }
    121.         }
    122.         chunkData.m_pos = new SerialVec3(chunks[x, z].m_pos);
    123.         chunkData.m_surfaceLevel = chunks[x, z].m_surfaceLevel;
    124.  
    125.         bf.Serialize(fs, chunkData);
    126.         fs.Close();
    127.     }
    128. }
    129.  
    130. [Serializable]
    131. class ChunkSaveData {
    132.     public float[,,] m_voxels;
    133.     public SerialVec3[,,] m_normals;
    134.     public SerialVec3 m_pos;
    135.     public float m_surfaceLevel;
    136. }
    137.  
    VoxelChunk:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class VoxelChunk {
    5.  
    6.     public float[,,] m_voxels;
    7.     public Vector3[,,] m_normals;
    8.     public Vector3 m_pos;
    9.     public GameObject m_mesh;
    10.     public float m_surfaceLevel;
    11.  
    12.     int[,] m_sampler = new int[,] {
    13.         {1,-1,0}, {1,-1,1}, {0,-1,1}, {-1,-1,1}, {-1,-1,0}, {-1,-1,-1}, {0,-1,-1}, {1,-1,-1}, {0,-1,0}, {1,0,0}, {1,0,1}, {0,0,1}, {-1,0,1}, {-1,0,0},
    14.         {-1,0,-1}, {0,0,-1}, {1,0,-1}, {0,0,0}, {1,1,0}, {1,1,1}, {0,1,1}, {-1,1,1}, {-1,1,0}, {-1,1,-1}, {0,1,-1}, {1,1,-1}, {0,1,0}
    15.     };
    16.  
    17.     public VoxelChunk(Vector3 pos, int width, int height, int length, float surfaceLevel) {
    18.         m_surfaceLevel = surfaceLevel;
    19.         m_pos = pos;
    20.         //As we need some extra data to smooth the voxels and create the normals we need a extra 5 voxels
    21.         //+1 one to create a seamless mesh. +2 to create smoothed normals and +2 to smooth the voxels
    22.         //This is a little unoptimsed as it means some data is being generated that has alread been generated in other voxel chunks
    23.         //but it is simpler as we dont need to access the data in the other voxels. You could try and copy the data
    24.         //needed in for the other voxel chunks as a optimisation step
    25.         m_voxels = new float[width + 5, height + 5, length + 5];
    26.     }
    27.  
    28.     //The last three values in the noise functions are octaves, frequency and amplitude.
    29.     //More ocatves will create more detail but is slower.
    30.     //Higher/lower frquency will 'strech/shrink' out the noise.
    31.     //Amplitude defines roughly the range of the noise, ie amp = 1.0 means roughly -1.0 to +1.0 * range of noise
    32.     //The range of noise is 0.5 for 1D, 0.75 for 2D and 1.5 for 3D
    33.  
    34.     float SampleMountains(float x, float z, PerlinNoise perlin) {
    35.         //This creates the noise used for the mountains. It used something called
    36.         //domain warping. Domain warping is basically offseting the position used for the noise by
    37.         //another noise value. It tends to create a warped effect that looks nice.
    38.         float w = perlin.FractalNoise2D(x, z, 3, 120.0f, 32.0f);
    39.         //Clamp noise to 0 so mountains only occur where there is a positive value
    40.         //The last value (32.0f) is the amp that defines (roughly) the maximum mountaion height
    41.         //Change this to create high/lower mountains
    42.         return Mathf.Min(0.0f, perlin.FractalNoise2D(x+w, z+w, 6, 120.0f, 32.0f));
    43.     }
    44.  
    45.     float SampleGround(float x, float z, PerlinNoise perlin) {
    46.         //This creates the noise used for the ground.
    47.         //The last value (8.0f) is the amp that defines (roughly) the maximum
    48.         //and minimum vaule the ground varies from the surface level
    49.         return perlin.FractalNoise2D(x, z, 1, 80.0f, 8.0f);
    50.     }
    51.  
    52.     public void CreateVoxels(PerlinNoise surfacePerlin) {
    53.         //float startTime = Time.realtimeSinceStartup;
    54.      
    55.         //Creates the data the mesh is created form. Fills m_voxels with values between -1 and 1 where
    56.         //-1 is a soild voxel and 1 is a empty voxel.
    57.      
    58.         int w = m_voxels.GetLength(0);
    59.         int h = m_voxels.GetLength(1);
    60.         int l = m_voxels.GetLength(2);
    61.      
    62.         for(int x = 0; x < w; x++) {
    63.             for(int z = 0; z < l; z++) {
    64.                 //world pos is the voxels position plus the voxel chunks position
    65.                 float worldX = x+m_pos.x;
    66.                 float worldZ = z+m_pos.z;
    67.                 float groundHt = SampleGround(worldX, worldZ, surfacePerlin);
    68.                 float mountainHt = SampleMountains(worldX, worldZ, surfacePerlin);
    69.                 float mountainHit = mountainHt + groundHt;
    70.  
    71.                 for(int y = 0; y < h; y++) {
    72.                     float worldY = y+m_pos.y-m_surfaceLevel;
    73.                     //If we take the heigth value and add the world
    74.                     //the voxels will change from positiove to negative where the surface cuts through the voxel chunk
    75.                     m_voxels[x,y,z] = Mathf.Clamp(mountainHit + worldY , -1F, 1F);
    76.                     m_voxels[x,y,z] = Mathf.Clamp(m_voxels[x,y,z], -1F, 1F);
    77.                 }
    78.             }
    79.         }
    80.     }
    81.  
    82.     public void SmoothVoxels() {
    83.         //This averages a voxel with all its neighbours. Its is a optional step
    84.         //but I think it looks nicer. You might what to do a fancier smoothing step
    85.         //like a gaussian blur
    86.      
    87.         int w = m_voxels.GetLength(0);
    88.         int h = m_voxels.GetLength(1);
    89.         int l = m_voxels.GetLength(2);
    90.      
    91.         float[,,] smothedVoxels = new float[w,h,l];
    92.      
    93.         for(int x = 1; x < w-1; x++) {
    94.             for(int y = 1; y < h-1; y++) {
    95.                 for(int z = 1; z < l-1; z++) {
    96.                     float ht = 0.0f;
    97.                     for(int i = 0; i < 27; i++) {
    98.                         ht += m_voxels[x + m_sampler[i,0], y + m_sampler[i,1], z + m_sampler[i,2]];
    99.                     }
    100.                     smothedVoxels[x,y,z] = ht/27.0f;
    101.                 }
    102.             }
    103.         }
    104.         m_voxels = smothedVoxels;
    105.     }
    106.  
    107.     public void CalculateNormals() {
    108.         //This calculates the normal of each voxel. If you have a 3d array of data
    109.         //the normal is the derivitive of the x, y and z axis.
    110.         //Normally you need to flip the normal (*-1) but it is not needed in this case.
    111.         //If you dont call this function the normals that Unity generates for a mesh are used.
    112.      
    113.         int w = m_voxels.GetLength(0);
    114.         int h = m_voxels.GetLength(1);
    115.         int l = m_voxels.GetLength(2);
    116.      
    117.         if(m_normals == null) m_normals = new Vector3[w,h,l];
    118.         for(int x = 2; x < w-2; x++) {
    119.             for(int y = 2; y < h-2; y++) {
    120.                 for(int z = 2; z < l-2; z++) {
    121.                     float dx = m_voxels[x+1,y,z] - m_voxels[x-1,y,z];
    122.                     float dy = m_voxels[x,y+1,z] - m_voxels[x,y-1,z];
    123.                     float dz = m_voxels[x,y,z+1] - m_voxels[x,y,z-1];
    124.                     m_normals[x,y,z] = Vector3.Normalize(new Vector3(dx,dy,dz));
    125.                 }
    126.             }
    127.         }
    128.     }
    129.  
    130.     Vector3 TriLinearInterpNormal(Vector3 pos) {
    131.         int x = (int)pos.x;
    132.         int y = (int)pos.y;
    133.         int z = (int)pos.z;
    134.      
    135.         float fx = pos.x-x;
    136.         float fy = pos.y-y;
    137.         float fz = pos.z-z;
    138.      
    139.         Vector3 x0 = m_normals[x,y,z] * (1.0f-fx) + m_normals[x+1,y,z] * fx;
    140.         Vector3 x1 = m_normals[x,y,z+1] * (1.0f-fx) + m_normals[x+1,y,z+1] * fx;
    141.      
    142.         Vector3 x2 = m_normals[x,y+1,z] * (1.0f-fx) + m_normals[x+1,y+1,z] * fx;
    143.         Vector3 x3 = m_normals[x,y+1,z+1] * (1.0f-fx) + m_normals[x+1,y+1,z+1] * fx;
    144.      
    145.         Vector3 z0 = x0 * (1.0f-fz) + x1 * fz;
    146.         Vector3 z1 = x2 * (1.0f-fz) + x3 * fz;
    147.      
    148.         return z0 * (1.0f-fy) + z1 * fy;
    149.     }
    150.  
    151.     public void CreateMesh(Material mat) {
    152.         //float startTime = Time.realtimeSinceStartup;
    153.      
    154.         Mesh mesh = VoxelUtils.CreateMesh(m_voxels,2,2);
    155.         if(mesh == null) return;
    156.      
    157.         int size = mesh.vertices.Length;
    158.      
    159.         if(m_normals != null) {
    160.             Vector3[] normals = new Vector3[size];
    161.             Vector3[] verts = mesh.vertices;
    162.          
    163.             //Each verts in the mesh generated is its position in the voxel array
    164.             //and you can use this to find what the normal at this position.
    165.             //The verts are not at whole numbers though so you need to use trilinear interpolation
    166.             //to find the normal for that position
    167.          
    168.             for(int i = 0; i < size; i++) {
    169.                 normals[i] = TriLinearInterpNormal(verts[i]);
    170.             }
    171.          
    172.             mesh.normals = normals;
    173.         } else {
    174.             mesh.RecalculateNormals();
    175.         }
    176.      
    177.         Color[] control = new Color[size];
    178.         Vector3[] meshNormals = mesh.normals;
    179.          
    180.         for(int i = 0; i < size; i++) {
    181.             //This creates a control map used to texture the mesh based on the slope
    182.             //of the vert. Its very basic and if you modify how this works yoou will
    183.             //you will probably need to modify the shader as well.
    184.             float dpUp = Vector3.Dot(meshNormals[i], Vector3.up);
    185.          
    186.             //Red channel is the sand on flat areas
    187.             float R = (Mathf.Max(0.0f, dpUp) < 0.8f) ? 0.0f : 1.0f;
    188.             //Green channel is the gravel on the sloped areas
    189.             float G = Mathf.Pow(Mathf.Abs(dpUp), 2.0f);
    190.          
    191.             //Whats left end up being the rock face on the vertical areas
    192.  
    193.             control[i] = new Color(R,G,0,0);
    194.         }
    195.      
    196.         //May as well store in colors
    197.         mesh.colors = control;
    198.      
    199.         m_mesh = new GameObject("Voxel Terrain Mesh @ X:" + m_pos.x + " Z:" + m_pos.z);
    200.         m_mesh.AddComponent<MeshFilter>();
    201.         m_mesh.AddComponent<MeshRenderer>();
    202.         m_mesh.renderer.material = mat;
    203.         m_mesh.GetComponent<MeshFilter>().mesh = mesh;
    204.         m_mesh.transform.localPosition = m_pos;
    205.      
    206.         MeshCollider collider = m_mesh.AddComponent<MeshCollider>();
    207.         collider.sharedMesh = mesh;
    208.  
    209.         m_mesh.transform.parent = GameObject.Find("Terrain").transform;
    210.     }
    211. }
    212.  
    SerialVec3:

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections;
    4. using System.Runtime.Serialization;
    5.  
    6. [Serializable]
    7. public class SerialVec3 {
    8.      public double X;
    9.      public double Y;
    10.      public double Z;
    11.  
    12.      public Vector3 Vector3 {
    13.          get {
    14.              return new Vector3((float)X, (float)Y, (float)Z);
    15.          }
    16.      }
    17.  
    18.      public SerialVec3() { }
    19.  
    20.      public SerialVec3(Vector3 vector) {
    21.          double val;
    22.          X = double.TryParse(vector.x.ToString(), out val) ? val : 0.0;
    23.          Y = double.TryParse(vector.y.ToString(), out val) ? val : 0.0;
    24.          Z = double.TryParse(vector.z.ToString(), out val) ? val : 0.0;
    25.      }    
    26. }
    27.  
    If anyone could recommend anything to me that would be perfect! :)

    If you want to witness how the lag acts, download the dev build of the game at http://www.clansyte.net/unityhelp/projectUtopia.zip
     
    Last edited: Nov 1, 2014
  2. ColossalDuck

    ColossalDuck

    Joined:
    Jun 6, 2009
    Posts:
    3,246
    If I were you, I would look into doing this on a separate thread rather than doing it all on the main tread.
     
  3. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    Perhaps, save all in one file, improve performance:

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3.  
    4. namespace Data{
    5.  
    6.     [Serializable]
    7.     public class SerialVec3{
    8.        
    9.         public float x,y,z;
    10.        
    11.         public SerialVec3(Vector3 vec){ this.x=vec.x;this.y=vec.y;this.z=vec.z; }
    12.         public SerialVec3(float x,float y, float z){ this.x=x;this.y=y;this.z=z; }
    13.        
    14.         public static implicit operator Vector3(SerialVec3 vec){ return new Vector3(vec.x,vec.y,vec.z); }
    15.         public static implicit operator SerialVec3(Vector3 vec){ return new SerialVec3(vec); }
    16.  
    17.     }
    18.    
    19.     [Serializable]
    20.     public class Chunk{
    21.        
    22.        
    23.         public float[,,] m_voxels;
    24.         public SerialVec3[,,] m_normals;
    25.         public SerialVec3 m_pos;
    26.         public float m_surfaceLevel;
    27.        
    28.         public Chunk(float[,,] m_voxels, Vector3[,,] m_normals, Vector3 m_pos, float m_surfaceLevel){
    29.            
    30.             this.m_voxels = m_voxels;
    31.             this.m_surfaceLevel = m_surfaceLevel;
    32.             this.m_pos = m_pos;
    33.  
    34.             int a = m_normals.GetLength(0);
    35.             int b = m_normals.GetLength(1);
    36.             int c = m_normals.GetLength(2);
    37.  
    38.             this.m_normals = new SerialVec3[a,b,c];
    39.  
    40.             for(int x=0;x<a;x++) for(int y=0;y<b;y++) for(int z=0;z<c;z++){ this.m_normals[x,y,z] = m_normals[x,y,z]; }
    41.            
    42.         }
    43.        
    44.     }
    45.    
    46.     [Serializable]
    47.     public class ChunkSaveData{      
    48.        
    49.         public Chunk[] chunks;
    50.        
    51.         public ChunkSaveData(Chunk[] chunks){ this.chunks = chunks; }
    52.        
    53.     }  
    54.    
    55. }
    I have created an implicit conversion between serialVec3 and vector3 to not use methods.

    And I've kept all the objects in an array chunks, ChunkSaveData.
     
  4. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    That is what i would do but apparently Unity's main thread doesn like that
     
  5. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    Hmm, this looks interesting, i will try this in a moment
     
  6. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    Thanks, this did help but it still lags intensively when it saves chunks and there is still a noticable lag spike when loading too, just too note this will save and load multiple chunks at once depending on the players view distance what will most likely be at 32 so.... if there is no way of doing this better i will have to change my plans on chunk loading
     
  7. ZO5KmUG6R

    ZO5KmUG6R

    Joined:
    Jul 15, 2010
    Posts:
    490
    Saving in one file is a very good idea. I wouldn't want my hard drive doing all that extra work you have in the first pos.

    Just make the header likie this

    DAT{
    int32 numChunks;
    int32 chunkOffsets[numChunks];
    }

    Then you would know where they are in the file. Also load it in a seperate thread as KHopcraft says
     
  8. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    I think i will have a file for each variable storage and also i cant make another thread due to apparently the Unity Thread doesnt like it and has checks for this
     
  9. cl9-2

    cl9-2

    Joined:
    May 31, 2013
    Posts:
    417
  10. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    No, this feature was adding in .NET framework 4.0 and Unity uses 3.5
     
  11. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    I have done this but my normals file reaches hundreds of megabytes from a very short distance of generation, is there any other way then saving the normals, this terrain will be modifiable so i need to save the terrain to its last state other than its generation seed and hard drive speed is a issue here too
     
  12. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    Also i dont get how you tag data in the file and then find it, can you explain?
     
  13. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    Modifying anything that depends on the main thread is bad to do in another thread.

    So you can't create GameObjects, or move them around, in separate thread.

    But doing the reads and writes to disk can by multi-threaded.



    I'd thread out what can be threaded. Then stagger the spawning of the scene objects over several frames.

    This can be done with a Coroutine. The Coroutine starts a thread to load the stuff and then yields until that thread is done. Then you divy up the data into smaller chunks and yield frame by frame generating scene objects as you go. The size of those smaller chunks will take some tweaking.
     
  14. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    I never thought of that, im trying it right now, hopefully i dont blue screen my computer
     
  15. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30

    Iv tried creating a thread but it doesnt seem to work because unity can detect it, and i also had to move a lot of variables to the new file too

    Code (CSharp):
    1.     public class DataManagement {
    2.  
    3.         private FileStream voxelsFile;
    4.         private FileStream normalsFile;
    5.         private FileStream positionsFile;
    6.         private FileStream surfaceLevelFile;
    7.         private BinaryFormatter serial;
    8.  
    9.         private Thread loadThread;
    10.         private Thread unloadThread;
    11.  
    12.         public DataManagement() {
    13.             serial = new BinaryFormatter();
    14.  
    15.             voxelsFile = File.Open(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/" + "Chunk - VoxelData.dat", FileMode.OpenOrCreate);
    16.             normalsFile = File.Open(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/" + "Chunk - NormalData.dat", FileMode.OpenOrCreate);
    17.             positionsFile = File.Open(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/" + "Chunk - PositionData.dat", FileMode.OpenOrCreate);
    18.             surfaceLevelFile = File.Open(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/" + "Chunk - SurfaceLevelData.dat", FileMode.OpenOrCreate);
    19.  
    20.             if (!Directory.Exists(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/")) {
    21.                 Directory.CreateDirectory(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/");
    22.             }
    23.         }
    24.  
    25.         public void LoadData(int x, int z) {
    26.             loadThread = new Thread(() => LoadData(x, z));
    27.             loadThread.Start();
    28.         }
    29.  
    30.         public void SaveData(int x, int z) {
    31.             unloadThread = new Thread(() => SaveThreaded(x, z));
    32.             unloadThread.Start();
    33.         }
    34.  
    35.         public void LoadThreaded(int x, int z) {
    36.             GameObject.Find("Terrain").GetComponent<TerrainMeshHandler>().chunks[x, z].m_voxels = (float[,,])serial.Deserialize(voxelsFile);
    37.             GameObject.Find("Terrain").GetComponent<TerrainMeshHandler>().chunks[x, z].m_normals = (Vector3[,,])serial.Deserialize(normalsFile);
    38.             GameObject.Find("Terrain").GetComponent<TerrainMeshHandler>().chunks[x, z].m_pos = (Vector3)serial.Deserialize(positionsFile);
    39.             GameObject.Find("Terrain").GetComponent<TerrainMeshHandler>().chunks[x, z].m_surfaceLevel = (float)serial.Deserialize(surfaceLevelFile);
    40.             loadThread.Join();
    41.         }
    42.  
    43.         public void SaveThreaded(int x, int z) {
    44.             serial.Serialize(voxelsFile, GameObject.Find("Terrain").GetComponent<TerrainMeshHandler>().chunks[x, z].m_voxels);
    45.             serial.Serialize(normalsFile, new ChunkSave.ChunkNormals(GameObject.Find("Terrain").GetComponent<TerrainMeshHandler>().chunks[x, z].m_normals));
    46.             serial.Serialize(positionsFile, new ChunkSave.ChunkPosition(GameObject.Find("Terrain").GetComponent<TerrainMeshHandler>().chunks[x, z].m_pos));
    47.             serial.Serialize(surfaceLevelFile, GameObject.Find("Terrain").GetComponent<TerrainMeshHandler>().chunks[x, z].m_surfaceLevel);
    48.             unloadThread.Join();
    49.         }
    50.     }
     
  16. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    Here is a updated version, it happens when i call a method in this class from another that is used in a scene

    Code (CSharp):
    1.     public class DataManagement {
    2.         private FileStream voxelsFile;
    3.         private FileStream normalsFile;
    4.         private FileStream positionsFile;
    5.         private FileStream surfaceLevelFile;
    6.         private BinaryFormatter serial;
    7.  
    8.         private Thread loadThread;
    9.         private Thread unloadThread;
    10.  
    11.         public DataManagement(string appString) {
    12.             serial = new BinaryFormatter();
    13.  
    14.             voxelsFile = File.Open(appString + "/Saves/" + CityHandler.cityName + "/" + "Chunk - VoxelData.dat", FileMode.OpenOrCreate);
    15.             normalsFile = File.Open(appString + "/Saves/" + CityHandler.cityName + "/" + "Chunk - NormalData.dat", FileMode.OpenOrCreate);
    16.             positionsFile = File.Open(appString + "/Saves/" + CityHandler.cityName + "/" + "Chunk - PositionData.dat", FileMode.OpenOrCreate);
    17.             surfaceLevelFile = File.Open(appString + "/Saves/" + CityHandler.cityName + "/" + "Chunk - SurfaceLevelData.dat", FileMode.OpenOrCreate);
    18.         }
    19.  
    20.         public void LoadData(int x, int z) {
    21.             loadThread = new Thread(() => LoadData(x, z));
    22.             loadThread.Start();
    23.         }
    24.  
    25.         public void SaveData(int x, int z) {
    26.             unloadThread = new Thread(() => SaveThreaded(x, z));
    27.             unloadThread.Start();
    28.         }
    29.  
    30.         public void LoadThreaded(int x, int z) {
    31.             TerrainMeshHandler.chunks[x, z].m_voxels = (float[,,])serial.Deserialize(voxelsFile);
    32.             TerrainMeshHandler.chunks[x, z].m_normals = (Vector3[,,])serial.Deserialize(normalsFile);
    33.             TerrainMeshHandler.chunks[x, z].m_pos = (Vector3)serial.Deserialize(positionsFile);
    34.             TerrainMeshHandler.chunks[x, z].m_surfaceLevel = (float)serial.Deserialize(surfaceLevelFile);
    35.             loadThread.Join();
    36.         }
    37.  
    38.         public void SaveThreaded(int x, int z) {
    39.             serial.Serialize(voxelsFile, TerrainMeshHandler.chunks[x, z].m_voxels);
    40.             serial.Serialize(normalsFile, new ChunkSave.ChunkNormals(TerrainMeshHandler.chunks[x, z].m_normals));
    41.             serial.Serialize(positionsFile, new ChunkSave.ChunkPosition(TerrainMeshHandler.chunks[x, z].m_pos));
    42.             serial.Serialize(surfaceLevelFile, TerrainMeshHandler.chunks[x, z].m_surfaceLevel);
    43.             unloadThread.Join();
    44.         }
    45.     }
     
  17. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    Are you sure this is correct?
    I think you meant to pass a delegate to LoadThreaded, not LoadData. Also I don't think your implementation of LoadThreaded is going to work since you're still interacting with GameObjects. LoadThreaded should return a generic, non-MonoBehaviour-derived type. Then you can use that to build your terrain from the main thread.
     
  18. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    Im not sure what to do then because the variables that are set and saved are in a class that creates a gameobject within it self and the ways around it will require large amounts of code, and im not sure what you mean by delegate? i know there is a method type called delegate but im not sure what your trying to tell me, there is also the problem of starting the thread until all variables for that chunk are set and ready to be used for rendering and then stopping it
     
  19. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    I meant that inside LoadData, instead of this:
    Code (CSharp):
    1. loadThread =new Thread(()=> LoadData(x, z));
    You should have this:
    Code (CSharp):
    1. loadThread =new Thread(()=> LoadThreaded(x, z));
    As to not interacting with the GameObjects directly from the other thread, here's an example: Why not modify LoadThreaded to have the following signature:

    Code (CSharp):
    1. void LoadThreaded(int x, int z, out float[,] voxels, out Vector3[,] normals, /* etc */ )
    Have it load your data from desk and return it through the out parameters. Then use that from your main thread to populate your GameObjects.
     
    Last edited: Nov 3, 2014
  20. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    Oh, this seems interesting, i didnt know you could return variables like this in C#, ill start experimenting with it now, Thanks
     
  21. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    Now its coming up with this error

    ChunkSave:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System;
    5. using System.Threading;
    6. using System.Runtime.Serialization.Formatters.Binary;
    7. using System.IO;
    8. using System.Xml.Serialization;
    9.  
    10. namespace ChunkSave {
    11.  
    12.     [Serializable]
    13.     public class SerialVec3 {
    14.         public float x, y, z;
    15.  
    16.         public SerialVec3(Vector3 vec) {
    17.             this.x = vec.x;
    18.             this.y = vec.y;
    19.             this.z = vec.z;
    20.         }
    21.  
    22.         public SerialVec3(float x,float y, float z) {
    23.             this.x = x;
    24.             this.y = y;
    25.             this.z = z;
    26.         }
    27.  
    28.         public static implicit operator Vector3(SerialVec3 vec) {
    29.             return new Vector3(vec.x, vec.y, vec.z);
    30.         }
    31.  
    32.         public static implicit operator SerialVec3(Vector3 vec) {
    33.             return new SerialVec3(vec);
    34.         }
    35.     }
    36.  
    37.     [Serializable]
    38.     public class Vector3ToSerial {
    39.         public SerialVec3[,,] normals;
    40.  
    41.         public Vector3ToSerial(Vector3[,,] normals) {
    42.             int a = normals.GetLength(0);
    43.             int b = normals.GetLength(1);
    44.             int c = normals.GetLength(2);
    45.  
    46.             this.normals = new SerialVec3[a, b, c];
    47.  
    48.             for (int x = 0; x < a; x++) {
    49.                 for (int y = 0; y < b; y++) {
    50.                     for(int z = 0; z < c; z++) {
    51.                         this.normals[x, y, z] = normals[x, y, z];
    52.                     }
    53.                 }
    54.             }
    55.         }
    56.     }
    57.  
    58.     [Serializable]
    59.     public class SerialToVector3 {
    60.         public Vector3[,,] normals;
    61.  
    62.         public SerialToVector3(SerialVec3[,,] normals) {
    63.             int a = normals.GetLength(0);
    64.             int b = normals.GetLength(1);
    65.             int c = normals.GetLength(2);
    66.  
    67.             this.normals = new Vector3[a, b, c];
    68.  
    69.             for (int x = 0; x < a; x++) {
    70.                 for (int y = 0; y < b; y++) {
    71.                     for(int z = 0; z < c; z++) {
    72.                         this.normals[x, y, z] = normals[x, y, z];
    73.                     }
    74.                 }
    75.             }
    76.         }
    77.     }
    78.  
    79.     [Serializable]
    80.     public class ChunkPosition {
    81.         public SerialVec3 position;
    82.  
    83.         public ChunkPosition(Vector3 position) {
    84.             this.position = position;
    85.         }
    86.     }
    87.  
    88.     public class DataManagement {
    89.         public Thread loadThread;
    90.         public Thread unloadThread;
    91.  
    92.         private string appString;
    93.  
    94.         public DataManagement(string appString) {
    95.             this.appString = appString;
    96.         }
    97.  
    98.         public void LoadData(int x, int z, out float[,,] voxels, out SerialVec3[,,] normals, out SerialVec3 position, out float surfaceLevel) {
    99.             BinaryFormatter serial = new BinaryFormatter();
    100.             FileStream saveFile = File.Open(appString + "/Saves/" + CityHandler.cityName + "/" + "Chunk - X" + x + " Z" + z + ".dat", FileMode.OpenOrCreate);
    101.             ChunkData data = (ChunkData)serial.Deserialize(saveFile);
    102.             voxels = data.voxels;
    103.             normals = data.normals;
    104.             position = data.pos;
    105.             surfaceLevel = data.surfaceLevel;
    106.             loadThread.Join();
    107.         }
    108.  
    109.         public void SaveData(int x, int z, float[,,] voxels, SerialVec3[,,] normals, SerialVec3 position, float surfaceLevel) {
    110.             BinaryFormatter serial = new BinaryFormatter();
    111.             FileStream saveFile = File.Open(appString + "/Saves/" + CityHandler.cityName + "/" + "Chunk - X" + x + " Z" + z + ".dat", FileMode.OpenOrCreate);
    112.             ChunkData data = new ChunkData();
    113.             data.voxels = voxels;
    114.             data.normals = normals;
    115.             data.pos = position;
    116.             data.surfaceLevel = surfaceLevel;
    117.  
    118.             serial.Serialize(saveFile, voxels);
    119.             unloadThread.Join();
    120.         }
    121.     }
    122.  
    123.     [Serializable]
    124.     public class ChunkData {
    125.         public float[,,] voxels;
    126.         public SerialVec3[,,] normals;
    127.         public SerialVec3 pos;
    128.         public float surfaceLevel;
    129.     }
    130. }
    131.  
    TerrainMeshHandler:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System;
    5. using System.Threading;
    6. using System.Runtime.Serialization.Formatters.Binary;
    7. using System.IO;
    8. using System.Xml.Serialization;
    9.  
    10. public class TerrainMeshHandler : MonoBehaviour {
    11.  
    12.     private TerrainHandler terrain;
    13.  
    14.     public Material material;
    15.     public static VoxelChunk[,] chunks;
    16.  
    17.     private bool currentlySaving = false;
    18.     private bool currentlyLoading = false;
    19.  
    20.     private PerlinNoise sufacePerlin;
    21.  
    22.     private ChunkSave.DataManagement data = new ChunkSave.DataManagement(Application.persistentDataPath);
    23.  
    24.     void Start () {
    25.         terrain = GetComponent<TerrainHandler>();
    26.  
    27.         chunks = new VoxelChunk[terrain.worldSize / terrain.chunkSize, terrain.worldSize / terrain.chunkSize];
    28.  
    29.         sufacePerlin = new PerlinNoise(terrain.seed);
    30.  
    31.         VoxelUtils.SetTarget(0F);
    32.         VoxelUtils.SetWindingOrder(2, 1, 0);
    33.         VoxelUtils.SetModeToTetrahedrons();
    34.  
    35.         if (!Directory.Exists(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/")) {
    36.             Directory.CreateDirectory(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/");
    37.         }
    38.  
    39.         if (!Directory.Exists(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/Chunks/")) {
    40.             Directory.CreateDirectory(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/Chunks/");
    41.         }
    42.     }
    43.  
    44.     public void LoadChunksAroundPlayer(Vector3 playerPos, int viewDistance) {
    45.         viewDistance = viewDistance * terrain.chunkSize;
    46.         for(int x = 0; x < chunks.GetLength(0); x++) {
    47.             for(int z = 0; z < chunks.GetLength(1); z++) {
    48.                 float dist = Vector2.Distance(new Vector2(x * terrain.chunkSize, z * terrain.chunkSize), new Vector2(playerPos.x, playerPos.z));
    49.                 if (dist < viewDistance){
    50.                     if (chunks[x, z] == null && currentlyLoading == false) {
    51.                         currentlyLoading = true;
    52.                         LoadChunk(x, z);
    53.                         currentlyLoading = false;
    54.                     }
    55.                 } else if (dist > viewDistance + terrain.chunkSize) {
    56.                     if(chunks[x, z] != null && currentlySaving == false) {
    57.                         currentlySaving = true;
    58.                         UnLoadChunk(x, z);
    59.                         currentlySaving = false;
    60.                     }
    61.                 }
    62.             }
    63.         }
    64.     }
    65.  
    66.     public void LoadChunk(int x, int z) {
    67.         //Create the voxel object
    68.         chunks[x, z] = new VoxelChunk(new Vector3(x * terrain.voxelSize, 0, z * terrain.voxelSize), terrain.voxelSize, terrain.voxelSize * 10, terrain.voxelSize, terrain.worldSurfaceLevel);
    69.         if (!File.Exists(Application.persistentDataPath + "/Saves/" + CityHandler.cityName + "/" + "Chunk - X" + x + " Z" + z + ".bin")) {
    70.             //Create the voxel data
    71.             chunks[x, z].CreateVoxels(sufacePerlin);
    72.             //Smooth the voxels, is optional but I think it looks nicer
    73.             chunks[x, z].SmoothVoxels();
    74.             //Create the normals. This will create smoothed normal.
    75.             //This is optional and if not called the unsmoothed mesh normals will be used
    76.             chunks[x, z].CalculateNormals();
    77.             //Creates the mesh form voxel data using the marching cubes plugin and creates the mesh collider
    78.             chunks[x, z].CreateMesh(material);
    79.             data.unloadThread = new Thread(() => data.SaveData(x, z, chunks[x, z].m_voxels, new ChunkSave.Vector3ToSerial(chunks[x, z].m_normals).normals, new ChunkSave.ChunkPosition(chunks[x, z].m_pos).position, chunks[x, z].m_surfaceLevel));
    80.             data.unloadThread.Start();
    81.         } else {
    82.             float[,,] voxels;
    83.             ChunkSave.SerialVec3[,,] normals;
    84.             ChunkSave.SerialVec3 position;
    85.             float surfaceLevel;
    86.             data.LoadData(x, z, out voxels, out normals, out position, out surfaceLevel);
    87.             chunks[x, z].m_voxels = voxels;
    88.             chunks[x, z].m_normals = new ChunkSave.SerialToVector3(normals).normals;
    89.             chunks[x, z].m_pos = position;
    90.             chunks[x, z].m_surfaceLevel = surfaceLevel;
    91.             chunks[x, z].CalculateNormals();
    92.             chunks[x, z].CreateMesh(material);
    93.         }
    94.     }
    95.  
    96.     public void UnLoadChunk(int x, int z) {
    97.         data.unloadThread = new Thread(() => data.SaveData(x, z, chunks[x, z].m_voxels, new ChunkSave.Vector3ToSerial(chunks[x, z].m_normals).normals, new ChunkSave.ChunkPosition(chunks[x, z].m_pos).position, chunks[x, z].m_surfaceLevel));
    98.         data.unloadThread.Start();
    99.         Destroy(chunks[x, z].m_mesh);
    100.         chunks[x, z] = null;
    101.     }
    102. }
    103.  
     
  22. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    The error message is quite clear. Look at line 22 in your second code snippet. You're initializing the variable by using Application.persistentDataPath, and the error message is telling you that that's a no-no. The solution is also quite clear from the error message.

    A friendly tip: Posting walls of code and asking people to wade through them is most likely going to put people off.
     
  23. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    I dont see how this is clear but ok, i will just have to use a custom save path :/
     
  24. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    The error message you posted explicitly said to "Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function." You have that and I gave you the line number, which is 22. So the solution would be to "move the initialization code at line 22 to the Awake or Start function," which is the combination of two bits of information that were given to you directly and in no uncertain terms, which strikes this rather average programmer as being quite obvious and pretty clear.

    And if you don't know what "initialization code" is, then it's the stuff after the equals (=) sign in this particular case.
     
  25. Thecheatgamer1

    Thecheatgamer1

    Joined:
    Jul 9, 2013
    Posts:
    30
    I know how it works but i have already tried putting it in the awake and start methods but it still comes up with the same, im getting confused by this error