请求以块的形式加载文件的指示(仅需要建议)

时间:2011-06-15 03:51:12

标签: c# .net memory-management memory-mapped-files chunks

过去几天我一直在研究一种方法,在我的xna游戏中压缩1.44亿个瓷砖表示,保存时的尺寸非常小。设法解决这个问题之后,我现在发现自己难以理解如何将它们从文件块中取回来。

在我的文件中。

  1. 一个整数(使用7BitEncodedInt方法将其压缩为字节)
  2. 一个字节
  3. 压缩整数表示切片的数量,后面的字节决定切片的类型。这一切都很好,而且效果很好。最重要的是,它平均缩小文件大小到50mb。

    问题是我目前正在读回整个文件。 从文件中我得到了这个。

    1. 每个图块的索引值(只是我抓取图块时的基本迭代)
    2. 每个磁贴的类型为字节值
    3. 表示该图块纹理的字节值(这很难解释,但每个图块必须这样做)
    4. 所有这一切的最终结果是我设法保存文件并且只使用大约50mb。但是通过将整个东西重新装入其中,它扩展到了公羊上近1.5公斤。我真的不能再牺牲瓷砖信息了。所以我需要一种方法只根据玩家位置加载部分地图。目标是在100-200mb范围内

      我一直在研究使用四叉树映射文件的内存,几乎可以找到任何用于以块的形式加载文件的内容。虽然这些选项看起来都很好,但我不确定哪种选择最好,或者如果考虑到这种情况,可能会有另一个更好的选择。所有这一切的另一个问题是这些解决方案似乎都非常复杂(特别是因为这是我第一次使用它们)而且我并不反对将自己投入到一些冗长的编码中,我想知道它会做我做的事情。需要它在手。

      我的问题是,考虑到我在处理文件时必须处理文件以及需要根据玩家位置完成这一事实,最好的方法是什么?我只想在这里寻找方向。代码随时欢迎,但不是必需的。

2 个答案:

答案 0 :(得分:0)

你想在你的Tile类中有固定长度的变量,并实现类似的东西:

这是一个集合类(People)的示例,它可以根据序列化为文件的集合中的索引获取值。

Person是People集合的基础类。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FileStreamDatabaseTest
{
    class Program
    {
        static void Main(string[] args)
        {
            People.OpenCollection();
            People.Test_WillOverwriteData();
            People.CloseCollection();
            Console.ReadLine();
        }
    }

    public class Person
    {
        // define maxium variable sizes for serialisation
        protected static int pMaxLength_FirstName = 64;
        protected static int pMaxLength_Age = 10;
        public static int MaxObjectSize
        {
            get
            {
                // return the sum of all the maxlegnth variables to define the entire object size for serialisation
                return pMaxLength_FirstName + pMaxLength_Age;
            }
        }

        // define each object that will be serialised as follows 
        protected string pFirstName;
        public string Firstname
        {
            set
            {
                // ensure the new value is not over max variable size
                if (value.Length > pMaxLength_FirstName)
                    throw new Exception("the length of the value is to long.");

                pFirstName = value;
            }
            get
            {
                return pFirstName;
            }
        }
        protected int pAge;
        public int Age
        {
            get
            {
                return pAge;
            }
            set
            {
                pAge = value;
            }
        }

        public byte[] Serialise()
        {
            // Output string builder
            StringBuilder Output = new StringBuilder();

            // Append firstname value
            Output.Append(Firstname);

            // Add extra spaces to end of string until max length is reached
            if (Firstname.Length < pMaxLength_FirstName)
                for (int i = Firstname.Length; i < pMaxLength_FirstName; i++)
                    Output.Append(" ");

            // Append age value as string
            Output.Append(Age.ToString());

            // Add extra spaces to end of string until max length is reached
            int AgeLength = Age.ToString().Length;
            if (AgeLength < pMaxLength_Age)
                for (int i = AgeLength; i < pMaxLength_Age; i++)
                    Output.Append(" ");

            // Return the output string as bytes using ascii encoding
            return System.Text.Encoding.ASCII.GetBytes(Output.ToString());
        }

        public void Deserialise(byte[] SerialisedData)
        {
            string Values = System.Text.Encoding.ASCII.GetString(SerialisedData);

            pFirstName = Values.Substring(0, pMaxLength_FirstName).Trim();
            pAge = int.Parse(Values.Substring(pMaxLength_FirstName, pMaxLength_Age).Trim());
        }
    }

    public static class People
    {
        private static string tileDatasource = @"c:\test.dat";
        private static System.IO.FileStream FileStream;

        public static void OpenCollection()
        {
            FileStream = new System.IO.FileStream(tileDatasource, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite, System.IO.FileShare.None);
        }

        public static void CloseCollection()
        {
            FileStream.Close();
            FileStream.Dispose();
            FileStream = null;
        }

        public static void SaveCollection(Person[] People)
        {
            FileStream.SetLength(People.Length * Person.MaxObjectSize);
            FileStream.Position = 0;

            foreach (Person PersonToWrite in People)
            {
                // call serialise to get bytes
                byte[] OutputBytes = PersonToWrite.Serialise();

                // write the output buffer
                // note: this will always be the same size as each variable should 
                //       append spaces until its max size is reached
                FileStream.Write(OutputBytes, 0, OutputBytes.Length);
            }
        }

        public static Person GetValue(int Index)
        {
            // set the stream position to read the object by multiplying the requested index with the max object size
            FileStream.Position = Index * Person.MaxObjectSize;

            // read the data
            byte[] InputBytes = new byte[Person.MaxObjectSize];
            FileStream.Read(InputBytes, 0, Person.MaxObjectSize);

            // deserialise
            Person PersonToReturn = new Person();
            PersonToReturn.Deserialise(InputBytes);

            // retun the person
            return PersonToReturn;
        }

        public static void Test_WillOverwriteData()
        {
            long StartTime;
            long EndTime;
            TimeSpan TimeTaken;

            Console.WriteLine("-------------------------------------------------------------------");
            Console.WriteLine("*** Creating 2,000,000 test people... ");
            StartTime = DateTime.Now.Ticks;
            Person[] People = new Person[2000000];
            for (int i = 0; i < 2000000; i++)
            {
                People[i] = new Person();
                People[i].Firstname = "TestName." + i;
                People[i].Age = i;
            }
            EndTime = DateTime.Now.Ticks;
            TimeTaken = new TimeSpan(EndTime - StartTime);
            Console.WriteLine("-> Completed in " + TimeTaken.TotalSeconds + " seconds");

            Console.WriteLine("-------------------------------------------------------------------");
            Console.WriteLine("*** Serialising Collection to disk... ");
            StartTime = DateTime.Now.Ticks;
            SaveCollection(People);
            EndTime = DateTime.Now.Ticks;
            TimeTaken = new TimeSpan(EndTime - StartTime);
            Console.WriteLine("-> Completed in " + TimeTaken.TotalSeconds + " seconds");

            Console.WriteLine("-------------------------------------------------------------------");
            Console.WriteLine("*** Redundancy Test... ");
            StartTime = DateTime.Now.Ticks;
            bool Parsed = true;
            int FailedCount = 0;
            for (int i = 0; i < 2000000; i++)
            {
                if (GetValue(i).Age != i)
                {
                    Parsed = false;
                    FailedCount++;
                }
            }
            EndTime = DateTime.Now.Ticks;
            TimeTaken = new TimeSpan(EndTime - StartTime);
            Console.WriteLine("-> " + (Parsed ? "PARSED" : "FAILED (" + FailedCount + " failed index's"));
            Console.WriteLine("-> Completed in " + TimeTaken.TotalSeconds + " seconds");

            Console.WriteLine("-------------------------------------------------------------------");
            Console.WriteLine("*** Reading 10,000 index's at once... ");
            StartTime = DateTime.Now.Ticks;
            Person[] ChunkOfPeople = new Person[10000];
            for (int i = 0; i < 10000; i++)
                ChunkOfPeople[i] = GetValue(i);
            EndTime = DateTime.Now.Ticks;
            TimeTaken = new TimeSpan(EndTime - StartTime);
            Console.WriteLine("-> Completed in " + TimeTaken.TotalSeconds + " seconds");


            Console.WriteLine("-------------------------------------------------------------------");
            Console.WriteLine("*** Reading 100,000 index's at once... ");
            StartTime = DateTime.Now.Ticks;
            ChunkOfPeople = new Person[100000];
            for (int i = 0; i < 100000; i++)
                ChunkOfPeople[i] = GetValue(i);
            EndTime = DateTime.Now.Ticks;
            TimeTaken = new TimeSpan(EndTime - StartTime);
            Console.WriteLine("-> Completed in " + TimeTaken.TotalSeconds + " seconds");

            Console.WriteLine("-------------------------------------------------------------------");
            Console.WriteLine("*** Reading 1,000,000 index's at once... ");
            StartTime = DateTime.Now.Ticks;
            ChunkOfPeople = new Person[1000000];
            for (int i = 0; i < 1000000; i++)
                ChunkOfPeople[i] = GetValue(i);
            EndTime = DateTime.Now.Ticks;
            TimeTaken = new TimeSpan(EndTime - StartTime);
            Console.WriteLine("-> Completed in " + TimeTaken.TotalSeconds + " seconds");
        }
    }     
}

答案 1 :(得分:0)

有许多选项,并非所有选项都适合您的特定项目:

  • 不要将单个文件用于所有数据。将地图划分为较小的“房间”,并将每个地图存储在自己的文件中。仅加载播放器启动的“房间”并抢先加载相邻的“房间”并卸载旧房间。
  • 减少需要存储的图块数量。使用程序生成来创建区域的布局。 如果您有一个10x10的房间,地板由单个瓷砖类型制成,那么不要存储100个单独的瓷砖,而是使用一个特定的标记,上面写着“这个区域有一个10x10的地板和这个瓷砖”。如果它是墙,则保存开始和结束位置以及纹理类型。如果你在一个空旷的场地中间有一个多瓦片的doodad并且它的位置与故事无关,那么将它随机地放置在该字段中(并将随机数生成器的种子保存在地图文件中,以便下次它将出现在同一个地方)。