これは、かなり前に見つけたC ++でのVFS(仮想ファイルシステム)の記述に関する記事の最初の部分の翻訳です。 楽しんでいただければ幸いです。 :)
エントリー
3Dエンジンの開発を始めたとき、ファイルシステムのようなものが必要であることに気付きました。 単純なアーカイブではなく、圧縮、暗号化をサポートする独自の仮想ファイルシステムであり、アクセス時間が高速です。
そして、私はあなたが車輪を再発明する必要がないように私のベストプラクティスをレイアウトすることにしました。 この記事は2つのパートに分かれています。 1つ目は現在読んでいるもので、VFSの構造についてはここで説明します。 2番目の部分では、実際にVFS自体を記述します(非常に大きくなります)。
VFSとは何ですか?
VFSは、Windowsのファイルシステム(fat32、ntfsなど)と同様のファイルシステムです。 VFSと実際のファイルシステムの主な違いは、VFSがその内部で実際のファイルシステムを使用することです。
機能性
VFSのいくつかの機能は次のとおりです。
クイックアクセスタイム
膨大な数の小さなファイルではなく、いくつかのアーカイブ
デバッグ機能
プラグ可能な暗号化と圧縮(PEC)
セキュリティ(vfsファイルの内部にはMD5キーが格納されているため、アーカイブへの変更はすぐに通知されます)
いくつかのルートパス
これで、機能のリストがリストされたので、設計段階に進むことができます
基本設計
始めましょう:16個の関数を含むメインインターフェイスがあります。 最初にこれらの関数を紹介し、次にそれらの目的を説明し、次のパートでそれらを作成します。
#define VFS_VERSION 0x0100<br/>
#define VFS_PATH_SEPARATOR '\\'<br/>
<br/>
void VFS_Init();<br/>
void VFS_Shutdown();<br/>
これらの関数は実際には何もしませんが、後で必要になるいくつかの構造を開始/アンロードします
フィルター
typedef BOOL (* VFS_FilterProc )( LPCBYTE pIn, DWORD dwInCount, LPBYTE* ppOut, DWORD* pOutCount );<br/>
<br/>
struct VFS_Filter<br/>
{<br/>
string strName;<br/>
string strDescription;<br/>
VFS_FilterProc pfnEncodeProc;<br/>
VFS_FilterProc pfnDecodeProc;<br/>
};<br/>
<br/>
void VFS_RegisterFilter( VFS_Filter* pFilter );<br/>
void VFS_UnregisterFilter( VFS_Filter* pFilter );<br/>
void VFS_UnregisterFilter( DWORD dwIndex );<br/>
DWORD VFS_GetNumFilters();<br/>
const VFS_Filter* VFS_GetFilter( DWORD dwIndex );<br/>
まあ、それはすでに少し複雑です。 VFS_Filterは、データを処理する一種のルーチンです。 たとえば、データを暗号化/復号化するVFS_CryptFilterを記述できます。 ありますか? フィルターは、アーカイブに書き込まれるデータ用の予備プロセッサー(pfnEncodeProcプロシージャー)と、アーカイブからデータを読み取るためのポストプロセッサー(pfnDecodeProcプロシージャー)のようなもので構成されます。 これらのフィルターは、上記のプラグ可能な暗号化と圧縮を実装しているため、使用する各vfsファイルに1つ以上のフィルターを割り当てることができます。 少し混乱している場合は、フィルター図である図1をご覧ください。
ご覧のとおり、エンコード/デコード手順は、アーカイブからメモリへ、およびメモリからアーカイブへの両方向のデータフローを操作します。
フィルターの理解を深めるために、単純なフィルターを作成しましょう(いずれにしても、後でVFSを実装するため、フィルターをチェックできないことに注意してください)。 フィルターは各バイトに1を追加します。
VFS_Filter ONEADD_Filter =<br/>
{<br/>
"ONEADD",<br/>
"This Filter adds 1 to each Byte of the Data. It doesn't really make sense, "<br/>
" but anyway, this is just a test, you know",<br/>
ONEADD_EncodeProc,<br/>
ONEADD_DecodeProc<br/>
};<br/>
<br/>
BOOL ONEADD_EncodeProc( LPCBYTE pIn, DWORD dwInCount, LPBYTE* ppOut, DWORD* pOutCount )<br/>
{<br/>
assert( ppOut );<br/>
assert( pOutCount );<br/>
<br/>
// Allocate the Memory.<br/>
*ppOut = new BYTE[ dwInCount ];<br/>
<br/>
// Perform a For-Loop through each Byte.<br/>
for( DWORD dwIndex = 0; dwIndex < dwInCount; dwIndex++ )<br/>
{<br/>
( *ppOut )[ dwIndex ] = ( BYTE )( pIn[ dwIndex ] + 1 );<br/>
}<br/>
<br/>
// Set the Output Count.<br/>
*pOutCount = dwInCount;<br/>
}<br/>
<br/>
BOOL ONEADD_DecodeProc( LPCBYTE pIn, DWORD dwInCount, LPBYTE* ppOut, DWORD* pOutCount )<br/>
{<br/>
assert( ppOut );<br/>
assert( pOutCount );<br/>
<br/>
// Allocate the Memory.<br/>
*ppOut = new BYTE[ dwInCount ];<br/>
<br/>
// Perform a For-Loop through each Byte.<br/>
for( DWORD dwIndex = 0; dwIndex < dwInCount; dwIndex++ )<br/>
{<br/>
( *ppOut )[ dwIndex ] = ( BYTE )( pIn[ dwIndex ] - 1 );<br/>
}<br/>
<br/>
// Set the Output Count.<br/>
*pOutCount = dwInCount;<br/>
}<br/>
このフィルターの唯一のポイントは、vfsファイルを開くことがより困難になる可能性があることです。
ルートパス関数
ルートパス関数については、少し前に説明しましたが、覚えていますか? そうでなければ、問題ありません。 関数に複数のルートパス、つまり、プログラムインストールディレクトリ、光ディスク、ネットワークドライブなどの複数の検索パスを使用するようにしたいと言いました。 これを実現するために、次の機能が使用されます。
void VFS_AddRootPath( LPCTSTR pszRootPath );<br/>
void VFS_RemoveRootPath( LPCTSTR pszRootPath );<br/>
void VFS_RemoveRootPath( DWORD dwIndex );<br/>
DWORD VFS_GetNumRootPaths();<br/>
LPCTSTR VFS_GetRootPath( DWORD dwIndex );
すべてが簡単ですよね?
いくつかのシンプルだが必要なもの
次の4つの関数は非常に簡単です。
void VFS_Flush();
この関数は、アクセスされていないすべての開いているvfsファイルを閉じます。 アクセスされていないvfsファイルがなぜ自動的に閉じないのか疑問に思うかもしれませんが、その場合、ファイルを開いたり閉じたりするたびにvfsファイルを再分析する必要がありました。 詳細については、このコードをご覧ください。
// Reference Count is 1. -> Load + Parse!!!<br/>
DWORD dwHandle = VFS_File_Open( "Bla\\Bla.Txt" ); <br/>
<br/>
// Reference Count is 0. -> Close!!!<br/>
VFS_File_Close( dwHandle ); <br/>
<br/>
// Reference Count is 1. -> Load + Parse!!!<br/>
dwHandle = VFS_File_Open( "Bla\\Bla.Txt" );<br/>
<br/>
// Reference Count is 0. -> Close!!!<br/>
VFS_File_Close( dwHandle );<br/>
ご覧のとおり、ファイルアーカイブを2回開く必要があります。 ゲームでVFS_Flush()を呼び出すのに適した場所は、すべてのレベルのデータが読み込まれたときです。 ただし、最後の3つの主な機能は次のとおりです。
struct VFS_EntityInfo<br/>
{<br/>
BOOL bIsDir; // Is the Entity a Directory.<br/>
BOOL bArchived; // True if the Entity is located in an Archive.<br/>
string strDir; // like Models/Sarge/Textures<br/>
string strPath; // like Models/Sarge/Textures/Texture1.Jpg<br/>
string strName; // like Texture1.Jpg<br/>
DWORD dwSize; // The Number of Files and Subdirectories for a<br/>
Directory.<br/>
};<br/>
<br/>
BOOL VFS_Exists( LPCTSTR pszPath );<br/>
void VFS_GetEntityInfo( LPCTSTR pszPath, VFS_EntityInfo* pInfo );<br/>
DWORD VFS_GetVersion();<br/>
現在、最初の機能は、pszPathパスを持つオブジェクトが存在するかどうかを確認しています。 これはかなり単純なことですが、C(++)では標準ライブラリにそのような関数は含まれていません(stat()のような関数があることは知っていますが、exists()のようなものが欲しいです)。 2番目の関数はstat()のようなもので、オブジェクトに関する情報を返します
最後の関数はオブジェクト情報とは関係ありません;この関数は単にVFSの現在のバージョンを返します。 特別なことは何もありません(真剣に、VFS_VERSION定数を返すだけです;-)
ファイルインターフェース
単純なものを見ました。 しかし、心配しないでください。まだいくつか簡単なことがあります。 実際、記事のこの部分で説明されていることはすべて簡単です。 残念ながら、もっと複雑なものが必要な場合は、この記事の次の部分を待つ必要があります... ;-)
さて、ここに、ファイルインターフェイス関数があります。
#define VFS_INVALID_HANDLE ( ( DWORD ) -1 )<br/>
<br/>
// The VFS_File_Open/Create() Flags.<br/>
#define VFS_READ 0x01<br/>
#define VFS_WRITE 0x02<br/>
<br/>
// The VFS_File_Seek() Flags.<br/>
#define VFS_SET 0x00<br/>
#define VFS_CURRENT 0x01<br/>
#define VFS_END 0x02<br/>
<br/>
// Create / Open / Close a File.<br/>
DWORD VFS_File_Create( LPCTSTR pszFile, DWORD dwFlags );<br/>
DWORD VFS_File_Open( LPCTSTR pszFile, DWORD dwFlags );<br/>
void VFS_File_Close( DWORD dwHandle );<br/>
<br/>
// Read / Write from / to the File.<br/>
void VFS_File_Read( DWORD dwHandle, LPBYTE pBuffer, DWORD dwToRead, DWORD* pRead = NULL );<br/>
void VFS_File_Write( DWORD dwHandle, LPCBYTE pBuffer, DWORD dwToWrite, DWORD* pWritten = NULL );<br/>
<br/>
// Direct Data Access.<br/>
LPCBYTE VFS_File_GetData( DWORD dwHandle );<br/>
<br/>
// Positioning.<br/>
void VFS_File_Seek( DWORD dwHandle, LONG dwPos, DWORD dwOrigin = VFS_SET );<br/>
LONG VFS_File_Tell( DWORD dwHandle );<br/>
DWORD VFS_File_GetSize( DWORD dwHandle );<br/>
<br/>
// Information.<br/>
BOOL VFS_File_Exists( LPCTSTR pszFile );<br/>
void VFS_File_GetInfo( LPCTSTR pszFile, VFS_EntityInfo* pInfo );<br/>
void VFS_File_GetInfo( DWORD dwHandle, VFS_EntityInfo* pInfo );<br/>
注目に値することがいくつかあります。 まず、VFS_File_Create()およびVFS_File_Open()のdwFlagsパラメーターは、VFS_READまたはVFS_WRITEのいずれか、または両方を同時に使用できます。つまり、読み取り、書き込み、または読み取り/書き込みアクセスを意味します。 第二に、これらの2つの関数はハンドルを返します。これは、他のほとんどすべての関数が一種のポインターとして使用します。 ポインターは使用しませんが、もう1つの抽象化レベルを提供するため、ハンドルを使用します。 また、関数がファイル全体をメモリにロードするという事実に言及したいと思います。 これは、フィルターの特性のために必要です(処理にはメモリが必要なため)。 VFS_File_GetData()でこのメモリに直接アクセスできます。 さて、標準I / Oライブラリのおかげで、あなたが知っておくべき事柄の残りの部分。
ライブラリのインターフェース
これは、いくつかの行から始めて、あなたが期待していた場所かもしれませんし、ページを言う方が良いでしょう(そして、私たちが期待について話しているとき:私が期待していなかったのは、これが既に7ページ程度であるという事実です.WOW!)
とにかく、続けましょう:
// Create / Open / Close an Archive.<br/>
DWORD VFS_Archive_Create( LPCTSTR pszArchive, const VFS_FilterNameList& Filters, DWORD dwFlags );<br/>
DWORD VFS_Archive_CreateFromDirectory( LPCTSTR pszArchive, LPCTSTR pszSrcDir,<br/>
const VFS_FilterNameList& Filters, DWORD dwFlags );<br/>
DWORD VFS_Archive_Open( LPCTSTR pszArchive, DWORD dwFlags );<br/>
void VFS_Archive_Close( DWORD dwHandle );<br/>
<br/>
// Set the Filters used by this Archive.<br/>
void VFS_Archive_SetUsedFilters( DWORD dwHandle, const VFS_FilterNameList& Filters );<br/>
void VFS_Archive_GetUsedFilters( DWORD dwHandle, VFS_FilterNameList& Filters );<br/>
<br/>
// Add / Remove Files to / from the Archive.<br/>
void VFS_Archive_AddFile( DWORD dwHandle, LPCTSTR pszFile );<br/>
void VFS_Archive_RemoveFile( DWORD dwHandle, LPCTSTR pszFile );<br/>
<br/>
// Extract the Archive.<br/>
void VFS_Archive_Extract( DWORD dwHandle, LPCTSTR pszTarget );<br/>
<br/>
// Information.<br/>
void VFS_Archive_GetInfo( DWORD dwHandle, VFS_EntityInfo* pInfo );<br/>
void VFS_Archive_GetInfo( LPCTSTR pszArchive, VFS_EntityInfo* pInfo );<br/>
非常にシンプルなインターフェースですよね? アーカイブファイルの通常のこと。 そして、ようやく、先ほど見たフィルター関数のアプリケーションが表示されます。 VFS_Archive_Set / GetUsedFilters()を使用してフィルターを適用できます。
フォルダーインターフェイス
これは最新のVFSインターフェースです。 私が思うに説明なしで理解されるべき3つの機能が含まれています。
// Information.<br/>
BOOL VFS_Dir_Exists( LPCTSTR pszDir );<br/>
BOOL VFS_Dir_GetInfo( LPCTSTR pszDir, VFS_EntityInfo* pInfo );<br/>
<br/>
// Get the Contents of a Directory.<br/>
vector< VFS_EntityInfo > VFS_Dir_GetContents( LPCTSTR pszDir, BOOL bRecursive = FALSE );
関数1と2は非常に単純です(ファイルに使用されるかのように)。 機能3はDOSコマンドのように機能します。 ;-)
ちょっとおしゃべり
以上です。 記事の前半は終了しました。 私は信じない(私も:)約。 翻訳者)。 しかし、最も難しいのはまだ来ていません。
VFSを書く必要があります!!!
article_vfs_header.hをダウンロードする