XML serialisaton. Error: XmlException: Root element is missing.

MaksimMaltsev

Member
Joined
Mar 29, 2021
Messages
9
Programming Experience
1-3
Hi all. I am making a game like minecraft. I need to keep the world when the player exits the game. Chunk (it is a place 16 by 16 blocks) and world contain a lot datas, that don't serialize can. so i have created classes WorldData and ChunkData, that contain only serializable varibles. But i have a error when world is serialized: XmlException: Root element is missing. This is the code:

WorldData:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.IO;
using System.Xml.Serialization;

public class WorldData{
    public static XmlSerializableDictionary<Vector2, ChunkData> bloksData = new XmlSerializableDictionary<Vector2, ChunkData>();//deserialized dictionary
    public static XmlSerializableDictionary<VectorConventer, ChunkData> bloksConvertData = new XmlSerializableDictionary<VectorConventer, ChunkData>();//serializible dictionary

    //get deserialized world <Vector2, ChunkData>
    public static XmlSerializableDictionary<Vector2, ChunkData> Deserialization(){
        XmlSerializer WorldXML = new XmlSerializer(typeof(XmlSerializableDictionary<VectorConventer, ChunkData>));
        using (FileStream fs = new FileStream("New_World.xml", FileMode.OpenOrCreate)){
            bloksConvertData = (XmlSerializableDictionary<VectorConventer, ChunkData>)WorldXML.Deserialize(fs); //Error. XmlException: Root element is missing.
        }
        foreach(var item in bloksData){
            bloksConvertData[new VectorConventer(item.Key)] = item.Value;
        }
        return bloksData;
    }

    //serialisation
    static void SaveWorld(){
        foreach(var item in bloksData){
            VectorConventer keyVec = new VectorConventer(item.Key);
            bloksConvertData[keyVec] = item.Value;
        }
        XmlSerializer WorldXML = new XmlSerializer(typeof(XmlSerializableDictionary<VectorConventer, ChunkData>));
        using (FileStream fs = new FileStream("New_World.xml", FileMode.OpenOrCreate)){
            WorldXML.Serialize(fs, bloksConvertData);
        }
    }

    //add new chunk to save
    public static void AddNewChunk(Vector2 position){
        bloksData.Add(position, new ChunkData());
    }
}
CunkData:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using System.IO;
using System.Runtime.Serialization;
using System.Xml.Serialization;


[Serializable]
public class ChunkData
{
    public ChunkData(){}
    public BlockType [] bloks = new BlockType [165888];
    public bool isGenetated;
    
    public float posX;
    public float posZ;
    
    public void SetValues(BlockType [ , , ] bloks3d, Vector2 position){
        bloks = ToSimpleArray(bloks3d);
        posX = position.x;
        posZ = position.y;
    }

    public BlockType [ , , ] GetArray(){
        return To3dArray(bloks);
    }
    //to three dimensional array
    private static BlockType [ , , ] To3dArray(BlockType [] Array){
        BlockType [ , , ] resultArray = new BlockType [18, 512, 18];
        for(int i = 0; i < 18; i++){
            for(int j = 0; j < 512; j++){
                for(int k = 0; k < 18; k++){
                    resultArray[i, j, k] = Array[i*512*18 + j*18 + k];
                }
            }
        }
        return resultArray;
    }
    //to one dimensional array
    private static BlockType [] ToSimpleArray(BlockType [ , , ] Array)
    {
        BlockType [] resultArray = new BlockType [18 * 512 * 18];
        int index = 0;
        BlockType [] SimpleArray = new BlockType [27];
        for(int i = 0; i < 18; i++){
            for(int j = 0; j < 512; j++){
                for(int k = 0; k < 18; k++){
                    SimpleArray[index] = Array[i, j, k];
                    index++;
                }
            }
        }
        return resultArray;
    }
}
World:
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using UnityEngine;

public class World : MonoBehaviour
{
    public static Dictionary<Vector2, Chunck> chunks = new Dictionary<Vector2, Chunck>();

    public List <Vector2> toGenerate;
    public List <Vector2> toDelete;
    public static XmlSerializableDictionary<Vector2, Chunck> loadedChuncks = new XmlSerializableDictionary<Vector2, Chunck>();
    //<...>

    void Start() //called when the game starts
    {
        LoadWorld();
        //<...>
    }

    public void LoadWorld(){
        Dictionary<Vector2, ChunkData> world = WorldData.Deserialization();
        foreach (var item in world)
        {
            loadedChuncks.Add(item.Key, new Chunck());
            loadedChuncks[item.Key].Data = item.Value;
            loadedChuncks[item.Key].SetValues();
        }
    }
    //<...>
}
Chunck:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Chunck : MonoBehaviour
{
    public ChunkData Data;
    public BlockType [ , , ] bloks  = new BlockType [18, 512, 18]; //data of the blocks in the chunk
    
    public Vector2 Position = new Vector2();
    //<...>

    //generation
    public void ChunckGeneration(){
        //<...>
    }

    public void Optimize() //build voxels
    {   
        //<...>
    }

    //set data to serialisation
    void ChunckToSave(){
        Data.SetValues(this.bloks, Position);
    }

    //get data
    public void SetValues(){
        this.bloks = Data.GetArray();
        this.isGenetated = false;
    }
}
 
If the data is repetitive, it will compress well.
i replaced xml with json serialization. I addn't compression yet, but size of the file is already smal. But the serialisation takes so long, like in xml. 6 minuts. I need to reduce time to 5 seconds. How to do it?
Now my serialization class looks so:
WorldData:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.IO;
using System.Xml.Serialization;

public class WorldData{
    public static Dictionary<Vector2, ChunkData> bloksData = new Dictionary<Vector2, ChunkData>();//deserialized dictionary
    public static Dictionary<VectorConventer, ChunkData> bloksConvertData = new Dictionary<VectorConventer, ChunkData>();//serializible dictionary


    //get deserialized world <Vector2, ChunkData>
    public static Dictionary<Vector2, ChunkData> Deserialization(){
        DataContractJsonSerializer serializer= new DataContractJsonSerializer(typeof(Dictionary<VectorConventer, ChunkData>));
        using (FileStream fs = new FileStream("New_World.json", FileMode.Create)){
            bloksConvertData = (Dictionary<VectorConventer, ChunkData>)serializer.ReadObject(fs); //Error. XmlException: Root element is missing.
        }
        foreach(var item in bloksData){
            bloksConvertData[new VectorConventer(item.Key)] = item.Value;
        }
        return bloksData;
    }

    //serialisation
    public static void SaveWorld(){
        //преобразование векторов
        foreach(var item in bloksData){
            VectorConventer keyVec = new VectorConventer(item.Key);
            bloksConvertData[keyVec] = item.Value;
        }
        //сама сериализация
        DataContractJsonSerializer serializer= new DataContractJsonSerializer(typeof(Dictionary<VectorConventer, ChunkData>));
        using (FileStream fs = new FileStream("New_World.json", FileMode.Create)){
            serializer.WriteObject(fs, bloksConvertData);
        }
    }

    //add new chunk to save
    public static void AddNewChunk(Vector2 position){
        bloksData.Add(position, new ChunkData());
    }
}
 
Does your ChunkData still look like your original post where it contains an array of 165,888 BlockType? Is that array a sparse array? If so, have you consisdered changing the implementation of the class so that it exposes a something like a linked list or dictionary that only contains elements that are not empty?
 
Does your ChunkData still look like your original post where it contains an array of 165,888 BlockType?
Yes. I just added [DataContract] and [DataMember]
Is that array a sparse array?
As I understand it, a sparse array is array that doesn't contain empty elements. In my array there aren't empty elements, each cell of the array contain a type of block. But array has elements of the same type. Is it possibly to sava only different elements and their numbers? Is the conversion algorithm to and from a compressed array fast?
 
From the same you gave earlier, it looked like most of your BlockType values were Air. If you treated Air as the default (e.g. empty) value and only stored the non-Air values you could potentially save a lot of time and space. So when you are serializing you would only save the non-Air indexes, and on deserialization, you would only need to get these values put back into the array. (Recall that C# will initialize value types arrays with the value.)
 
From the same you gave earlier, it looked like most of your BlockType values were Air. If you treated Air as the default (e.g. empty) value and only stored the non-Air values you could potentially save a lot of time and space. So when you are serializing you would only save the non-Air indexes, and on deserialization, you would only need to get these values put back into the array. (Recall that C# will initialize value types arrays with the value.)
I tried to do so, but not succenssful. Now it works like this:
ChunkData:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;


[DataContract]
public class ChunkData
{
    public ChunkData(){}
    [DataMember]
    public BlockType [] bloks = new BlockType [165888];
    public bool isGenetated;
    
    public float posX;
    public float posZ;
    
    public void SetValues(BlockType [ , , ] bloks3d, Vector2 position){
        bloks = ToSimpleArray(bloks3d);
        posX = position.x;
        posZ = position.y;
    }

    public BlockType [ , , ] GetArray(){
        return To3dArray(bloks);
    }
    //to three dimensional array
    private static BlockType [ , , ] To3dArray(BlockType [] Array){
        BlockType [ , , ] resultArray = new BlockType [18, 512, 18];
        for(int i = 0; i < 18; i++){
            for(int j = 0; j < 512; j++){
                for(int k = 0; k < 18; k++){
                    resultArray[i, j, k] = Array[i*512*18 + j*18 + k];
                }
            }
        }
        return resultArray;
    }
    //to one dimensional array
    private static BlockType [] ToSimpleArray(BlockType [ , , ] Array)
    {
        Debug.Log("It's here");
        BlockType [] resultArray = new BlockType [18 * 512 * 18];
        int index = 0;
        for(int i = 0; i < 18; i++){
            for(int j = 0; j < 512; j++){
                for(int k = 0; k < 18; k++){
                    if(Array[i, j, k] != BlockType.Air){
                        resultArray[index] = Array[i, j, k];
                    }
                    index++;
                }
            }
        }
        return resultArray;
    }
}
WorldData:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.IO;
using System.Xml.Serialization;

public class WorldData{
    public static Dictionary<Vector2, ChunkData> bloksData = new Dictionary<Vector2, ChunkData>();//deserialized dictionary
    public static Dictionary<VectorConventer, ChunkData> bloksConvertData = new Dictionary<VectorConventer, ChunkData>();//serializible dictionary


    //get deserialized world <Vector2, ChunkData>
    public static Dictionary<Vector2, ChunkData> Deserialization(){
        DataContractJsonSerializer serializer= new DataContractJsonSerializer(typeof(Dictionary<VectorConventer, ChunkData>));
        using (FileStream fs = new FileStream("New_World.json", FileMode.Create)){
            bloksConvertData = (Dictionary<VectorConventer, ChunkData>)serializer.ReadObject(fs); //Error. XmlException: Root element is missing.
        }
        foreach(var item in bloksData){
            bloksConvertData[new VectorConventer(item.Key)] = item.Value;
        }
        return bloksData;
    }

    //serialisation
    public static void SaveWorld(){
        //преобразование векторов
        foreach(var item in bloksData){
            VectorConventer keyVec = new VectorConventer(item.Key);
            bloksConvertData[keyVec] = item.Value;
        }
        
        DataContractJsonSerializer serializer= new DataContractJsonSerializer(typeof(Dictionary<VectorConventer, ChunkData>));
        using (FileStream fs = new FileStream("New_World.json", FileMode.Create)){
            serializer.WriteObject(fs, bloksConvertData);
        }
    }

    //add new chunk to save
    public static void AddNewChunk(Vector2 position){
        if(!bloksData.ContainsKey(position)){
            bloksData.Add(position, new ChunkData());
        }
        if(World.chunks.ContainsKey(position)){
            World.chunks[position].Data = bloksData[position];
        }
    }
}
But I didn't think, that it helps to reduce serialisation time.
 
You are still still serializing the same amount of data, not just the data that matters.
 
Back
Top Bottom