I'm creating a class library to read/write .3di files which are binary files describing 3D models for a video game. This review is for the write/serialization process.
I've mapped the different structures in the .3di file (header, textures, vertices, etc.) to the classes shown below. Each class implements the IBinarySerializable
interface and is thus responsible for its own serialization logic. I've omitted many parts of the 3D model here for brevity, but they follow the same pattern.
Clients would use the library by building out a Model
object and then calling SaveToFile()
.
interface IBinarySerializable
{
byte[] Serialize();
}
public class Model
{
public ModelHeader Header { get; set; }
public List<Texture> Textures { get; set; }
public List<Vertex> Vertices { get; set; }
public void SaveToFile(string filePath)
{
using (FileStream fileStream = new FileStream(filePath, FileMode.Create))
using (BinaryWriter writer = new BinaryWriter(fileStream))
{
writer.Write(Header.Serialize());
writer.Write(Textures.SelectMany(texture => texture.Serialize()).ToArray());
writer.Write(Vertices.SelectMany(vertex => vertex.Serialize()).ToArray());
}
}
}
public class ModelHeader : IBinarySerializable
{
public string Name { get; set; }
public int TextureCount { get; set; }
public byte[] Serialize()
{
using (var buffer = new MemoryStream())
using (var writer = new BinaryWriter(buffer))
{
// Hardcoded values.
writer.Write(Encoding.ASCII.GetBytes("3DI"));
writer.Write((byte)8);
// Header data.
writer.Write(Encoding.ASCII.GetBytes(Name.PadRight(8, '\0')));
writer.Write(TextureCount);
return buffer.ToArray();
}
}
}
public class Texture : IBinarySerializable
{
public string Name { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public byte[] Serialize()
{
using (var buffer = new MemoryStream())
using (var writer = new BinaryWriter(buffer))
{
writer.Write(Encoding.ASCII.GetBytes(Name.PadRight(28, '\0')));
writer.Write(Width);
writer.Write(Height);
return buffer.ToArray();
}
}
}
public class Vertex : IBinarySerializable
{
public short X { get; set; }
public short Y { get; set; }
public short Z { get; set; }
public byte[] Serialize()
{
using (var buffer = new MemoryStream())
using (var writer = new BinaryWriter(buffer))
{
writer.Write(X);
writer.Write(Y);
writer.Write(Z);
return buffer.ToArray();
}
}
}
My Concerns
- Since the .3di file schema is proprietary, I don't believe I can use the binary serialization that comes with .NET (e.g.,
BinaryFormatter
) since it adds .NET-specific data to the file. Is that correct? - Should each class be responsible for its own serialization logic? I've considered centralizing all of that logic to a dedicated class, e.g.,
ModelSerializer
,ModelFileWriter
, etc. One benefit of that approach is that I wouldn't have to map the file structures to classes so literally. - I repeat this in each
Serialize()
method:
Should I extract those lines to a helper function and just pass in what I want to do with theusing (var buffer = new MemoryStream()) using (var writer = new BinaryWriter(buffer))
writer
, e.g.,BinaryUtils.Serialize(Action<BinaryWriter> writeAction)
?
Model
class. Hint:IBinarySerializable
\$\endgroup\$