Сохранить 16 000 000 объектов в файл сохранения?

Программирование на Юнити.

Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Инженер 17 апр 2020, 00:09

Я хочу дать игроку возможность изменять игровой мир, все изменения должны сохраняться даже после перезапуска игры. Поэтому я пришел к выводу, что все объекты мира должны быть сериализованы в файл, начиная от травы, заканчивая домами. Но проведя тесты, я обнаружил, что размер сериализованных данных на много больше, чем я ожидал.

- Игровой мир площадью 16 км2 (4х4 км).
- Всего в игровом мире будет примерно 16 000 000 объектов. Усредненно 1 объект на 1 м2.
- Каждый объект будет иметь 11 значений, которые надо сохранить: string с адресом префаба в папке Resources, 6 float'ов позиции и вращения и 4 дополнительных byte-переменных для сохранения важных скриптовых данных, типа здоровья, уровня топлива в транспорте и т.д.

Синтаксис:
Используется csharp
    class Entity
    {
        public string prefabAddress;
       
        public float positionX;
        public float pozitionY;
        public float positionZ;

        public float rotationX;
        public float rotationY;
        public float rotationZ;

        public byte extra1;
        public byte extra2;
        public byte extra3;
        public byte extra4;
    }



Так вот проблема в том, что 1 млн. объектов (это 1 км2 площади мира) занимает 46 мб. При попытке сохранить 16 млн. объектов, редактор зависает. Нехитрым умножением получаем 736 мб. Это слишком много.

Как мне сохранить нужные данные для столь большого числа объектов, чтобы размер данных, как на жестком диске, так и в оперативной памяти, не превышал... ну предположим, 100 мб?

Может это звучит как требование чуда, но экспериментируя, я узнал, что если бы можно было все 11 переменных записать в одну строку string через пробел, то размер файла с 1 млн. объектов составил бы 19 мб (против 46 мб, если все сохранять отдельно). Выигрыш почти в 2.5 раза. А можно придумать еще эффективнее? Например, шифровать несколько переменных в одну переменную, а при необходимости расшифровывать обратно.

Вот и спрашиваю у вас, может подкинете идейку.





P.S.
Вот тестовый образец, в котором я экспериментирую. Ничего особенного, просто экспериментальная модель сохранения всего мира игры. Поэтому там что-то про сектора, на которые поделен мир (размер сектора 10х10 м), каждый сектор как бы хранит список объектов, которых на самом деле нет (сериализуются случайные данные). Главное- sectorsCount (количество секторов размером 10х10 м) определяют размер мира, entitiesCountInSector- сколько объектов внутри каждого сектора. Все это в данный момент просто случайные данные.

Синтаксис:
Используется csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Threading;

namespace Test
{
    [System.Serializable]
    class Container
    {
        public Sector[] sectors;
    }

    [System.Serializable]
    class Sector
    {
        public List<Entity> entities;
    }

    [System.Serializable]
    class Entity
    {
        public string prefabAddress;
       
        public float positionX;
        public float pozitionY;
        public float positionZ;

        public float rotationX;
        public float rotationY;
        public float rotationZ;

        public byte extra1;
        public byte extra2;
        public byte extra3;
        public byte extra4;

        public void SetValue(int f)
        {
            //Тупо напихиваем в класс случайные данные, чтобы симулировать,
            //будто мы сериализовали настоящий объект в Entity.

            this.positionX = f;
            this.pozitionY = f;
            this.positionZ = f;

            this.rotationX = f;
            this.rotationY = f;
            this.rotationZ = f;

            this.extra1 = 111;
            this.extra2 = 12;
            this.extra3 = 23;
            this.extra4 = 64;
        }
    }
    public class SaveLoadAsync : MonoBehaviour
    {

        int sectorsCount = 10000; //количество секторов 10х10 м
        int entitiesCountInSector = 100; //среднее количество объектов в 1 секторе
        Sector[] sectorsArray; //все сектора мира
        string path; //путь сохранения

        private void Start()
        {
            sectorsArray = new Sector[sectorsCount];
            path = Application.persistentDataPath + "/AsyncSave.dat";

            CreateSectors();
        }

         //создаем сектора и заполняем их случайными данными, т.к. нам важен лишь размер
        void CreateSectors()
        {
            for (int i = 0; i < sectorsCount; i++)
            {
                List<Entity> list = new List<Entity>();
                for (int j = 0; j < entitiesCountInSector; j++)
                {
                    Entity e = new Entity();
                    e.SetValue(i);
                    list.Add(e);
                }
                sectorsArray[i] = new Sector();
                sectorsArray[i].entities = list;
            }
        }

        //асинхронная загрузка и сохранение секторов
        private void Update()
        {
            if (Input.GetKeyDown(KeyCode.R))
            {
                var thread = new Thread(Save);
                thread.Start();
            }
            if (Input.GetKeyDown(KeyCode.F))
            {
                var thread = new Thread(Load);
                thread.Start();
            }
        }

        void Save()
        {
            BinaryFormatter bf = new BinaryFormatter();
            FileStream fs = File.Create(path);

            Container container = new Container();
            container.sectors = sectorsArray;

            bf.Serialize(fs, container);
            fs.Close();
            Debug.Log("Save! " + sectorsArray.Length);
        }

        void Load()
        {
            if (File.Exists(path))
            {
                BinaryFormatter bf = new BinaryFormatter();
                FileStream fs = File.Open(path, FileMode.Open);

                Container container = (Container)bf.Deserialize(fs);
                sectorsArray = container.sectors;
                fs.Close();
                Debug.Log("Load! " + sectorsArray.Length);
            }
        }
    }
}


 
Инженер
UNIт
 
Сообщения: 88
Зарегистрирован: 22 май 2016, 11:13

Re: Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Tolking 17 апр 2020, 01:26

e.SetValue(i); - это не случайный результат это минимум 1 байт (0-9) и максимум 5 байт (10000) в строке, а должно быть минимум 7 байт (0.00000), а максимум 11 (10000.00000) Ты не будешь ведь только в целые координаты предметы ставить?
Ковчег построил любитель, профессионалы построили Титаник.
Аватара пользователя
Tolking
Адепт
 
Сообщения: 2715
Зарегистрирован: 08 июн 2009, 18:22
Откуда: Тула

Re: Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Cr0c 17 апр 2020, 03:19

float - 4 байта, итого 6 float + 4 byte = 28 байт. Если префабы закодировать 1 байтом, то можно в 29 байт умечтить одну запись. Это 29 Мб на 1 миллион записей. Если поворот ограничить не любым, а только фиксированным углом (например в 255 углов по каждой оси), то можно уменьшить до 3 float + 3 byte + 4 byte + 1 byte = 20 байт.
С позицией сложнее, но если диапазон фиксирован (4000х4000х1000), то можно сделать в 3*3 байта, вместо 4 байта флоата, это ещё 3 байта экономия (17 байт на запись).
1 - id префаба
9 - позиция
3 - поворот
4 - доп данные
Аватара пользователя
Cr0c
Адепт
 
Сообщения: 3035
Зарегистрирован: 19 июн 2015, 13:50
Skype: cr0c81

Re: Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Cr0c 17 апр 2020, 03:29

С учётом, что объект внутри чанка 10х10, то можно позицию до 2 байт ужать , это даст 6 байт
Учти, что 3 байта строки - это 1/1000 точность, а 2 байта бинарные - это 1/65536 точность. Для чанка 10х10 метров это даст точность 1/6553 метра погрешность (меньше миллиметра). Тогда можно и поворот уточнить до 2 байт (погрешность в 360/65535 градусов).
1 byte - id префаба
3 dword - позиция
3 dword - поворот
4 byte - доп данные
Итого: 17 байт на запись
Если поворот задать как угол по XZ и угол Y, то можно поворот задать 2х2 байта, что уменьшить запись до 15 байт (тогда можно при необходимости id префаба увеличить до двух байт) и получить 16 байт на запись
Последний раз редактировалось Cr0c 17 апр 2020, 03:49, всего редактировалось 1 раз.
Аватара пользователя
Cr0c
Адепт
 
Сообщения: 3035
Зарегистрирован: 19 июн 2015, 13:50
Skype: cr0c81

Re: Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Cr0c 17 апр 2020, 03:41

И у тебя в тесте не заполняется поле стринга, что может увеличить текущий сейв раза в два-три-четыре )))
Плюс на чанк надо 1 байт для счётчика объектов внутри чанка и один общий счётчик для количества чанков в файле. Возможно ещё 4 байта для координаты чанка в мире. Это не считая данных самого чанка (не объектов внутри чанка).
Аватара пользователя
Cr0c
Адепт
 
Сообщения: 3035
Зарегистрирован: 19 июн 2015, 13:50
Skype: cr0c81

Re: Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Инженер 17 апр 2020, 21:55

Cr0c, жму руку! Есть над чем подумать.
Под id префабов придется отвести 2 байта, т.к. их будет точно больше 255. Вращения впихиваем в 255 углов. Позиции- 3 ushort'а. Плюс 4 доп переменные. Итого 15 байт.
Позицию объекта определяем относительно позиции чанка, а не мира.
Позицию чанка не хранить в файле. Т.к. позиция чанка в мировых координатах определяется по номеру строки и столбца массива всех чанков.

Cr0c писал(а):Если поворот задать как угол по XZ и угол Y, то можно поворот задать 2х2 байта


Что это значит? Как уместить XZ в одном числе?
Инженер
UNIт
 
Сообщения: 88
Зарегистрирован: 22 май 2016, 11:13

Re: Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Jarico 17 апр 2020, 22:23

Инженер писал(а):Что это значит? Как уместить XZ в одном числе?


Можешь структурой
Синтаксис:
Используется csharp
[StructLayout(LayoutKind.Explicit)]
public struct DoubleVar
{
    [FieldOffset(0)]
    public byte a1;
    [FieldOffset(1)]
    public byte a2;
    [FieldOffset(2)]
    public byte a3;
    [FieldOffset(3)]
    public byte a4;
}
 
Github: _https://github.com/redheadgektor
Discord: Конь! Чаю!#9382 (сижу редко)
YouTube: _https://www.youtube.com/channel/UCPQ04Xpbbw2uGc1gsZtO3HQ
Telegram: _https://t.me/redheadgektor
Аватара пользователя
Jarico
Адепт
 
Сообщения: 1084
Зарегистрирован: 06 янв 2019, 17:37
Откуда: 0xDEAD
Skype: none
  • Сайт

Re: Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Инженер 17 апр 2020, 23:50

И все таки мне не дает покоя одна вещь. Почему если я сохраняю 11 чисел в одну переменную string через пробел, миллион таких объектов в файле занимает 19 мб, а если сохраняю числа по отдельности (в тех же string или числовых типах), то 60 мб?

Синтаксис:
Используется csharp
   [System.Serializable]
    class EntityAsync
    {
        public string allData;

        public void SetValue()
        {
                this.allData= "12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901";
        }
    }

   
 


Какое-то волшебство. Может сохранять все данные в один string, а при десериализации извлекать оттуда и при этом я буду иметь очень сжатые данные?

Полный текст:

Синтаксис:
Используется csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Threading;

namespace Test
{
    [System.Serializable]
    class ContainerAsync
    {
        public SectorAsync[] sectors;
    }

    [System.Serializable]
    class SectorAsync
    {
        public List<EntityAsync> entities;
    }

    [System.Serializable]
    class EntityAsync
    {
        public string allData;

        public void SetValue()
        {

            this.allData= "12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901";

        }
    }
    public class SaveLoadAsync : MonoBehaviour
    {

        int sectorsCount = 10000;
        int entitiesCountInSector = 100;
        SectorAsync[] sectorsArray;
        string path;

        private void Start()
        {
            sectorsArray = new SectorAsync[sectorsCount];
            path = Application.persistentDataPath + "/AsyncSave.dat";

            CreateSectors();
        }

        void CreateSectors()
        {
            for (int i = 0; i < sectorsCount; i++)
            {
                List<EntityAsync> list = new List<EntityAsync>();
                for (int j = 0; j < entitiesCountInSector; j++)
                {
                    EntityAsync e = new EntityAsync();
                    e.SetValue();
                    list.Add(e);
                }
                sectorsArray[i] = new SectorAsync();
                sectorsArray[i].entities = list;
            }
        }
        private void Update()
        {
            if (Input.GetKeyDown(KeyCode.R))
            {
                var thread = new Thread(Save);
                thread.Start();
            }
            if (Input.GetKeyDown(KeyCode.F))
            {
                var thread = new Thread(Load);
                thread.Start();
            }
        }

        void Save()
        {
            BinaryFormatter bf = new BinaryFormatter();
            FileStream fs = File.Create(path);

            ContainerAsync container = new ContainerAsync();
            container.sectors = sectorsArray;

            bf.Serialize(fs, container);
            fs.Close();
            Debug.Log("Save! " + sectorsArray.Length);
        }

        void Load()
        {
            if (File.Exists(path))
            {
                BinaryFormatter bf = new BinaryFormatter();
                FileStream fs = File.Open(path, FileMode.Open);

                ContainerAsync container = (ContainerAsync)bf.Deserialize(fs);
                sectorsArray = container.sectors;
                fs.Close();
                Debug.Log("Load! " + sectorsArray.Length);
            }
        }
    }
}


 
Инженер
UNIт
 
Сообщения: 88
Зарегистрирован: 22 май 2016, 11:13

Re: Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Cr0c 18 апр 2020, 01:10

Инженер писал(а):
Cr0c писал(а):Если поворот задать как угол по XZ и угол Y, то можно поворот задать 2х2 байта

Что это значит? Как уместить XZ в одном числе?

Тут я слегка маху дал. Надо сжимать transform.rotation.eulerAngles до 3х2 байта, как и позицию.
Аватара пользователя
Cr0c
Адепт
 
Сообщения: 3035
Зарегистрирован: 19 июн 2015, 13:50
Skype: cr0c81

Re: Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Cr0c 18 апр 2020, 01:14

Инженер писал(а):И все таки мне не дает покоя одна вещь. Почему если я сохраняю 11 чисел в одну переменную string через пробел, миллион таких объектов в файле занимает 19 мб, а если сохраняю числа по отдельности (в тех же string или числовых типах), то 60 мб?

Это из-за неправильного теста. 6 байт флоата могут в 1 символ влезть, а могут "всего лишь" в 10-12 )) так же и 4 байта инта могут 1 символ, а могут в 10
Аватара пользователя
Cr0c
Адепт
 
Сообщения: 3035
Зарегистрирован: 19 июн 2015, 13:50
Skype: cr0c81

Re: Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Cr0c 18 апр 2020, 01:52

Ещё и бинари форматтер сохраняет не "чистые" данные, а структуру. Можно же вручную брать из структуры массив байт ((де)сериализатор написать).
Я, когда-то давно, писал такой. У меня первая версия была по 20 кб на запись, а шестая 50+ кб на 100 записей. Но там всё было сложнее, конечно, я хранил набор записей за каждый день с ссылками при неизменной записи на предыдущую... и стринговые поля (их было всего-то 30+ видов) в отдельной таблицей держал с ссылками в записи на нужную.
Аватара пользователя
Cr0c
Адепт
 
Сообщения: 3035
Зарегистрирован: 19 июн 2015, 13:50
Skype: cr0c81

Re: Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Cr0c 18 апр 2020, 02:05

И советую стейт объекта держать отдельной структурой, чтобы унифицировать гетерогенные данные. Так можно байт-массив под трансформ и байт-массив под стейт совместить с очень уж разными объектами. Всего-то стейт из байта длины + данные, склеить два массива легко, прочесть/записать тоже. А (де)сериализатор внутри самой структуры реализовать, чтобы упростить методы сохранения/загрузки.
Аватара пользователя
Cr0c
Адепт
 
Сообщения: 3035
Зарегистрирован: 19 июн 2015, 13:50
Skype: cr0c81

Re: Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Tolking 18 апр 2020, 02:13

:) Почти, Крок, форматер жмет данные... 120 байт строка * 1 000 000 в чистом виде не может весить 19 мб...

Инженер, Используй эту процедуру для заполнения данных и расскажи нам про объём...
Синтаксис:
Используется csharp
 
   public void SetValue()
    {
        this.allData = "";
        for (int i = 0; i < 11; i++)
        {
            if (this.allData != "") this.allData += " ";
            this.allData += Random.Range(1000000000, 2000000000).ToString();
        }
    }
 


P.S. Странный ты... Используешь потоки, пространство имен, знаешь как обозначается время поиска элемента в массиве, опять же сериализация вместо сохранения и при этом задаешь наивные вопросы... Уж не троль ли ты жЫрный?
Ковчег построил любитель, профессионалы построили Титаник.
Аватара пользователя
Tolking
Адепт
 
Сообщения: 2715
Зарегистрирован: 08 июн 2009, 18:22
Откуда: Тула

Re: Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Cr0c 18 апр 2020, 02:27

Ещё бы найти где описано как он жмёт данные...
Аватара пользователя
Cr0c
Адепт
 
Сообщения: 3035
Зарегистрирован: 19 июн 2015, 13:50
Skype: cr0c81

Re: Сохранить 16 000 000 объектов в файл сохранения?

Сообщение Cr0c 18 апр 2020, 02:34

Помимо сохранения данных полей, BinaryFormatter также сохраняет полное квалифицированное имя каждого типа, полное имя сборки, где он определен, сюда входит информация об имени, версии, маркере общедоступного ключа (public key) и культуре.

Как-то не очень он сжимает
Похоже, что в тесте те 19 Мб - это данные инфраструктуры занимают, потому что строка везде одна (ссылочный тип же)
Аватара пользователя
Cr0c
Адепт
 
Сообщения: 3035
Зарегистрирован: 19 июн 2015, 13:50
Skype: cr0c81

След.

Вернуться в Скрипты

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 12