上篇笔记看完已经能够在逻辑上让框架以最简单的方式跑了起来,接下来就要填充具体的功能模块了。正常游戏一开始运行就要去加载必要的资源,而且资源管理这块也还蛮重要的,所以就从资源这块开始啃吧。
GameFramework资源这块依赖大量的事件、回调以及序列化等辅助工具,还有用到的文件系统可以先了解一下(虽然也不大影响对资源模块的理解)。
一、文件系统
看了作者对于这个虚拟文件系统的说明以后,感觉要完全理解代码的话可能会有点难度,里面大量的使用了非托管代码与托管代码的交互。
/// <summary>
/// 文件信息。
/// </summary>
[StructLayout(LayoutKind.Auto)]
public struct FileInfo
{
private readonly string m_Name;
private readonly long m_Offset;
private readonly int m_Length;
/// <summary>
/// 初始化文件信息的新实例。
/// </summary>
/// <param name="name">文件名称。</param>
/// <param name="offset">文件偏移。</param>
/// <param name="length">文件长度。</param>
public FileInfo(string name, long offset, int length)
{
if (string.IsNullOrEmpty(name))
{
throw new GameFrameworkException("Name is invalid.");
}
if (offset < 0L)
{
throw new GameFrameworkException("Offset is invalid.");
}
if (length < 0)
{
throw new GameFrameworkException("Length is invalid.");
}
m_Name = name;
m_Offset = offset;
m_Length = length;
}
/// <summary>
/// 获取文件信息是否有效。
/// </summary>
public bool IsValid
{
get
{
return !string.IsNullOrEmpty(m_Name) && m_Offset >= 0L && m_Length >= 0;
}
}
/// <summary>
/// 获取文件名称。
/// </summary>
public string Name
{
get
{
return m_Name;
}
}
/// <summary>
/// 获取文件偏移。
/// </summary>
public long Offset
{
get
{
return m_Offset;
}
}
/// <summary>
/// 获取文件长度。
/// </summary>
public int Length
{
get
{
return m_Length;
}
}
}
先是关于文件信息的定义。一开头就又看到了StructLayout,为了和非托管数据交互而添加的特性。成员也很简单,包含文件的基本信息:名字、长度和偏移量以及判断文件信息是否有效的字段IsValid。
既然是文件系统,那肯定会牵扯到文件的读写。框架中读写文件是通过文件流实现的。
/// <summary>
/// 文件系统流。
/// </summary>
public abstract class FileSystemStream
{
/// <summary>
/// 缓存二进制流的长度。
/// </summary>
protected const int CachedBytesLength = 0x1000;
/// <summary>
/// 缓存二进制流。
/// </summary>
protected static readonly byte[] s_CachedBytes = new byte[CachedBytesLength];
/// <summary>
/// 获取或设置文件系统流位置。
/// </summary>
protected internal abstract long Position
{
get;
set;
}
/// <summary>
/// 获取文件系统流长度。
/// </summary>
protected internal abstract long Length
{
get;
}
/// <summary>
/// 设置文件系统流长度。
/// </summary>
/// <param name="length">要设置的文件系统流的长度。</param>
protected internal abstract void SetLength(long length);
/// <summary>
/// 定位文件系统流位置。
/// </summary>
/// <param name="offset">要定位的文件系统流位置的偏移。</param>
/// <param name="origin">要定位的文件系统流位置的方式。</param>
protected internal abstract void Seek(long offset, SeekOrigin origin);
/// <summary>
/// 从文件系统流中读取一个字节。
/// </summary>
/// <returns>读取的字节,若已经到达文件结尾,则返回 -1。</returns>
protected internal abstract int ReadByte();
/// <summary>
/// 从文件系统流中读取二进制流。
/// </summary>
/// <param name="buffer">存储读取文件内容的二进制流。</param>
/// <param name="startIndex">存储读取文件内容的二进制流的起始位置。</param>
/// <param name="length">存储读取文件内容的二进制流的长度。</param>
/// <returns>实际读取了多少字节。</returns>
protected internal abstract int Read(byte[] buffer, int startIndex, int length);
/// <summary>
/// 从文件系统流中读取二进制流。
/// </summary>
/// <param name="stream">存储读取文件内容的二进制流。</param>
/// <param name="length">存储读取文件内容的二进制流的长度。</param>
/// <returns>实际读取了多少字节。</returns>
protected internal int Read(Stream stream, int length)
{
int bytesRead = 0;
int bytesLeft = length;
while ((bytesRead = Read(s_CachedBytes, 0, bytesLeft < CachedBytesLength ? bytesLeft : CachedBytesLength)) > 0)
{
bytesLeft -= bytesRead;
stream.Write(s_CachedBytes, 0, bytesRead);
}
Array.Clear(s_CachedBytes, 0, CachedBytesLength);
return length - bytesLeft;
}
/// <summary>
/// 向文件系统流中写入一个字节。
/// </summary>
/// <param name="value">要写入的字节。</param>
protected internal abstract void WriteByte(byte value);
/// <summary>
/// 向文件系统流中写入二进制流。
/// </summary>
/// <param name="buffer">存储写入文件内容的二进制流。</param>
/// <param name="startIndex">存储写入文件内容的二进制流的起始位置。</param>
/// <param name="length">存储写入文件内容的二进制流的长度。</param>
protected internal abstract void Write(byte[] buffer, int startIndex, int length);
/// <summary>
/// 向文件系统流中写入二进制流。
/// </summary>
/// <param name="stream">存储写入文件内容的二进制流。</param>
/// <param name="length">存储写入文件内容的二进制流的长度。</param>
protected internal void Write(Stream stream, int length)
{
int bytesRead = 0;
int bytesLeft = length;
while ((bytesRead = stream.Read(s_CachedBytes, 0, bytesLeft < CachedBytesLength ? bytesLeft : CachedBytesLength)) > 0)
{
bytesLeft -= bytesRead;
Write(s_CachedBytes, 0, bytesRead);
}
Array.Clear(s_CachedBytes, 0, CachedBytesLength);
}
/// <summary>
/// 将文件系统流立刻更新到存储介质中。
/// </summary>
protected internal abstract void Flush();
/// <summary>
/// 关闭文件系统流。
/// </summary>
protected internal abstract void Close();
}
这个文件系统流是个抽象类,里面包含了基础数据:缓存数据流的数组以及数组的最大长度,文件系统流位置和长度;方法有定位、读写(重载比较多,还有片段读写的方法)、刷新和关闭。对其中一对读写方法感到比较奇怪,只读取和写入一个字节,不是很懂这个方法会在什么情况下使用。
/// <summary>
/// 通用文件系统流。
/// </summary>
public sealed class CommonFileSystemStream : FileSystemStream, IDisposable
{
private readonly FileStream m_FileStream;
/// <summary>
/// 初始化通用文件系统流的新实例。
/// </summary>
/// <param name="fullPath">要加载的文件系统的完整路径。</param>
/// <param name="access">要加载的文件系统的访问方式。</param>
/// <param name="createNew">是否创建新的文件系统流。</param>
public CommonFileSystemStream(string fullPath, FileSystemAccess access, bool createNew)
{
if (string.IsNullOrEmpty(fullPath))
{
throw new GameFrameworkException("Full path is invalid.");
}
switch (access)
{
case FileSystemAccess.Read:
m_FileStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read);
break;
case FileSystemAccess.Write:
m_FileStream = new FileStream(fullPath, createNew ? FileMode.Create : FileMode.Open,
FileAccess.Write, FileShare.Read);
break;
case FileSystemAccess.ReadWrite:
m_FileStream = new FileStream(fullPath, createNew ? FileMode.Create : FileMode.Open,
FileAccess.ReadWrite, FileShare.Read);
break;
default:
throw new GameFrameworkException("Access is invalid.");
}
}
/// <summary>
/// 获取或设置文件系统流位置。
/// </summary>
protected internal override long Position
{
get { return m_FileStream.Position; }
set { m_FileStream.Position = value; }
}
/// <summary>
/// 获取文件系统流长度。
/// </summary>
protected internal override long Length
{
get { return m_FileStream.Length; }
}
/// <summary>
/// 设置文件系统流长度。
/// </summary>
/// <param name="length">要设置的文件系统流的长度。</param>
protected internal override void SetLength(long length)
{
m_FileStream.SetLength(length);
}
/// <summary>
/// 定位文件系统流位置。
/// </summary>
/// <param name="offset">要定位的文件系统流位置的偏移。</param>
/// <param name="origin">要定位的文件系统流位置的方式。</param>
protected internal override void Seek(long offset, SeekOrigin origin)
{
m_FileStream.Seek(offset, origin);
}
/// <summary>
/// 从文件系统流中读取一个字节。
/// </summary>
/// <returns>读取的字节,若已经到达文件结尾,则返回 -1。</returns>
protected internal override int ReadByte()
{
return m_FileStream.ReadByte();
}
/// <summary>
/// 从文件系统流中读取二进制流。
/// </summary>
/// <param name="buffer">存储读取文件内容的二进制流。</param>
/// <param name="startIndex">存储读取文件内容的二进制流的起始位置。</param>
/// <param name="length">存储读取文件内容的二进制流的长度。</param>
/// <returns>实际读取了多少字节。</returns>
protected internal override int Read(byte[] buffer, int startIndex, int length)
{
return m_FileStream.Read(buffer, startIndex, length);
}
/// <summary>
/// 向文件系统流中写入一个字节。
/// </summary>
/// <param name="value">要写入的字节。</param>
protected internal override void WriteByte(byte value)
{
m_FileStream.WriteByte(value);
}
/// <summary>
/// 向文件系统流中写入二进制流。
/// </summary>
/// <param name="buffer">存储写入文件内容的二进制流。</param>
/// <param name="startIndex">存储写入文件内容的二进制流的起始位置。</param>
/// <param name="length">存储写入文件内容的二进制流的长度。</param>
protected internal override void Write(byte[] buffer, int startIndex, int length)
{
m_FileStream.Write(buffer, startIndex, length);
}
/// <summary>
/// 将文件系统流立刻更新到存储介质中。
/// </summary>
protected internal override void Flush()
{
m_FileStream.Flush();
}
/// <summary>
/// 关闭文件系统流。
/// </summary>
protected internal override void Close()
{
m_FileStream.Close();
}
/// <summary>
/// 销毁文件系统流。
/// </summary>
public void Dispose()
{
m_FileStream.Dispose();
}
}
CommonFileSystemStream继承了FileSystemStream并实现了IDisposable接口。发现它重写FileSystemStream里面的方法是依靠FileSystem的api,也就是说这个类是对FileSystem进行了封装,通过流操作实现了读写功能。
流操作看完了,感觉还好,接下来就要对文件系统进行操作了。
/// <summary>
/// 文件系统接口。
/// </summary>
public interface IFileSystem
{
/// <summary>
/// 获取文件系统完整路径。
/// </summary>
string FullPath
{
get;
}
/// <summary>
/// 获取文件系统访问方式。
/// </summary>
FileSystemAccess Access
{
get;
}
/// <summary>
/// 获取文件数量。
/// </summary>
int FileCount
{
get;
}
/// <summary>
/// 获取最大文件数量。
/// </summary>
int MaxFileCount
{
get;
}
/// <summary>
/// 获取文件信息。
/// </summary>
/// <param name="name">要获取文件信息的文件名称。</param>
/// <returns>获取的文件信息。</returns>
FileInfo GetFileInfo(string name);
/// <summary>
/// 获取所有文件信息。
/// </summary>
/// <returns>获取的所有文件信息。</returns>
FileInfo[] GetAllFileInfos();
/// <summary>
/// 获取所有文件信息。
/// </summary>
/// <param name="results">获取的所有文件信息。</param>
void GetAllFileInfos(List<FileInfo> results);
/// <summary>
/// 检查是否存在指定文件。
/// </summary>
/// <param name="name">要检查的文件名称。</param>
/// <returns>是否存在指定文件。</returns>
bool HasFile(string name);
/// <summary>
/// 读取指定文件。
/// </summary>
/// <param name="name">要读取的文件名称。</param>
/// <returns>存储读取文件内容的二进制流。</returns>
byte[] ReadFile(string name);
/// <summary>
/// 读取指定文件。
/// </summary>
/// <param name="name">要读取的文件名称。</param>
/// <param name="buffer">存储读取文件内容的二进制流。</param>
/// <returns>实际读取了多少字节。</returns>
int ReadFile(string name, byte[] buffer);
/// <summary>
/// 读取指定文件。
/// </summary>
/// <param name="name">要读取的文件名称。</param>
/// <param name="buffer">存储读取文件内容的二进制流。</param>
/// <param name="startIndex">存储读取文件内容的二进制流的起始位置。</param>
/// <returns>实际读取了多少字节。</returns>
int ReadFile(string name, byte[] buffer, int startIndex);
/// <summary>
/// 读取指定文件。
/// </summary>
/// <param name="name">要读取的文件名称。</param>
/// <param name="buffer">存储读取文件内容的二进制流。</param>
/// <param name="startIndex">存储读取文件内容的二进制流的起始位置。</param>
/// <param name="length">存储读取文件内容的二进制流的长度。</param>
/// <returns>实际读取了多少字节。</returns>
int ReadFile(string name, byte[] buffer, int startIndex, int length);
/// <summary>
/// 读取指定文件。
/// </summary>
/// <param name="name">要读取的文件名称。</param>
/// <param name="stream">存储读取文件内容的二进制流。</param>
/// <returns>实际读取了多少字节。</returns>
int ReadFile(string name, Stream stream);
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>存储读取文件片段内容的二进制流。</returns>
byte[] ReadFileSegment(string name, int length);
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="offset">要读取片段的偏移。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>存储读取文件片段内容的二进制流。</returns>
byte[] ReadFileSegment(string name, int offset, int length);
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="buffer">存储读取文件片段内容的二进制流。</param>
/// <returns>实际读取了多少字节。</returns>
int ReadFileSegment(string name, byte[] buffer);
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="buffer">存储读取文件片段内容的二进制流。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>实际读取了多少字节。</returns>
int ReadFileSegment(string name, byte[] buffer, int length);
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="buffer">存储读取文件片段内容的二进制流。</param>
/// <param name="startIndex">存储读取文件片段内容的二进制流的起始位置。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>实际读取了多少字节。</returns>
int ReadFileSegment(string name, byte[] buffer, int startIndex, int length);
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="offset">要读取片段的偏移。</param>
/// <param name="buffer">存储读取文件片段内容的二进制流。</param>
/// <returns>实际读取了多少字节。</returns>
int ReadFileSegment(string name, int offset, byte[] buffer);
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="offset">要读取片段的偏移。</param>
/// <param name="buffer">存储读取文件片段内容的二进制流。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>实际读取了多少字节。</returns>
int ReadFileSegment(string name, int offset, byte[] buffer, int length);
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="offset">要读取片段的偏移。</param>
/// <param name="buffer">存储读取文件片段内容的二进制流。</param>
/// <param name="startIndex">存储读取文件片段内容的二进制流的起始位置。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>实际读取了多少字节。</returns>
int ReadFileSegment(string name, int offset, byte[] buffer, int startIndex, int length);
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="stream">存储读取文件片段内容的二进制流。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>实际读取了多少字节。</returns>
int ReadFileSegment(string name, Stream stream, int length);
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="offset">要读取片段的偏移。</param>
/// <param name="stream">存储读取文件片段内容的二进制流。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>实际读取了多少字节。</returns>
int ReadFileSegment(string name, int offset, Stream stream, int length);
/// <summary>
/// 写入指定文件。
/// </summary>
/// <param name="name">要写入的文件名称。</param>
/// <param name="buffer">存储写入文件内容的二进制流。</param>
/// <returns>是否写入指定文件成功。</returns>
bool WriteFile(string name, byte[] buffer);
/// <summary>
/// 写入指定文件。
/// </summary>
/// <param name="name">要写入的文件名称。</param>
/// <param name="buffer">存储写入文件内容的二进制流。</param>
/// <param name="startIndex">存储写入文件内容的二进制流的起始位置。</param>
/// <returns>是否写入指定文件成功。</returns>
bool WriteFile(string name, byte[] buffer, int startIndex);
/// <summary>
/// 写入指定文件。
/// </summary>
/// <param name="name">要写入的文件名称。</param>
/// <param name="buffer">存储写入文件内容的二进制流。</param>
/// <param name="startIndex">存储写入文件内容的二进制流的起始位置。</param>
/// <param name="length">存储写入文件内容的二进制流的长度。</param>
/// <returns>是否写入指定文件成功。</returns>
bool WriteFile(string name, byte[] buffer, int startIndex, int length);
/// <summary>
/// 写入指定文件。
/// </summary>
/// <param name="name">要写入的文件名称。</param>
/// <param name="stream">存储写入文件内容的二进制流。</param>
/// <returns>是否写入指定文件成功。</returns>
bool WriteFile(string name, Stream stream);
/// <summary>
/// 写入指定文件。
/// </summary>
/// <param name="name">要写入的文件名称。</param>
/// <param name="filePath">存储写入文件内容的文件路径。</param>
/// <returns>是否写入指定文件成功。</returns>
bool WriteFile(string name, string filePath);
/// <summary>
/// 将指定文件另存为物理文件。
/// </summary>
/// <param name="name">要另存为的文件名称。</param>
/// <param name="filePath">存储写入文件内容的文件路径。</param>
/// <returns>是否将指定文件另存为物理文件成功。</returns>
bool SaveAsFile(string name, string filePath);
/// <summary>
/// 重命名指定文件。
/// </summary>
/// <param name="oldName">要重命名的文件名称。</param>
/// <param name="newName">重命名后的文件名称。</param>
/// <returns>是否重命名指定文件成功。</returns>
bool RenameFile(string oldName, string newName);
/// <summary>
/// 删除指定文件。
/// </summary>
/// <param name="name">要删除的文件名称。</param>
/// <returns>是否删除指定文件成功。</returns>
bool DeleteFile(string name);
}
文件系统接口IFileSystem中的第一个属性是文件系统的全路径,这个全路径暂时还不太理解。然后是文件系统的访问权限以及文件系统的文件数量和最大数量。方法按逻辑顺序来说是:判断文件是否存在、获取文件信息、获取全部文件信息、读写文件,还有另存、重命名和删除的方法。
实现接口的类FileSystem分成了四个脚本来写:实现接口部分、头数据处理、块数据处理以及字符串数据处理。
internal sealed partial class FileSystem : IFileSystem
{
/// <summary>
/// 头数据。
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct HeaderData
{
private const int HeaderLength = 3;
private const int EncryptBytesLength = 4;
private static readonly byte[] Header = new byte[HeaderLength] { (byte)'G', (byte)'F', (byte)'F' };
[MarshalAs(UnmanagedType.ByValArray, SizeConst = HeaderLength)]
private readonly byte[] m_Header;
private readonly byte m_Version;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = EncryptBytesLength)]
private readonly byte[] m_EncryptBytes;
private readonly int m_MaxFileCount;
private readonly int m_MaxBlockCount;
private readonly int m_BlockCount;
public HeaderData(int maxFileCount, int maxBlockCount)
: this(0, new byte[EncryptBytesLength], maxFileCount, maxBlockCount, 0)
{
Utility.Random.GetRandomBytes(m_EncryptBytes);
}
public HeaderData(byte version, byte[] encryptBytes, int maxFileCount, int maxBlockCount, int blockCount)
{
m_Header = Header;
m_Version = version;
m_EncryptBytes = encryptBytes;
m_MaxFileCount = maxFileCount;
m_MaxBlockCount = maxBlockCount;
m_BlockCount = blockCount;
}
public bool IsValid
{
get
{
return m_Header.Length == HeaderLength && m_Header[0] == Header[0] && m_Header[1] == Header[1] && m_Header[2] == Header[2] && m_Version == 0 && m_EncryptBytes.Length == EncryptBytesLength
&& m_MaxFileCount > 0 && m_MaxBlockCount > 0 && m_MaxFileCount <= m_MaxBlockCount && m_BlockCount > 0 && m_BlockCount <= m_MaxBlockCount;
}
}
public byte Version
{
get
{
return m_Version;
}
}
public int MaxFileCount
{
get
{
return m_MaxFileCount;
}
}
public int MaxBlockCount
{
get
{
return m_MaxBlockCount;
}
}
public int BlockCount
{
get
{
return m_BlockCount;
}
}
public byte[] GetEncryptBytes()
{
return m_EncryptBytes;
}
public HeaderData SetBlockCount(int blockCount)
{
return new HeaderData(m_Version, m_EncryptBytes, m_MaxFileCount, m_MaxBlockCount, blockCount);
}
}
}
头数据部分,定义了一个私有结构体HeaderData。这个结构体保存的是文件的整体信息,主要字段有三个:最大文件数、最大块数和块数(单从命名上看不出来指的是什么块数),以及用于加密的数组。
块数据部分类似头数据,也是定义了一个结构体来保存文件中的字符串索引和簇索引以及块长。其中簇索引是用来计算数据偏移的(从命名上判断的),字符串索引,emm,暂时还不清楚。
internal sealed partial class FileSystem : IFileSystem
{
/// <summary>
/// 块数据。
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct BlockData
{
public static readonly BlockData Empty = new BlockData(0, 0);
private readonly int m_StringIndex;
private readonly int m_ClusterIndex;
private readonly int m_Length;
public BlockData(int clusterIndex, int length)
: this(-1, clusterIndex, length)
{
}
public BlockData(int stringIndex, int clusterIndex, int length)
{
m_StringIndex = stringIndex;
m_ClusterIndex = clusterIndex;
m_Length = length;
}
public bool Using
{
get
{
return m_StringIndex >= 0;
}
}
public int StringIndex
{
get
{
return m_StringIndex;
}
}
public int ClusterIndex
{
get
{
return m_ClusterIndex;
}
}
public int Length
{
get
{
return m_Length;
}
}
public BlockData Free()
{
return new BlockData(m_ClusterIndex, (int)GetUpBoundClusterOffset(m_Length));
}
}
}
字符串数据部分最简单,只定义了一个数组和一个长度,以及一个辅助计算的临时数组。
internal sealed partial class FileSystem : IFileSystem
{
/// <summary>
/// 字符串数据。
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct StringData
{
private static readonly byte[] s_CachedBytes = new byte[byte.MaxValue + 1];
private readonly byte m_Length;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = byte.MaxValue)]
private readonly byte[] m_Bytes;
public StringData(byte length, byte[] bytes)
{
m_Length = length;
m_Bytes = bytes;
}
public string GetString(byte[] encryptBytes)
{
if (m_Length <= 0)
{
return null;
}
Array.Copy(m_Bytes, 0, s_CachedBytes, 0, m_Length);
Utility.Encryption.GetSelfXorBytes(s_CachedBytes, 0, m_Length, encryptBytes);
return Utility.Converter.GetString(s_CachedBytes, 0, m_Length);
}
public StringData SetString(string value, byte[] encryptBytes)
{
if (string.IsNullOrEmpty(value))
{
return Clear();
}
int length = Utility.Converter.GetBytes(value, s_CachedBytes);
if (length > byte.MaxValue)
{
throw new GameFrameworkException(Utility.Text.Format("String '{0}' is too long.", value));
}
Utility.Encryption.GetSelfXorBytes(s_CachedBytes, encryptBytes);
Array.Copy(s_CachedBytes, 0, m_Bytes, 0, length);
return new StringData((byte) length, m_Bytes);
}
public StringData Clear()
{
return new StringData(0, m_Bytes);
}
}
}
把这三部分列出来,发现都比较简单,都是一个用于存储数据的结构体,但是具体功能或者说怎么使用它们还不是很清楚。
internal sealed partial class FileSystem : IFileSystem
{
private const int ClusterSize = 1024 * 4;
private const int CachedBytesLength = 0x1000;
private static readonly string[] EmptyStringArray = new string[] { };
private static readonly byte[] s_CachedBytes = new byte[CachedBytesLength];
private static readonly int HeaderDataSize = Marshal.SizeOf(typeof(HeaderData));
private static readonly int BlockDataSize = Marshal.SizeOf(typeof(BlockData));
private static readonly int StringDataSize = Marshal.SizeOf(typeof(StringData));
private readonly string m_FullPath;
private readonly FileSystemAccess m_Access;
private readonly FileSystemStream m_Stream;
private readonly Dictionary<string, int> m_FileDatas;
private readonly List<BlockData> m_BlockDatas;
private readonly GameFrameworkMultiDictionary<int, int> m_FreeBlockIndexes;
private readonly SortedDictionary<int, StringData> m_StringDatas;
private readonly Queue<KeyValuePair<int, StringData>> m_FreeStringDatas;
private HeaderData m_HeaderData;
private int m_BlockDataOffset;
private int m_StringDataOffset;
private int m_FileDataOffset;
/// <summary>
/// 初始化文件系统的新实例。
/// </summary>
/// <param name="fullPath">文件系统完整路径。</param>
/// <param name="access">文件系统访问方式。</param>
/// <param name="stream">文件系统流。</param>
private FileSystem(string fullPath, FileSystemAccess access, FileSystemStream stream)
{
if (string.IsNullOrEmpty(fullPath))
{
throw new GameFrameworkException("Full path is invalid.");
}
if (access == FileSystemAccess.Unspecified)
{
throw new GameFrameworkException("Access is invalid.");
}
if (stream == null)
{
throw new GameFrameworkException("Stream is invalid.");
}
m_FullPath = fullPath;
m_Access = access;
m_Stream = stream;
m_FileDatas = new Dictionary<string, int>(StringComparer.Ordinal);
m_BlockDatas = new List<BlockData>();
m_FreeBlockIndexes = new GameFrameworkMultiDictionary<int, int>();
m_StringDatas = new SortedDictionary<int, StringData>();
m_FreeStringDatas = new Queue<KeyValuePair<int, StringData>>();
m_HeaderData = default(HeaderData);
m_BlockDataOffset = 0;
m_StringDataOffset = 0;
m_FileDataOffset = 0;
Utility.Marshal.EnsureCachedHGlobalSize(CachedBytesLength);
}
/// <summary>
/// 获取文件系统完整路径。
/// </summary>
public string FullPath
{
get
{
return m_FullPath;
}
}
/// <summary>
/// 获取文件系统访问方式。
/// </summary>
public FileSystemAccess Access
{
get
{
return m_Access;
}
}
/// <summary>
/// 获取文件数量。
/// </summary>
public int FileCount
{
get
{
return m_FileDatas.Count;
}
}
/// <summary>
/// 获取最大文件数量。
/// </summary>
public int MaxFileCount
{
get
{
return m_HeaderData.MaxFileCount;
}
}
/// <summary>
/// 创建文件系统。
/// </summary>
/// <param name="fullPath">要创建的文件系统的完整路径。</param>
/// <param name="access">要创建的文件系统的访问方式。</param>
/// <param name="stream">要创建的文件系统的文件系统流。</param>
/// <param name="maxFileCount">要创建的文件系统的最大文件数量。</param>
/// <param name="maxBlockCount">要创建的文件系统的最大块数据数量。</param>
/// <returns>创建的文件系统。</returns>
public static FileSystem Create(string fullPath, FileSystemAccess access, FileSystemStream stream, int maxFileCount, int maxBlockCount)
{
if (maxFileCount <= 0)
{
throw new GameFrameworkException("Max file count is invalid.");
}
if (maxBlockCount <= 0)
{
throw new GameFrameworkException("Max block count is invalid.");
}
if (maxFileCount > maxBlockCount)
{
throw new GameFrameworkException("Max file count can not larger than max block count.");
}
FileSystem fileSystem = new FileSystem(fullPath, access, stream);
fileSystem.m_HeaderData = new HeaderData(maxFileCount, maxBlockCount);
CalcOffsets(fileSystem);
Utility.Marshal.StructureToBytes(fileSystem.m_HeaderData, HeaderDataSize, s_CachedBytes);
try
{
stream.Write(s_CachedBytes, 0, HeaderDataSize);
stream.SetLength(fileSystem.m_FileDataOffset);
return fileSystem;
}
catch
{
fileSystem.Shutdown();
return null;
}
}
/// <summary>
/// 加载文件系统。
/// </summary>
/// <param name="fullPath">要加载的文件系统的完整路径。</param>
/// <param name="access">要加载的文件系统的访问方式。</param>
/// <param name="stream">要加载的文件系统的文件系统流。</param>
/// <returns>加载的文件系统。</returns>
public static FileSystem Load(string fullPath, FileSystemAccess access, FileSystemStream stream)
{
FileSystem fileSystem = new FileSystem(fullPath, access, stream);
stream.Read(s_CachedBytes, 0, HeaderDataSize);
fileSystem.m_HeaderData = Utility.Marshal.BytesToStructure<HeaderData>(HeaderDataSize, s_CachedBytes);
CalcOffsets(fileSystem);
if (fileSystem.m_BlockDatas.Capacity < fileSystem.m_HeaderData.BlockCount)
{
fileSystem.m_BlockDatas.Capacity = fileSystem.m_HeaderData.BlockCount;
}
for (int i = 0; i < fileSystem.m_HeaderData.BlockCount; i++)
{
stream.Read(s_CachedBytes, 0, BlockDataSize);
BlockData blockData = Utility.Marshal.BytesToStructure<BlockData>(BlockDataSize, s_CachedBytes);
fileSystem.m_BlockDatas.Add(blockData);
}
for (int i = 0; i < fileSystem.m_BlockDatas.Count; i++)
{
BlockData blockData = fileSystem.m_BlockDatas[i];
if (blockData.Using)
{
StringData stringData = fileSystem.ReadStringData(blockData.StringIndex);
fileSystem.m_StringDatas.Add(blockData.StringIndex, stringData);
fileSystem.m_FileDatas.Add(stringData.GetString(fileSystem.m_HeaderData.GetEncryptBytes()), i);
}
else
{
fileSystem.m_FreeBlockIndexes.Add(blockData.Length, i);
}
}
return fileSystem;
}
/// <summary>
/// 关闭并清理文件系统。
/// </summary>
public void Shutdown()
{
m_Stream.Close();
m_FileDatas.Clear();
m_BlockDatas.Clear();
m_FreeBlockIndexes.Clear();
m_StringDatas.Clear();
m_FreeStringDatas.Clear();
m_BlockDataOffset = 0;
m_StringDataOffset = 0;
m_FileDataOffset = 0;
}
/// <summary>
/// 获取文件信息。
/// </summary>
/// <param name="name">要获取文件信息的文件名称。</param>
/// <returns>获取的文件信息。</returns>
public FileInfo GetFileInfo(string name)
{
if (string.IsNullOrEmpty(name))
{
throw new GameFrameworkException("Name is invalid.");
}
int blockIndex = 0;
if (!m_FileDatas.TryGetValue(name, out blockIndex))
{
return default(FileInfo);
}
BlockData blockData = m_BlockDatas[blockIndex];
return new FileInfo(name, GetClusterOffset(blockData.ClusterIndex), blockData.Length);
}
/// <summary>
/// 获取所有文件信息。
/// </summary>
/// <returns>获取的所有文件信息。</returns>
public FileInfo[] GetAllFileInfos()
{
int index = 0;
FileInfo[] results = new FileInfo[m_FileDatas.Count];
foreach (KeyValuePair<string, int> fileData in m_FileDatas)
{
BlockData blockData = m_BlockDatas[fileData.Value];
results[index++] = new FileInfo(fileData.Key, GetClusterOffset(blockData.ClusterIndex), blockData.Length);
}
return results;
}
/// <summary>
/// 获取所有文件信息。
/// </summary>
/// <param name="results">获取的所有文件信息。</param>
public void GetAllFileInfos(List<FileInfo> results)
{
if (results == null)
{
throw new GameFrameworkException("Results is invalid.");
}
results.Clear();
foreach (KeyValuePair<string, int> fileData in m_FileDatas)
{
BlockData blockData = m_BlockDatas[fileData.Value];
results.Add(new FileInfo(fileData.Key, GetClusterOffset(blockData.ClusterIndex), blockData.Length));
}
}
/// <summary>
/// 检查是否存在指定文件。
/// </summary>
/// <param name="name">要检查的文件名称。</param>
/// <returns>是否存在指定文件。</returns>
public bool HasFile(string name)
{
if (string.IsNullOrEmpty(name))
{
throw new GameFrameworkException("Name is invalid.");
}
return m_FileDatas.ContainsKey(name);
}
/// <summary>
/// 读取指定文件。
/// </summary>
/// <param name="name">要读取的文件名称。</param>
/// <returns>存储读取文件内容的二进制流。</returns>
public byte[] ReadFile(string name)
{
if (m_Access != FileSystemAccess.Read && m_Access != FileSystemAccess.ReadWrite)
{
throw new GameFrameworkException("File system is not readable.");
}
if (string.IsNullOrEmpty(name))
{
throw new GameFrameworkException("Name is invalid.");
}
FileInfo fileInfo = GetFileInfo(name);
if (!fileInfo.IsValid)
{
return null;
}
int length = fileInfo.Length;
byte[] buffer = new byte[length];
if (length > 0)
{
m_Stream.Position = fileInfo.Offset;
m_Stream.Read(buffer, 0, length);
}
return buffer;
}
/// <summary>
/// 读取指定文件。
/// </summary>
/// <param name="name">要读取的文件名称。</param>
/// <param name="buffer">存储读取文件内容的二进制流。</param>
/// <returns>实际读取了多少字节。</returns>
public int ReadFile(string name, byte[] buffer)
{
if (buffer == null)
{
throw new GameFrameworkException("Buffer is invalid.");
}
return ReadFile(name, buffer, 0, buffer.Length);
}
/// <summary>
/// 读取指定文件。
/// </summary>
/// <param name="name">要读取的文件名称。</param>
/// <param name="buffer">存储读取文件内容的二进制流。</param>
/// <param name="startIndex">存储读取文件内容的二进制流的起始位置。</param>
/// <returns>实际读取了多少字节。</returns>
public int ReadFile(string name, byte[] buffer, int startIndex)
{
if (buffer == null)
{
throw new GameFrameworkException("Buffer is invalid.");
}
return ReadFile(name, buffer, startIndex, buffer.Length - startIndex);
}
/// <summary>
/// 读取指定文件。
/// </summary>
/// <param name="name">要读取的文件名称。</param>
/// <param name="buffer">存储读取文件内容的二进制流。</param>
/// <param name="startIndex">存储读取文件内容的二进制流的起始位置。</param>
/// <param name="length">存储读取文件内容的二进制流的长度。</param>
/// <returns>实际读取了多少字节。</returns>
public int ReadFile(string name, byte[] buffer, int startIndex, int length)
{
if (m_Access != FileSystemAccess.Read && m_Access != FileSystemAccess.ReadWrite)
{
throw new GameFrameworkException("File system is not readable.");
}
if (string.IsNullOrEmpty(name))
{
throw new GameFrameworkException("Name is invalid.");
}
if (buffer == null)
{
throw new GameFrameworkException("Buffer is invalid.");
}
if (startIndex < 0 || length < 0 || startIndex + length > buffer.Length)
{
throw new GameFrameworkException("Start index or length is invalid.");
}
FileInfo fileInfo = GetFileInfo(name);
if (!fileInfo.IsValid)
{
return 0;
}
m_Stream.Position = fileInfo.Offset;
if (length > fileInfo.Length)
{
length = fileInfo.Length;
}
if (length > 0)
{
return m_Stream.Read(buffer, startIndex, length);
}
return 0;
}
/// <summary>
/// 读取指定文件。
/// </summary>
/// <param name="name">要读取的文件名称。</param>
/// <param name="stream">存储读取文件内容的二进制流。</param>
/// <returns>实际读取了多少字节。</returns>
public int ReadFile(string name, Stream stream)
{
if (m_Access != FileSystemAccess.Read && m_Access != FileSystemAccess.ReadWrite)
{
throw new GameFrameworkException("File system is not readable.");
}
if (string.IsNullOrEmpty(name))
{
throw new GameFrameworkException("Name is invalid.");
}
if (stream == null)
{
throw new GameFrameworkException("Stream is invalid.");
}
if (!stream.CanWrite)
{
throw new GameFrameworkException("Stream is not writable.");
}
FileInfo fileInfo = GetFileInfo(name);
if (!fileInfo.IsValid)
{
return 0;
}
int length = fileInfo.Length;
if (length > 0)
{
m_Stream.Position = fileInfo.Offset;
return m_Stream.Read(stream, length);
}
return 0;
}
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>存储读取文件片段内容的二进制流。</returns>
public byte[] ReadFileSegment(string name, int length)
{
return ReadFileSegment(name, 0, length);
}
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="offset">要读取片段的偏移。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>存储读取文件片段内容的二进制流。</returns>
public byte[] ReadFileSegment(string name, int offset, int length)
{
if (m_Access != FileSystemAccess.Read && m_Access != FileSystemAccess.ReadWrite)
{
throw new GameFrameworkException("File system is not readable.");
}
if (string.IsNullOrEmpty(name))
{
throw new GameFrameworkException("Name is invalid.");
}
if (offset < 0)
{
throw new GameFrameworkException("Index is invalid.");
}
if (length < 0)
{
throw new GameFrameworkException("Length is invalid.");
}
FileInfo fileInfo = GetFileInfo(name);
if (!fileInfo.IsValid)
{
return null;
}
if (offset > fileInfo.Length)
{
offset = fileInfo.Length;
}
int leftLength = fileInfo.Length - offset;
if (length > leftLength)
{
length = leftLength;
}
byte[] buffer = new byte[length];
if (length > 0)
{
m_Stream.Position = fileInfo.Offset + offset;
m_Stream.Read(buffer, 0, length);
}
return buffer;
}
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="buffer">存储读取文件片段内容的二进制流。</param>
/// <returns>实际读取了多少字节。</returns>
public int ReadFileSegment(string name, byte[] buffer)
{
if (buffer == null)
{
throw new GameFrameworkException("Buffer is invalid.");
}
return ReadFileSegment(name, 0, buffer, 0, buffer.Length);
}
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="buffer">存储读取文件片段内容的二进制流。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>实际读取了多少字节。</returns>
public int ReadFileSegment(string name, byte[] buffer, int length)
{
return ReadFileSegment(name, 0, buffer, 0, length);
}
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="buffer">存储读取文件片段内容的二进制流。</param>
/// <param name="startIndex">存储读取文件片段内容的二进制流的起始位置。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>实际读取了多少字节。</returns>
public int ReadFileSegment(string name, byte[] buffer, int startIndex, int length)
{
return ReadFileSegment(name, 0, buffer, startIndex, length);
}
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="offset">要读取片段的偏移。</param>
/// <param name="buffer">存储读取文件片段内容的二进制流。</param>
/// <returns>实际读取了多少字节。</returns>
public int ReadFileSegment(string name, int offset, byte[] buffer)
{
if (buffer == null)
{
throw new GameFrameworkException("Buffer is invalid.");
}
return ReadFileSegment(name, offset, buffer, 0, buffer.Length);
}
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="offset">要读取片段的偏移。</param>
/// <param name="buffer">存储读取文件片段内容的二进制流。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>实际读取了多少字节。</returns>
public int ReadFileSegment(string name, int offset, byte[] buffer, int length)
{
return ReadFileSegment(name, offset, buffer, 0, length);
}
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="offset">要读取片段的偏移。</param>
/// <param name="buffer">存储读取文件片段内容的二进制流。</param>
/// <param name="startIndex">存储读取文件片段内容的二进制流的起始位置。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>实际读取了多少字节。</returns>
public int ReadFileSegment(string name, int offset, byte[] buffer, int startIndex, int length)
{
if (m_Access != FileSystemAccess.Read && m_Access != FileSystemAccess.ReadWrite)
{
throw new GameFrameworkException("File system is not readable.");
}
if (string.IsNullOrEmpty(name))
{
throw new GameFrameworkException("Name is invalid.");
}
if (offset < 0)
{
throw new GameFrameworkException("Index is invalid.");
}
if (buffer == null)
{
throw new GameFrameworkException("Buffer is invalid.");
}
if (startIndex < 0 || length < 0 || startIndex + length > buffer.Length)
{
throw new GameFrameworkException("Start index or length is invalid.");
}
FileInfo fileInfo = GetFileInfo(name);
if (!fileInfo.IsValid)
{
return 0;
}
if (offset > fileInfo.Length)
{
offset = fileInfo.Length;
}
int leftLength = fileInfo.Length - offset;
if (length > leftLength)
{
length = leftLength;
}
if (length > 0)
{
m_Stream.Position = fileInfo.Offset + offset;
return m_Stream.Read(buffer, startIndex, length);
}
return 0;
}
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="stream">存储读取文件片段内容的二进制流。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>实际读取了多少字节。</returns>
public int ReadFileSegment(string name, Stream stream, int length)
{
return ReadFileSegment(name, 0, stream, length);
}
/// <summary>
/// 读取指定文件的指定片段。
/// </summary>
/// <param name="name">要读取片段的文件名称。</param>
/// <param name="offset">要读取片段的偏移。</param>
/// <param name="stream">存储读取文件片段内容的二进制流。</param>
/// <param name="length">要读取片段的长度。</param>
/// <returns>实际读取了多少字节。</returns>
public int ReadFileSegment(string name, int offset, Stream stream, int length)
{
if (m_Access != FileSystemAccess.Read && m_Access != FileSystemAccess.ReadWrite)
{
throw new GameFrameworkException("File system is not readable.");
}
if (string.IsNullOrEmpty(name))
{
throw new GameFrameworkException("Name is invalid.");
}
if (offset < 0)
{
throw new GameFrameworkException("Index is invalid.");
}
if (stream == null)
{
throw new GameFrameworkException("Stream is invalid.");
}
if (!stream.CanWrite)
{
throw new GameFrameworkException("Stream is not writable.");
}
if (length < 0)
{
throw new GameFrameworkException("Length is invalid.");
}
FileInfo fileInfo = GetFileInfo(name);
if (!fileInfo.IsValid)
{
return 0;
}
if (offset > fileInfo.Length)
{
offset = fileInfo.Length;
}
int leftLength = fileInfo.Length - offset;
if (length > leftLength)
{
length = leftLength;
}
if (length > 0)
{
m_Stream.Position = fileInfo.Offset + offset;
return m_Stream.Read(stream, length);
}
return 0;
}
/// <summary>
/// 写入指定文件。
/// </summary>
/// <param name="name">要写入的文件名称。</param>
/// <param name="buffer">存储写入文件内容的二进制流。</param>
/// <returns>是否写入指定文件成功。</returns>
public bool WriteFile(string name, byte[] buffer)
{
if (buffer == null)
{
throw new GameFrameworkException("Buffer is invalid.");
}
return WriteFile(name, buffer, 0, buffer.Length);
}
/// <summary>
/// 写入指定文件。
/// </summary>
/// <param name="name">要写入的文件名称。</param>
/// <param name="buffer">存储写入文件内容的二进制流。</param>
/// <param name="startIndex">存储写入文件内容的二进制流的起始位置。</param>
/// <returns>是否写入指定文件成功。</returns>
public bool WriteFile(string name, byte[] buffer, int startIndex)
{
if (buffer == null)
{
throw new GameFrameworkException("Buffer is invalid.");
}
return WriteFile(name, buffer, startIndex, buffer.Length - startIndex);
}
/// <summary>
/// 写入指定文件。
/// </summary>
/// <param name="name">要写入的文件名称。</param>
/// <param name="buffer">存储写入文件内容的二进制流。</param>
/// <param name="startIndex">存储写入文件内容的二进制流的起始位置。</param>
/// <param name="length">存储写入文件内容的二进制流的长度。</param>
/// <returns>是否写入指定文件成功。</returns>
public bool WriteFile(string name, byte[] buffer, int startIndex, int length)
{
if (m_Access != FileSystemAccess.Write && m_Access != FileSystemAccess.ReadWrite)
{
throw new GameFrameworkException("File system is not writable.");
}
if (string.IsNullOrEmpty(name))
{
throw new GameFrameworkException("Name is invalid.");
}
if (name.Length > byte.MaxValue)
{
throw new GameFrameworkException(Utility.Text.Format("Name '{0}' is too long.", name));
}
if (buffer == null)
{
throw new GameFrameworkException("Buffer is invalid.");
}
if (startIndex < 0 || length < 0 || startIndex + length > buffer.Length)
{
throw new GameFrameworkException("Start index or length is invalid.");
}
bool hasFile = false;
int oldBlockIndex = -1;
if (m_FileDatas.TryGetValue(name, out oldBlockIndex))
{
hasFile = true;
}
if (!hasFile && m_FileDatas.Count >= m_HeaderData.MaxFileCount)
{
return false;
}
int blockIndex = AllocBlock(length);
if (blockIndex < 0)
{
return false;
}
if (length > 0)
{
m_Stream.Position = GetClusterOffset(m_BlockDatas[blockIndex].ClusterIndex);
m_Stream.Write(buffer, startIndex, length);
}
ProcessWriteFile(name, hasFile, oldBlockIndex, blockIndex, length);
m_Stream.Flush();
return true;
}
/// <summary>
/// 写入指定文件。
/// </summary>
/// <param name="name">要写入的文件名称。</param>
/// <param name="stream">存储写入文件内容的二进制流。</param>
/// <returns>是否写入指定文件成功。</returns>
public bool WriteFile(string name, Stream stream)
{
if (m_Access != FileSystemAccess.Write && m_Access != FileSystemAccess.ReadWrite)
{
throw new GameFrameworkException("File system is not writable.");
}
if (string.IsNullOrEmpty(name))
{
throw new GameFrameworkException("Name is invalid.");
}
if (name.Length > byte.MaxValue)
{
throw new GameFrameworkException(Utility.Text.Format("Name '{0}' is too long.", name));
}
if (stream == null)
{
throw new GameFrameworkException("Stream is invalid.");
}
if (!stream.CanRead)
{
throw new GameFrameworkException("Stream is not readable.");
}
bool hasFile = false;
int oldBlockIndex = -1;
if (m_FileDatas.TryGetValue(name, out oldBlockIndex))
{
hasFile = true;
}
if (!hasFile && m_FileDatas.Count >= m_HeaderData.MaxFileCount)
{
return false;
}
int length = (int)(stream.Length - stream.Position);
int blockIndex = AllocBlock(length);
if (blockIndex < 0)
{
return false;
}
if (length > 0)
{
m_Stream.Position = GetClusterOffset(m_BlockDatas[blockIndex].ClusterIndex);
m_Stream.Write(stream, length);
}
ProcessWriteFile(name, hasFile, oldBlockIndex, blockIndex, length);
m_Stream.Flush();
return true;
}
/// <summary>
/// 写入指定文件。
/// </summary>
/// <param name="name">要写入的文件名称。</param>
/// <param name="filePath">存储写入文件内容的文件路径。</param>
/// <returns>是否写入指定文件成功。</returns>
public bool WriteFile(string name, string filePath)
{
if (string.IsNullOrEmpty(filePath))
{
throw new GameFrameworkException("File path is invalid");
}
if (!File.Exists(filePath))
{
return false;
}
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
return WriteFile(name, fileStream);
}
}
/// <summary>
/// 将指定文件另存为物理文件。
/// </summary>
/// <param name="name">要另存为的文件名称。</param>
/// <param name="filePath">存储写入文件内容的文件路径。</param>
/// <returns>是否将指定文件另存为物理文件成功。</returns>
public bool SaveAsFile(string name, string filePath)
{
if (m_Access != FileSystemAccess.Read && m_Access != FileSystemAccess.ReadWrite)
{
throw new GameFrameworkException("File system is not readable.");
}
if (string.IsNullOrEmpty(name))
{
throw new GameFrameworkException("Name is invalid.");
}
if (string.IsNullOrEmpty(filePath))
{
throw new GameFrameworkException("File path is invalid");
}
FileInfo fileInfo = GetFileInfo(name);
if (!fileInfo.IsValid)
{
return false;
}
try
{
if (File.Exists(filePath))
{
File.Delete(filePath);
}
string directory = Path.GetDirectoryName(filePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
{
int length = fileInfo.Length;
if (length > 0)
{
m_Stream.Position = fileInfo.Offset;
return m_Stream.Read(fileStream, length) == length;
}
return true;
}
}
catch
{
return false;
}
}
/// <summary>
/// 重命名指定文件。
/// </summary>
/// <param name="oldName">要重命名的文件名称。</param>
/// <param name="newName">重命名后的文件名称。</param>
/// <returns>是否重命名指定文件成功。</returns>
public bool RenameFile(string oldName, string newName)
{
if (m_Access != FileSystemAccess.Write && m_Access != FileSystemAccess.ReadWrite)
{
throw new GameFrameworkException("File system is not writable.");
}
if (string.IsNullOrEmpty(oldName))
{
throw new GameFrameworkException("Old name is invalid.");
}
if (string.IsNullOrEmpty(newName))
{
throw new GameFrameworkException("New name is invalid.");
}
if (newName.Length > byte.MaxValue)
{
throw new GameFrameworkException(Utility.Text.Format("New name '{0}' is too long.", newName));
}
if (oldName == newName)
{
return true;
}
if (m_FileDatas.ContainsKey(newName))
{
return false;
}
int blockIndex = 0;
if (!m_FileDatas.TryGetValue(oldName, out blockIndex))
{
return false;
}
int stringIndex = m_BlockDatas[blockIndex].StringIndex;
StringData stringData = m_StringDatas[stringIndex].SetString(newName, m_HeaderData.GetEncryptBytes());
m_StringDatas[stringIndex] = stringData;
WriteStringData(stringIndex, stringData);
m_FileDatas.Add(newName, blockIndex);
m_FileDatas.Remove(oldName);
m_Stream.Flush();
return true;
}
/// <summary>
/// 删除指定文件。
/// </summary>
/// <param name="name">要删除的文件名称。</param>
/// <returns>是否删除指定文件成功。</returns>
public bool DeleteFile(string name)
{
if (m_Access != FileSystemAccess.Write && m_Access != FileSystemAccess.ReadWrite)
{
throw new GameFrameworkException("File system is not writable.");
}
if (string.IsNullOrEmpty(name))
{
throw new GameFrameworkException("Name is invalid.");
}
int blockIndex = 0;
if (!m_FileDatas.TryGetValue(name, out blockIndex))
{
return false;
}
m_FileDatas.Remove(name);
BlockData blockData = m_BlockDatas[blockIndex];
int stringIndex = blockData.StringIndex;
StringData stringData = m_StringDatas[stringIndex].Clear();
m_FreeStringDatas.Enqueue(new KeyValuePair<int, StringData>(stringIndex, stringData));
m_StringDatas.Remove(stringIndex);
WriteStringData(stringIndex, stringData);
blockData = blockData.Free();
m_BlockDatas[blockIndex] = blockData;
if (!TryCombineFreeBlocks(blockIndex))
{
m_FreeBlockIndexes.Add(blockData.Length, blockIndex);
WriteBlockData(blockIndex);
}
m_Stream.Flush();
return true;
}
private void ProcessWriteFile(string name, bool hasFile, int oldBlockIndex, int blockIndex, int length)
{
BlockData blockData = m_BlockDatas[blockIndex];
if (hasFile)
{
BlockData oldBlockData = m_BlockDatas[oldBlockIndex];
blockData = new BlockData(oldBlockData.StringIndex, blockData.ClusterIndex, length);
m_BlockDatas[blockIndex] = blockData;
WriteBlockData(blockIndex);
oldBlockData = oldBlockData.Free();
m_BlockDatas[oldBlockIndex] = oldBlockData;
if (!TryCombineFreeBlocks(oldBlockIndex))
{
m_FreeBlockIndexes.Add(oldBlockData.Length, oldBlockIndex);
WriteBlockData(oldBlockIndex);
}
}
else
{
int stringIndex = AllocString(name);
blockData = new BlockData(stringIndex, blockData.ClusterIndex, length);
m_BlockDatas[blockIndex] = blockData;
WriteBlockData(blockIndex);
}
if (hasFile)
{
m_FileDatas[name] = blockIndex;
}
else
{
m_FileDatas.Add(name, blockIndex);
}
}
private bool TryCombineFreeBlocks(int freeBlockIndex)
{
BlockData freeBlockData = m_BlockDatas[freeBlockIndex];
if (freeBlockData.Length <= 0)
{
return false;
}
int previousFreeBlockIndex = -1;
int nextFreeBlockIndex = -1;
int nextBlockDataClusterIndex = freeBlockData.ClusterIndex + GetUpBoundClusterCount(freeBlockData.Length);
foreach (KeyValuePair<int, GameFrameworkLinkedListRange<int>> blockIndexes in m_FreeBlockIndexes)
{
if (blockIndexes.Key <= 0)
{
continue;
}
int blockDataClusterCount = GetUpBoundClusterCount(blockIndexes.Key);
foreach (int blockIndex in blockIndexes.Value)
{
BlockData blockData = m_BlockDatas[blockIndex];
if (blockData.ClusterIndex + blockDataClusterCount == freeBlockData.ClusterIndex)
{
previousFreeBlockIndex = blockIndex;
}
else if (blockData.ClusterIndex == nextBlockDataClusterIndex)
{
nextFreeBlockIndex = blockIndex;
}
}
}
if (previousFreeBlockIndex < 0 && nextFreeBlockIndex < 0)
{
return false;
}
m_FreeBlockIndexes.Remove(freeBlockData.Length, freeBlockIndex);
if (previousFreeBlockIndex >= 0)
{
BlockData previousFreeBlockData = m_BlockDatas[previousFreeBlockIndex];
m_FreeBlockIndexes.Remove(previousFreeBlockData.Length, previousFreeBlockIndex);
freeBlockData = new BlockData(previousFreeBlockData.ClusterIndex, previousFreeBlockData.Length + freeBlockData.Length);
m_BlockDatas[previousFreeBlockIndex] = BlockData.Empty;
m_FreeBlockIndexes.Add(0, previousFreeBlockIndex);
WriteBlockData(previousFreeBlockIndex);
}
if (nextFreeBlockIndex >= 0)
{
BlockData nextFreeBlockData = m_BlockDatas[nextFreeBlockIndex];
m_FreeBlockIndexes.Remove(nextFreeBlockData.Length, nextFreeBlockIndex);
freeBlockData = new BlockData(freeBlockData.ClusterIndex, freeBlockData.Length + nextFreeBlockData.Length);
m_BlockDatas[nextFreeBlockIndex] = BlockData.Empty;
m_FreeBlockIndexes.Add(0, nextFreeBlockIndex);
WriteBlockData(nextFreeBlockIndex);
}
m_BlockDatas[freeBlockIndex] = freeBlockData;
m_FreeBlockIndexes.Add(freeBlockData.Length, freeBlockIndex);
WriteBlockData(freeBlockIndex);
return true;
}
private int GetEmptyBlockIndex()
{
GameFrameworkLinkedListRange<int> lengthRange = default(GameFrameworkLinkedListRange<int>);
if (m_FreeBlockIndexes.TryGetValue(0, out lengthRange))
{
int blockIndex = lengthRange.First.Value;
m_FreeBlockIndexes.Remove(0, blockIndex);
return blockIndex;
}
if (m_BlockDatas.Count < m_HeaderData.MaxBlockCount)
{
int blockIndex = m_BlockDatas.Count;
m_BlockDatas.Add(BlockData.Empty);
WriteHeaderData();
return blockIndex;
}
return -1;
}
private int AllocBlock(int length)
{
if (length <= 0)
{
return GetEmptyBlockIndex();
}
length = (int)GetUpBoundClusterOffset(length);
int lengthFound = -1;
GameFrameworkLinkedListRange<int> lengthRange = default(GameFrameworkLinkedListRange<int>);
foreach (KeyValuePair<int, GameFrameworkLinkedListRange<int>> i in m_FreeBlockIndexes)
{
if (i.Key < length)
{
continue;
}
if (lengthFound >= 0 && lengthFound < i.Key)
{
continue;
}
lengthFound = i.Key;
lengthRange = i.Value;
}
if (lengthFound >= 0)
{
if (lengthFound > length && m_BlockDatas.Count >= m_HeaderData.MaxBlockCount)
{
return -1;
}
int blockIndex = lengthRange.First.Value;
m_FreeBlockIndexes.Remove(lengthFound, blockIndex);
if (lengthFound > length)
{
BlockData blockData = m_BlockDatas[blockIndex];
m_BlockDatas[blockIndex] = new BlockData(blockData.ClusterIndex, length);
WriteBlockData(blockIndex);
int deltaLength = lengthFound - length;
int anotherBlockIndex = GetEmptyBlockIndex();
m_BlockDatas[anotherBlockIndex] = new BlockData(blockData.ClusterIndex + GetUpBoundClusterCount(length), deltaLength);
m_FreeBlockIndexes.Add(deltaLength, anotherBlockIndex);
WriteBlockData(anotherBlockIndex);
}
return blockIndex;
}
else
{
int blockIndex = GetEmptyBlockIndex();
if (blockIndex < 0)
{
return -1;
}
long fileLength = m_Stream.Length;
try
{
m_Stream.SetLength(fileLength + length);
}
catch
{
return -1;
}
m_BlockDatas[blockIndex] = new BlockData(GetUpBoundClusterCount(fileLength), length);
WriteBlockData(blockIndex);
return blockIndex;
}
}
private int AllocString(string value)
{
int stringIndex = -1;
StringData stringData = default(StringData);
if (m_FreeStringDatas.Count > 0)
{
KeyValuePair<int, StringData> freeStringData = m_FreeStringDatas.Dequeue();
stringIndex = freeStringData.Key;
stringData = freeStringData.Value;
}
else
{
int index = 0;
foreach (KeyValuePair<int, StringData> i in m_StringDatas)
{
if (i.Key == index)
{
index++;
continue;
}
break;
}
if (index < m_HeaderData.MaxFileCount)
{
stringIndex = index;
byte[] bytes = new byte[byte.MaxValue];
Utility.Random.GetRandomBytes(bytes);
stringData = new StringData(0, bytes);
}
}
if (stringIndex < 0)
{
throw new GameFrameworkException("Alloc string internal error.");
}
stringData = stringData.SetString(value, m_HeaderData.GetEncryptBytes());
m_StringDatas.Add(stringIndex, stringData);
WriteStringData(stringIndex, stringData);
return stringIndex;
}
private void WriteHeaderData()
{
m_HeaderData = m_HeaderData.SetBlockCount(m_BlockDatas.Count);
Utility.Marshal.StructureToBytes(m_HeaderData, HeaderDataSize, s_CachedBytes);
m_Stream.Position = 0L;
m_Stream.Write(s_CachedBytes, 0, HeaderDataSize);
}
private void WriteBlockData(int blockIndex)
{
Utility.Marshal.StructureToBytes(m_BlockDatas[blockIndex], BlockDataSize, s_CachedBytes);
m_Stream.Position = m_BlockDataOffset + BlockDataSize * blockIndex;
m_Stream.Write(s_CachedBytes, 0, BlockDataSize);
}
private StringData ReadStringData(int stringIndex)
{
m_Stream.Position = m_StringDataOffset + StringDataSize * stringIndex;
m_Stream.Read(s_CachedBytes, 0, StringDataSize);
return Utility.Marshal.BytesToStructure<StringData>(StringDataSize, s_CachedBytes);
}
private void WriteStringData(int stringIndex, StringData stringData)
{
Utility.Marshal.StructureToBytes(stringData, StringDataSize, s_CachedBytes);
m_Stream.Position = m_StringDataOffset + StringDataSize * stringIndex;
m_Stream.Write(s_CachedBytes, 0, StringDataSize);
}
private static void CalcOffsets(FileSystem fileSystem)
{
fileSystem.m_BlockDataOffset = HeaderDataSize;
fileSystem.m_StringDataOffset = fileSystem.m_BlockDataOffset + BlockDataSize * fileSystem.m_HeaderData.MaxBlockCount;
fileSystem.m_FileDataOffset = (int)GetUpBoundClusterOffset(fileSystem.m_StringDataOffset + StringDataSize * fileSystem.m_HeaderData.MaxFileCount);
}
private static long GetUpBoundClusterOffset(long offset)
{
return (offset - 1L + ClusterSize) / ClusterSize * ClusterSize;
}
private static int GetUpBoundClusterCount(long length)
{
return (int)((length - 1L + ClusterSize) / ClusterSize);
}
private static long GetClusterOffset(int clusterIndex)
{
return (long)ClusterSize * clusterIndex;
}
}
FileSystem除了实现接口的方法以外还添加了分配释放空间、读写数据,计算偏移等。看完这部分只能理解个大概:在资源构建的时候会把指定的文件统一的写入到指定的文件系统当中(实质上就一个大文件)。在使用的时候,会解析拿到头数据、块数据和文件名(对StarForce项目进行测试,发现字符串指的是文件名)。至于里面的细节不理解的可太多了,举个栗子,计算簇偏移的公式为什么要写成下面这个样子?
private static long GetUpBoundClusterOffset(long offset)
{
return (offset - 1L + ClusterSize) / ClusterSize * ClusterSize;
}
private static int GetUpBoundClusterCount(long length)
{
return (int)((length - 1L + ClusterSize) / ClusterSize);
}
FileSystemManager则是来管理所有的Filesytem。其中的IFileSystemHelper只有一个创建CreateFileSystemStream的方法。加载文件系统时先创建文件系统流,然后通过文件系统流来读取文件系统,再把读取到的文件系统暂存到字典当中。
internal sealed class FileSystemManager : GameFrameworkModule, IFileSystemManager
{
private readonly Dictionary<string, FileSystem> m_FileSystems;
private IFileSystemHelper m_FileSystemHelper;
/// <summary>
/// 初始化文件系统管理器的新实例。
/// </summary>
public FileSystemManager()
{
m_FileSystems = new Dictionary<string, FileSystem>(StringComparer.Ordinal);
m_FileSystemHelper = null;
}
/// <summary>
/// 获取文件系统数量。
/// </summary>
public int Count
{
get
{
return m_FileSystems.Count;
}
}
/// <summary>
/// 文件系统管理器轮询。
/// </summary>
/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
internal override void Update(float elapseSeconds, float realElapseSeconds)
{
}
/// <summary>
/// 关闭并清理文件系统管理器。
/// </summary>
internal override void Shutdown()
{
while (m_FileSystems.Count > 0)
{
foreach (KeyValuePair<string, FileSystem> fileSystem in m_FileSystems)
{
DestroyFileSystem(fileSystem.Value, false);
break;
}
}
}
/// <summary>
/// 设置文件系统辅助器。
/// </summary>
/// <param name="fileSystemHelper">文件系统辅助器。</param>
public void SetFileSystemHelper(IFileSystemHelper fileSystemHelper)
{
if (fileSystemHelper == null)
{
throw new GameFrameworkException("File system helper is invalid.");
}
m_FileSystemHelper = fileSystemHelper;
}
/// <summary>
/// 检查是否存在文件系统。
/// </summary>
/// <param name="fullPath">要检查的文件系统的完整路径。</param>
/// <returns>是否存在文件系统。</returns>
public bool HasFileSystem(string fullPath)
{
if (string.IsNullOrEmpty(fullPath))
{
throw new GameFrameworkException("Full path is invalid.");
}
return m_FileSystems.ContainsKey(Utility.Path.GetRegularPath(fullPath));
}
/// <summary>
/// 获取文件系统。
/// </summary>
/// <param name="fullPath">要获取的文件系统的完整路径。</param>
/// <returns>获取的文件系统。</returns>
public IFileSystem GetFileSystem(string fullPath)
{
if (string.IsNullOrEmpty(fullPath))
{
throw new GameFrameworkException("Full path is invalid.");
}
FileSystem fileSystem = null;
if (m_FileSystems.TryGetValue(Utility.Path.GetRegularPath(fullPath), out fileSystem))
{
return fileSystem;
}
return null;
}
/// <summary>
/// 创建文件系统。
/// </summary>
/// <param name="fullPath">要创建的文件系统的完整路径。</param>
/// <param name="access">要创建的文件系统的访问方式。</param>
/// <param name="maxFileCount">要创建的文件系统的最大文件数量。</param>
/// <param name="maxBlockCount">要创建的文件系统的最大块数据数量。</param>
/// <returns>创建的文件系统。</returns>
public IFileSystem CreateFileSystem(string fullPath, FileSystemAccess access, int maxFileCount, int maxBlockCount)
{
if (m_FileSystemHelper == null)
{
throw new GameFrameworkException("File system helper is invalid.");
}
if (string.IsNullOrEmpty(fullPath))
{
throw new GameFrameworkException("Full path is invalid.");
}
if (access == FileSystemAccess.Unspecified)
{
throw new GameFrameworkException("Access is invalid.");
}
if (access == FileSystemAccess.Read)
{
throw new GameFrameworkException("Access read is invalid.");
}
fullPath = Utility.Path.GetRegularPath(fullPath);
if (m_FileSystems.ContainsKey(fullPath))
{
throw new GameFrameworkException(Utility.Text.Format("File system '{0}' is already exist.", fullPath));
}
FileSystemStream fileSystemStream = m_FileSystemHelper.CreateFileSystemStream(fullPath, access, true);
if (fileSystemStream == null)
{
throw new GameFrameworkException(Utility.Text.Format("Create file system stream for '{0}' failure.", fullPath));
}
FileSystem fileSystem = FileSystem.Create(fullPath, access, fileSystemStream, maxFileCount, maxBlockCount);
if (fileSystem == null)
{
throw new GameFrameworkException(Utility.Text.Format("Create file system '{0}' failure.", fullPath));
}
m_FileSystems.Add(fullPath, fileSystem);
return fileSystem;
}
/// <summary>
/// 加载文件系统。
/// </summary>
/// <param name="fullPath">要加载的文件系统的完整路径。</param>
/// <param name="access">要加载的文件系统的访问方式。</param>
/// <returns>加载的文件系统。</returns>
public IFileSystem LoadFileSystem(string fullPath, FileSystemAccess access)
{
if (m_FileSystemHelper == null)
{
throw new GameFrameworkException("File system helper is invalid.");
}
if (string.IsNullOrEmpty(fullPath))
{
throw new GameFrameworkException("Full path is invalid.");
}
if (access == FileSystemAccess.Unspecified)
{
throw new GameFrameworkException("Access is invalid.");
}
fullPath = Utility.Path.GetRegularPath(fullPath);
if (m_FileSystems.ContainsKey(fullPath))
{
throw new GameFrameworkException(Utility.Text.Format("File system '{0}' is already exist.", fullPath));
}
FileSystemStream fileSystemStream = m_FileSystemHelper.CreateFileSystemStream(fullPath, access, false);
if (fileSystemStream == null)
{
throw new GameFrameworkException(Utility.Text.Format("Create file system stream for '{0}' failure.", fullPath));
}
FileSystem fileSystem = FileSystem.Load(fullPath, access, fileSystemStream);
if (fileSystem == null)
{
throw new GameFrameworkException(Utility.Text.Format("Load file system '{0}' failure.", fullPath));
}
m_FileSystems.Add(fullPath, fileSystem);
return fileSystem;
}
/// <summary>
/// 销毁文件系统。
/// </summary>
/// <param name="fileSystem">要销毁的文件系统。</param>
/// <param name="deletePhysicalFile">是否删除文件系统对应的物理文件。</param>
public void DestroyFileSystem(IFileSystem fileSystem, bool deletePhysicalFile)
{
if (fileSystem == null)
{
throw new GameFrameworkException("File system is invalid.");
}
string fullPath = fileSystem.FullPath;
((FileSystem)fileSystem).Shutdown();
m_FileSystems.Remove(fullPath);
if (deletePhysicalFile && File.Exists(fullPath))
{
File.Delete(fullPath);
}
}
/// <summary>
/// 获取所有文件系统集合。
/// </summary>
/// <returns>获取的所有文件系统集合。</returns>
public IFileSystem[] GetAllFileSystems()
{
int index = 0;
IFileSystem[] results = new IFileSystem[m_FileSystems.Count];
foreach (KeyValuePair<string, FileSystem> fileSystem in m_FileSystems)
{
results[index++] = fileSystem.Value;
}
return results;
}
/// <summary>
/// 获取所有文件系统集合。
/// </summary>
/// <param name="results">获取的所有文件系统集合。</param>
public void GetAllFileSystems(List<IFileSystem> results)
{
if (results == null)
{
throw new GameFrameworkException("Results is invalid.");
}
results.Clear();
foreach (KeyValuePair<string, FileSystem> fileSystem in m_FileSystems)
{
results.Add(fileSystem.Value);
}
}
}
二、事件
/// <summary>
/// 游戏框架中包含事件数据的类的基类。
/// </summary>
public abstract class GameFrameworkEventArgs : EventArgs, IReference
{
/// <summary>
/// 初始化游戏框架中包含事件数据的类的新实例。
/// </summary>
public GameFrameworkEventArgs()
{
}
/// <summary>
/// 清理引用。
/// </summary>
public abstract void Clear();
}
事件的基类GameFrameworkEventArgs继承了EventArgs, IReference,这样就能在作为事件的同时还能被引用池管理。后面会发现事件的获取都是通过资源池的acquire方法拿到的。GameFrameworkEventArgs又派生出了BaseEventArgs以及各种具体事件类。BaseEventArgs又派生出了GameEventArgs(游戏逻辑的事件基类) 和 Packet(网消息包的基类)
虽然EventManager目前并没有用到,但是内容也并不多,大概看一下。
/// <summary>
/// 事件管理器。
/// </summary>
internal sealed class EventManager : GameFrameworkModule, IEventManager
{
private readonly EventPool<GameEventArgs> m_EventPool;
/// <summary>
/// 初始化事件管理器的新实例。
/// </summary>
public EventManager()
{
m_EventPool = new EventPool<GameEventArgs>(EventPoolMode.AllowNoHandler | EventPoolMode.AllowMultiHandler);
}
/// <summary>
/// 获取事件处理函数的数量。
/// </summary>
public int EventHandlerCount
{
get
{
return m_EventPool.EventHandlerCount;
}
}
/// <summary>
/// 获取事件数量。
/// </summary>
public int EventCount
{
get
{
return m_EventPool.EventCount;
}
}
/// <summary>
/// 获取游戏框架模块优先级。
/// </summary>
/// <remarks>优先级较高的模块会优先轮询,并且关闭操作会后进行。</remarks>
internal override int Priority
{
get
{
return 100;
}
}
/// <summary>
/// 事件管理器轮询。
/// </summary>
/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
internal override void Update(float elapseSeconds, float realElapseSeconds)
{
m_EventPool.Update(elapseSeconds, realElapseSeconds);
}
/// <summary>
/// 关闭并清理事件管理器。
/// </summary>
internal override void Shutdown()
{
m_EventPool.Shutdown();
}
/// <summary>
/// 获取事件处理函数的数量。
/// </summary>
/// <param name="id">事件类型编号。</param>
/// <returns>事件处理函数的数量。</returns>
public int Count(int id)
{
return m_EventPool.Count(id);
}
/// <summary>
/// 检查是否存在事件处理函数。
/// </summary>
/// <param name="id">事件类型编号。</param>
/// <param name="handler">要检查的事件处理函数。</param>
/// <returns>是否存在事件处理函数。</returns>
public bool Check(int id, EventHandler<GameEventArgs> handler)
{
return m_EventPool.Check(id, handler);
}
/// <summary>
/// 订阅事件处理函数。
/// </summary>
/// <param name="id">事件类型编号。</param>
/// <param name="handler">要订阅的事件处理函数。</param>
public void Subscribe(int id, EventHandler<GameEventArgs> handler)
{
m_EventPool.Subscribe(id, handler);
}
/// <summary>
/// 取消订阅事件处理函数。
/// </summary>
/// <param name="id">事件类型编号。</param>
/// <param name="handler">要取消订阅的事件处理函数。</param>
public void Unsubscribe(int id, EventHandler<GameEventArgs> handler)
{
m_EventPool.Unsubscribe(id, handler);
}
/// <summary>
/// 设置默认事件处理函数。
/// </summary>
/// <param name="handler">要设置的默认事件处理函数。</param>
public void SetDefaultHandler(EventHandler<GameEventArgs> handler)
{
m_EventPool.SetDefaultHandler(handler);
}
/// <summary>
/// 抛出事件,这个操作是线程安全的,即使不在主线程中抛出,也可保证在主线程中回调事件处理函数,但事件会在抛出后的下一帧分发。
/// </summary>
/// <param name="sender">事件源。</param>
/// <param name="e">事件参数。</param>
public void Fire(object sender, GameEventArgs e)
{
m_EventPool.Fire(sender, e);
}
/// <summary>
/// 抛出事件立即模式,这个操作不是线程安全的,事件会立刻分发。
/// </summary>
/// <param name="sender">事件源。</param>
/// <param name="e">事件参数。</param>
public void FireNow(object sender, GameEventArgs e)
{
m_EventPool.FireNow(sender, e);
}
}
可以看到EventManager基本上都是在对EventPool进行操作,实际上可以说EventManager就是EventPool暴露出来的接口。
/// <summary>
/// 事件池。
/// </summary>
/// <typeparam name="T">事件类型。</typeparam>
internal sealed partial class EventPool<T> where T : BaseEventArgs
{
private readonly GameFrameworkMultiDictionary<int, EventHandler<T>> m_EventHandlers;
private readonly Queue<Event> m_Events;
private readonly Dictionary<object, LinkedListNode<EventHandler<T>>> m_CachedNodes;
private readonly Dictionary<object, LinkedListNode<EventHandler<T>>> m_TempNodes;
private readonly EventPoolMode m_EventPoolMode;
private EventHandler<T> m_DefaultHandler;
/// <summary>
/// 初始化事件池的新实例。
/// </summary>
/// <param name="mode">事件池模式。</param>
public EventPool(EventPoolMode mode)
{
m_EventHandlers = new GameFrameworkMultiDictionary<int, EventHandler<T>>();
m_Events = new Queue<Event>();
m_CachedNodes = new Dictionary<object, LinkedListNode<EventHandler<T>>>();
m_TempNodes = new Dictionary<object, LinkedListNode<EventHandler<T>>>();
m_EventPoolMode = mode;
m_DefaultHandler = null;
}
/// <summary>
/// 获取事件处理函数的数量。
/// </summary>
public int EventHandlerCount
{
get { return m_EventHandlers.Count; }
}
/// <summary>
/// 获取事件数量。
/// </summary>
public int EventCount
{
get { return m_Events.Count; }
}
/// <summary>
/// 事件池轮询。
/// </summary>
/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
public void Update(float elapseSeconds, float realElapseSeconds)
{
lock (m_Events)
{
while (m_Events.Count > 0)
{
Event eventNode = null;
eventNode = m_Events.Dequeue();
HandleEvent(eventNode.Sender, eventNode.EventArgs);
ReferencePool.Release(eventNode);
}
}
}
/// <summary>
/// 关闭并清理事件池。
/// </summary>
public void Shutdown()
{
Clear();
m_EventHandlers.Clear();
m_CachedNodes.Clear();
m_TempNodes.Clear();
m_DefaultHandler = null;
}
/// <summary>
/// 清理事件。
/// </summary>
public void Clear()
{
lock (m_Events)
{
m_Events.Clear();
}
}
/// <summary>
/// 获取事件处理函数的数量。
/// </summary>
/// <param name="id">事件类型编号。</param>
/// <returns>事件处理函数的数量。</returns>
public int Count(int id)
{
GameFrameworkLinkedListRange<EventHandler<T>>
range = default(GameFrameworkLinkedListRange<EventHandler<T>>);
if (m_EventHandlers.TryGetValue(id, out range))
{
return range.Count;
}
return 0;
}
/// <summary>
/// 检查是否存在事件处理函数。
/// </summary>
/// <param name="id">事件类型编号。</param>
/// <param name="handler">要检查的事件处理函数。</param>
/// <returns>是否存在事件处理函数。</returns>
public bool Check(int id, EventHandler<T> handler)
{
if (handler == null)
{
throw new GameFrameworkException("Event handler is invalid.");
}
return m_EventHandlers.Contains(id, handler);
}
/// <summary>
/// 订阅事件处理函数。
/// </summary>
/// <param name="id">事件类型编号。</param>
/// <param name="handler">要订阅的事件处理函数。</param>
public void Subscribe(int id, EventHandler<T> handler)
{
if (handler == null)
{
throw new GameFrameworkException("Event handler is invalid.");
}
if (!m_EventHandlers.Contains(id))
{
m_EventHandlers.Add(id, handler);
}
else if ((m_EventPoolMode & EventPoolMode.AllowMultiHandler) != EventPoolMode.AllowMultiHandler)
{
throw new GameFrameworkException(Utility.Text.Format("Event '{0}' not allow multi handler.",
id.ToString()));
}
else if ((m_EventPoolMode & EventPoolMode.AllowDuplicateHandler) != EventPoolMode.AllowDuplicateHandler &&
Check(id, handler))
{
throw new GameFrameworkException(Utility.Text.Format("Event '{0}' not allow duplicate handler.",
id.ToString()));
}
else
{
m_EventHandlers.Add(id, handler);
}
}
/// <summary>
/// 取消订阅事件处理函数。
/// </summary>
/// <param name="id">事件类型编号。</param>
/// <param name="handler">要取消订阅的事件处理函数。</param>
public void Unsubscribe(int id, EventHandler<T> handler)
{
if (handler == null)
{
throw new GameFrameworkException("Event handler is invalid.");
}
if (m_CachedNodes.Count > 0)
{
foreach (KeyValuePair<object, LinkedListNode<EventHandler<T>>> cachedNode in m_CachedNodes)
{
if (cachedNode.Value != null && cachedNode.Value.Value == handler)
{
m_TempNodes.Add(cachedNode.Key, cachedNode.Value.Next);
}
}
if (m_TempNodes.Count > 0)
{
foreach (KeyValuePair<object, LinkedListNode<EventHandler<T>>> cachedNode in m_TempNodes)
{
m_CachedNodes[cachedNode.Key] = cachedNode.Value;
}
m_TempNodes.Clear();
}
}
if (!m_EventHandlers.Remove(id, handler))
{
throw new GameFrameworkException(Utility.Text.Format("Event '{0}' not exists specified handler.",
id.ToString()));
}
}
/// <summary>
/// 设置默认事件处理函数。
/// </summary>
/// <param name="handler">要设置的默认事件处理函数。</param>
public void SetDefaultHandler(EventHandler<T> handler)
{
m_DefaultHandler = handler;
}
/// <summary>
/// 抛出事件,这个操作是线程安全的,即使不在主线程中抛出,也可保证在主线程中回调事件处理函数,但事件会在抛出后的下一帧分发。
/// </summary>
/// <param name="sender">事件源。</param>
/// <param name="e">事件参数。</param>
public void Fire(object sender, T e)
{
if (e == null)
{
throw new GameFrameworkException("Event is invalid.");
}
Event eventNode = Event.Create(sender, e);
lock (m_Events)
{
m_Events.Enqueue(eventNode);
}
}
/// <summary>
/// 抛出事件立即模式,这个操作不是线程安全的,事件会立刻分发。
/// </summary>
/// <param name="sender">事件源。</param>
/// <param name="e">事件参数。</param>
public void FireNow(object sender, T e)
{
if (e == null)
{
throw new GameFrameworkException("Event is invalid.");
}
HandleEvent(sender, e);
}
/// <summary>
/// 处理事件结点。
/// </summary>
/// <param name="sender">事件源。</param>
/// <param name="e">事件参数。</param>
private void HandleEvent(object sender, T e)
{
bool noHandlerException = false;
GameFrameworkLinkedListRange<EventHandler<T>>
range = default(GameFrameworkLinkedListRange<EventHandler<T>>);
if (m_EventHandlers.TryGetValue(e.Id, out range))
{
LinkedListNode<EventHandler<T>> current = range.First;
while (current != null && current != range.Terminal)
{
m_CachedNodes[e] = current.Next != range.Terminal ? current.Next : null;
current.Value(sender, e);
current = m_CachedNodes[e];
}
m_CachedNodes.Remove(e);
}
else if (m_DefaultHandler != null)
{
m_DefaultHandler(sender, e);
}
else if ((m_EventPoolMode & EventPoolMode.AllowNoHandler) == 0)
{
noHandlerException = true;
}
ReferencePool.Release(e);
if (noHandlerException)
{
throw new GameFrameworkException(Utility.Text.Format("Event '{0}' not allow no handler.",
e.Id.ToString()));
}
}
}
在事件池中首先看到的是一个多值字典,用来存储事件码和对应的所有处理方法。然后是一个存储事件的队列以及两个字典m_CachedNodes和m_TempNodes。这两个字典的用法或者说Unsubscribe()方法着实有点奇怪。首先可以发现这两个字典只在Unsubscribe() 和EventHandler() 中用到。在EventHandler()中很好理解,就是遍历执行了一下指定事件码对应的订阅方法,并且在遍历完以后,还把指定的事件码从m_CachedNodes中移除掉了。然后,在Unsubscribe()中又对m_CachedNodes中是否存在数据做了判定。然而正常情况下,m_CachedNodes中是不会存在数据的。所以作者为什么会这么写呢?一开始想到会不会是多线程的问题,然后想到怎么可能会这么巧,恰好在某一个线程处理到这个事件的时候,另一个线程刚好要取消这个订阅。这个想法是不对的。然后找到了一个觉得还算靠谱的解释,总结就是:在前置订阅的方法中取消后置订阅。这种写法还没见到过,所以就没想到。。。除了这点以外,别的地方都还好理解。
还有个地方要提一下,就是下面这个枚举值的比较。
else if ((m_EventPoolMode & EventPoolMode.AllowMultiHandler) != EventPoolMode.AllowMultiHandler)
这个写法涉及到一个特性FlagsAttribute(官方文档)。加上这个特性,枚举值就会变成2的n次幂1,2,4,8…而不是默认的0,1,2,3… 还有,如果给定一个整数,C#就会把这个数字解析成若干个枚举值的组合而不是单个枚举值,可以参照官方文档里的具体例子理解。
/// <summary>
/// 事件池模式。
/// </summary>
[Flags]
internal enum EventPoolMode : byte
{
/// <summary>
/// 默认事件池模式,即必须存在有且只有一个事件处理函数。
/// </summary>
Default = 0,
/// <summary>
/// 允许不存在事件处理函数。
/// </summary>
AllowNoHandler = 1,
/// <summary>
/// 允许存在多个事件处理函数。
/// </summary>
AllowMultiHandler = 2,
/// <summary>
/// 允许存在重复的事件处理函数。
/// </summary>
AllowDuplicateHandler = 4
}
PS:在脚本中经常会看到作者把几个相同事件的不同结果的回调写在一起,比如说:
public sealed class LoadBytesCallbacks
{
private readonly LoadBytesSuccessCallback m_LoadBytesSuccessCallback;
private readonly LoadBytesFailureCallback m_LoadBytesFailureCallback;
/// <summary>
/// 初始化加载数据流回调函数集的新实例。
/// </summary>
/// <param name="loadBinarySuccessCallback">加载数据流成功回调函数。</param>
public LoadBytesCallbacks(LoadBytesSuccessCallback loadBinarySuccessCallback)
: this(loadBinarySuccessCallback, null)
{
}
/// <summary>
/// 初始化加载数据流回调函数集的新实例。
/// </summary>
/// <param name="loadBytesSuccessCallback">加载数据流成功回调函数。</param>
/// <param name="loadBytesFailureCallback">加载数据流失败回调函数。</param>
public LoadBytesCallbacks(LoadBytesSuccessCallback loadBytesSuccessCallback,
LoadBytesFailureCallback loadBytesFailureCallback)
{
if (loadBytesSuccessCallback == null)
{
throw new GameFrameworkException("Load bytes success callback is invalid.");
}
m_LoadBytesSuccessCallback = loadBytesSuccessCallback;
m_LoadBytesFailureCallback = loadBytesFailureCallback;
}
//...
}
这样就相当于对一个操作做了层封装。同时也学到了this的一种用法:当一个类有多个构造方法时,少参的构造函数可以通过":"+"this"来调用多参构造方法。
public LoadAssetCallbacks(LoadAssetSuccessCallback loadAssetSuccessCallback)
: this(loadAssetSuccessCallback, null, null, null)
{
}
/// <summary>
/// 初始化加载资源回调函数集的新实例。
/// </summary>
/// <param name="loadAssetSuccessCallback">加载资源成功回调函数。</param>
/// <param name="loadAssetFailureCallback">加载资源失败回调函数。</param>
/// <param name="loadAssetUpdateCallback">加载资源更新回调函数。</param>
/// <param name="loadAssetDependencyAssetCallback">加载资源时加载依赖资源回调函数。</param>
public LoadAssetCallbacks(LoadAssetSuccessCallback loadAssetSuccessCallback,
LoadAssetFailureCallback loadAssetFailureCallback, LoadAssetUpdateCallback loadAssetUpdateCallback,
LoadAssetDependencyAssetCallback loadAssetDependencyAssetCallback)
{
if (loadAssetSuccessCallback == null)
{
throw new GameFrameworkException("Load asset success callback is invalid.");
}
m_LoadAssetSuccessCallback = loadAssetSuccessCallback;
m_LoadAssetFailureCallback = loadAssetFailureCallback;
m_LoadAssetUpdateCallback = loadAssetUpdateCallback;
m_LoadAssetDependencyAssetCallback = loadAssetDependencyAssetCallback;
}