引擎之旅 Chapter.3 文件系统

news/2024/5/21 11:27:06/文章来源:https://blog.csdn.net/qq_38134452/article/details/127281175

文章目录

  • 引言
  • 在此之前...
    • Unicode和ASCII
    • C风格字符串的操作函数集合
      • 字符串操作
      • 字符串类型转换
  • Part1:操作文件名和文件路径
  • Part2:单个文件的读写
    • 文件打开的模式
    • TFile的定义
  • Part3:异步文件I/O
    • 异步I/O线程
    • 文件类中的异步方法

引言

为什么会将文件系统放在引擎的底层核心代码呢?对于游戏而言,游戏的本质就是多媒体体验(模型、声音、视频等),而游戏引擎引擎需要在底层实现相关文件的读取作为支撑。

文件系统和资源管理器是两个概念。文件系统的功能是文件的读写,针对的是单文件或文件夹;而资源管理器则是目录级别的增删查改,可以成为引擎文件的简易版数据库。

  • 对于游戏引擎来说,文件系统应该要实现一下几个部分:
    • 操作文件名和文件路径
    • 开、关、读、写单独的文件
    • 处理异步文件输入/输出(IO)请求(做串流之用)

下面我们这几部分进行逐一分析,并展示实现的代码。

在此之前…

对于文件系统。字符串应该是密不可分的。且不说很多文件都与文本(字符串组成),文件路径也是通过字符串来表达的。因此,对字符串的处理对于文件系统来说是十分重要的。幸运的是,C提供了许多对字符串的操作函数,我们需要对它们进行封装整合即可。

Unicode和ASCII

关于使用ASCII还是Unicode,我的建议是尽量两种都实现,毕竟我们不知道仅仅使用一种类型的字符会不会在未来的开发中遇到麻烦,多多益善。

例如:在文件流处理中,我发现了宽字符版本的fputws和fgetws使用时其fopen()不能基于文本,只能基于二进制。而二进制对于要记录文字可读性是非常差的。因此,最好使用ASCII版的 fputs和fgets

而在Windows中字符和字符串的类型有众多定义(天晓得为什么会则么多)。为代码类型统一性,我现在这里做一个规范。在此引擎代码中,我将使用如下字符类型。

字符集字符类型常量字符类型字符指针类型常量字符串类型
ASCIICHARconst CHARPSTRPCSTR
UnicodeWCHARconst WCHARPWSTRPCWSTR

C风格字符串的操作函数集合

接下来我们要将C风格字符串的操作函数用自己的命名风格对其进行封装。代码虽然比较多,但不复杂。每一种函数都要写ASCII和Unicode两种脚本。函数按功能分类主要有:

  • 按功能分类
    • 获取字符串长度
    • 字符串复制
    • 字符串拼接
    • 字符串比较
    • 字符串中查找字符(从左往右和从右往左两个版本)
    • 字符格式化
    • CHAR字符串和WCHAR字符串之间的相互转换

字符串操作

//TEString.h
//-------------------------------------------------------------------------
#include <wchar.h>
#include <tchar.h>//获取宽字符串长度
//param:
//  str:计算的字符串
//return:
//  返回字符串长度
inline size_t TStrLen(PCWSTR str)
{return _tcslen(str);
}//获取字符串长度
//param:
//  str:计算的字符串
//return:
//  返回字符串长度
inline size_t TStrLen(PCSTR str)
{return strlen(str);
}//获取[char16_t]字符串长度
//param:
//  str:计算的字符串
//return:
//  返回字符串长度
inline size_t TStrLen(register const char16_t* str)
{if (!str)return 0;register size_t len = 0;while (str[len++]);return len - 1;
}//获取[char32_t]字符串长度
//param:
//  str:计算的字符串
//return:
//  返回字符串长度
inline size_t TStrLen(register const char32_t* str)
{if (!str)return 0;register size_t len = 0;while (str[len++]);return len - 1;
}//宽字符串复制
//param:
//  dest:目标缓存指针
//  destlen:目标缓存大小
//  source:拷贝源字符串  
inline errno_t TStrCpy(PWSTR dest, size_t destlen, PCWSTR source)
{return _tcscpy_s(dest, destlen, source);
}//char字符串复制
//param:
//  dest:目标缓存指针
//  destlen:目标缓存大小
//  source:拷贝源字符串  
inline errno_t TStrCpy(PSTR dest, size_t destlen, PCSTR source)
{return strcpy_s(dest, destlen, source);
}//宽字符串复制
//param:
//  dest:目标缓存指针
//  destlen:目标缓存大小
//  source:拷贝源字符串  
//  cpylen:拷贝字符串长度
inline errno_t TStrCpy(PWSTR dest, unsigned int destlen, PCWSTR source, unsigned int cpylen)
{return _tcsncpy_s(dest, destlen, source, cpylen);
}//将宽字符串endStr拼接到dest上
//param:
//  dest:目标缓存指针
//  destlen:目标缓存大小
//  endStr:拼接的字符串
inline errno_t TStrCat(PWSTR dest, size_t destlen, PCWSTR endStr)
{return _tcscat_s(dest, destlen, endStr);
}//将宽字符串endStr拼接到dest上
//param:
//  dest:目标缓存指针
//  destlen:目标缓存大小
//  endStr:拼接的字符串
//  catlen:需要从拼接字符串获取的长度
inline errno_t LStrCat(PWSTR dest, size_t destlen, PCWSTR endStr, int catlen)
{return _tcsncat_s(dest, destlen, endStr, catlen);
}//将字符串endStr拼接到dest上
//param:
//  dest:目标缓存指针
//  destlen:目标缓存大小
//  endStr:拼接的字符串
inline errno_t TStrCat(PSTR dest, size_t destlen, PCSTR endStr)
{return strcat_s(dest, destlen, endStr);
}//宽字符串比较函数
//param
//  str1:比较字符串1
//  str2:比较字符串2
inline int TStrCmp(PWSTR str1, PWSTR str2)
{return _tcscmp(str1, str2);
}//宽字符串比较函数
//param
//  str1:比较字符串1
//  str2:比较字符串2
//  cmplen:比较的长度
inline int TStrCmp(PWSTR str1, PWSTR str2, size_t cmplen)
{return _tcsncmp(str1, str2, cmplen);
}//宽字符查找函数(从左往右找)
//param
//  opStr:查找字符串
//  c:目标字符
inline PWSTR TStrChr(PWSTR opStr, WCHAR c)
{return wcschr(opStr, c);
}//字符查找函数(从左往右找)
//param
//  opStr:查找字符串
//  c:目标字符
inline PCSTR TStrChr(PCSTR opStr, CHAR c)
{return strchr(opStr, c);
}//宽字符查找函数(从右往左找)
//param
//  opStr:查找字符串
//  c:目标字符
inline PWSTR TStrRChr(PWSTR opStr, WCHAR c)
{return wcsrchr(opStr, c);
}//字符查找函数(从右往左找)
//param
//  opStr:查找字符串
//  c:目标字符
inline PCSTR TStrRChr(PCSTR opStr, CHAR c)
{return strrchr(opStr, c);
}

字符串类型转换

Windows提供了API用以ASCII和Unicode之间的转化。

/**  将多字节字符串转换为宽字符串*  @param*  uCodePage:标识一个与多字节字符串相关的代码页号。一般设定为CP_ACP*  dwFlags:设定另一个控件,它可以用重音符号之类的区分标记来影响字符*  pMultiByteStr:要转换的多字节字符串*  cchMultiByte:转换字符串的长度。如果传递-1,则该函数用于确定源字符串的长度*  pWideCharStr:指向转换后获得的字符串缓存*  cchWideChar:缓存的最大值*/
int MultiByteToWideChar(UINT uCodePage,DWORD dwFlags,PCSTR pMultiByteStr,int cchMultiByte,PWSTR pWideCharStr,int cchWideChar
);/**  将宽字符串转换为多字节字符串*  @param*  uCodePage:标识一个与多字节字符串相关的代码页号。一般设定为CP_ACP*  dwFlags:设定另一个控件,它可以用重音符号之类的区分标记来影响字符.一般设为0*  pWideCharStr:要转换的款宽字符串*  cchMultiByte:转换字符串的长度。如果传递-1,则该函数用于确定源字符串的长度*  pWideCharStr:指向转换后获得的字符串缓存*  cchWideChar:缓存的最大值*  pDefaultChar:当一个字符转换失败时的默认替代字符*  pfUsedDefaultChar:本次操作是否存在为转换成功而用了默认字符的情况。有为TRUE,无为FALSE*/
int MultiByteToWideChar(UINT uCodePage,DWORD dwFlags,PCWSTR pWideCharStr,int cchMultiByte,PSTR pMultiByteStr,int cchWideChar,PCSTR pDefaultChar,PBOOL pfUsedDefaultChar
)

关于这两个函数如何使用,我在TEString.h实现了ASCII和Unicode字符串。

//ASCII(char)字符串转Unicode
inline void TAsciiToUnicode(PCSTR source, PSTR dest, int destBufferSize)
{size_t size = MultiByteToWideChar(CP_ACP, 0, source, -1, NULL, 0);size = size > destBufferSize ? destBufferSize : size;ULONGLONG len = sizeof(WCHAR) * size;MultiByteToWideChar(CP_ACP, 0, (LPCCH)source, -1, (LPWSTR)dest, len);dest[size + 1] = '\0';
}//Unicode字符串转ASCII(char)
inline void TUnicodeToAscii(PCWSTR source, PSTR dest, int destBufferSize)
{size_t size = WideCharToMultiByte(CP_ACP, NULL, source, -1, NULL, 0, NULL, FALSE);size = size > destBufferSize ? destBufferSize : size;WideCharToMultiByte(CP_ACP, NULL, (LPCWCH)source, -1,     (LPSTR)dest, size, NULL, FALSE);
}

这样,我们得到了一些简单的字符串操作函数合集。这些函数对于文件系统来说足够使用了。

Part1:操作文件名和文件路径

文件名和文件路径构成了文件的一个标识。对于文件路径,我们想要获取的属性主要有:

重要熟悉描述用例
文件拓展名路径字符串从右往左第一个’.'之后的字符串C:/log.txt 的拓展名是txt
文件名(含拓展名)路径字符串从右往左第一个’/‘或’\'之后的字符串C:/log.txt 的文件名是log.txt
文件名(不含拓展名)路径字符串从右往左第一个’/‘或’\‘之后和第一个’.'之前的字符串C:/log.txt 的文件名是log
文件所在的根盘符路径字符串从左往右第一个"😕“或”:\"之前的字符串C:/log.txt 的文件名是C:/
文件所在文件夹路径字符串从右往左第一个’/‘或’\'之前的字符串C:/Folder/log.txt 的文件夹名是C:/Folder/

在上述的表格中,通过对属性特征的描述,当我们得到一个文件路径的时候,我们便可以根据属性特征,利用字符串的操作函数来获取相应的属性。在路径解析函数集合中,我们主要利用的字符串函数如下:

  • 使用的字符串操作函数列表
    • TStrRChr:从右往左查找字符
    • TStrCpy:字符串拷贝
    • TStrCat:字符串拼接
//路径类
class TURBO_CORE_API TPath
{
public://修改文件拓展名(不做长度验证)static bool ChangeExtension(PWSTR result, size_t resBufferLen, PWSTR path, PCWSTR extension);//将传入字符串合成一个路径static void Combine(PWSTR result, size_t resBufferLen, int combineCount, ...);//获取目录名。//若是个文件目录,则返回文件所在文件夹;//若是文件夹目录,则返回上一级文件夹的路径static bool GetDirectoryName(PWSTR result, size_t resBufferLen, PWSTR path);//获取拓展名static bool GetExtensionName(PWSTR result, size_t resBufferLen, PWSTR path);//获取带拓展名的文件名static bool GetFileName(PWSTR result, size_t resBufferLen, PWSTR path);//获取不带拓展名的文件名static bool GetFileNameWithoutExtension(PWSTR result, size_t resBufferLen, PWSTR path);//获取根盘符static bool GetPathRoot(PWSTR result, size_t resBufferLen, PWSTR path);//路径是否包含文件拓展名static bool HasExtension(PWSTR path);//路径是否包含根盘符static bool HasVolume(PWSTR path);public:inline static WCHAR DirectorySeparatorChar() { return ms_DirectorySeparatorChar;}inline static WCHAR AltDirectorySeparatorChar() { return ms_AltDirectorySeparatorChar; }inline static WCHAR VolumeSeparatorChar() { return ms_VolumeSeparatorChar; }inline static WCHAR PathSeparatorChar() { return ms_PathSeparatorChar; }inline static WCHAR ExtensionPrefixChar() { return ms_ExtensionPrefixChar; }protected://获取路径最后一个目录分隔符的位置static PWSTR GetLastDirSeparator(PWSTR path);//获取路径第一个目录分隔符的位置static PWSTR GetFirstDirSeparator(PWSTR path);static const WCHAR ms_DirectorySeparatorChar;           //常规的目录分隔符static const WCHAR ms_AltDirectorySeparatorChar;        //得到替换的目录分隔符(和常规的等价)static const WCHAR ms_VolumeSeparatorChar;              //Windows卷的分隔符static const WCHAR ms_PathSeparatorChar;                //路径分隔符static const WCHAR ms_ExtensionPrefixChar;              //文件拓展名前缀
};const WCHAR TurboEngine::Core::TPath::ms_DirectorySeparatorChar = '\\';
const WCHAR TurboEngine::Core::TPath::ms_AltDirectorySeparatorChar = '/';
const WCHAR TurboEngine::Core::TPath::ms_VolumeSeparatorChar = ':';
const WCHAR TurboEngine::Core::TPath::ms_PathSeparatorChar = ';';
const WCHAR TurboEngine::Core::TPath::ms_ExtensionPrefixChar = '.';

Part2:单个文件的读写

C语言可以采用文件IO流的方式对于文件的读写。如何理解文件流呢?下面根据我自己的理解画了一张图(仅供参考)。

文件I/O流需要关注那些属性呢?我自己总结了一下几点:

  • 文件使用字符集:ASCII或Unicode
  • 读写权限:是否读、是否可写、若不存在文件是否可创建、文件打开写入时是否是追加模式等
  • 文本存储格式:基于文本或基于二进制- 文件大小

在引擎中,我们将单个文件的读写方式全部封装在TFile这一个类中。并利用Open()函数传入路径并打开相应的文件。在Open中,我们调用了fopen_s()。函数的解释如下:

/**  C语言函数,打开一个文件*  pFile:返回的打开文件的句柄*  filename:文件名(文件路径)*  mode:文件存储格式和文件读写权限的模式说明*/
errno_t fopen_s( FILE** pFile, const char *filename, const char *mode );//宽字符版本
errno_t _wfopen_s( FILE** pFile, const wchar_t *filename, const wchar_t *mode );

文件打开的模式

下面我讲重点介绍mode这个参数。mode 的类型是一个char字符串。对于mode字符串的解析,可参考如下表格:

  • mode之读写权限
符号意义
r以只读的方式打开,文件必须存在
r+以可读写的方式打开,文件必须存在
w打开只写文件。若文件存在则文件长度清0。若不存在则创建新文件
w+打开可读写文件,若文件存在则文件长度清0。若不存在则创建新文件
a以附加的方式打开只写文件。若文件存在写入时追加在文件末尾。若不存在则创建新文件
a+以附加的方式打开可读写文件。若文件存在写入时追加在文件末尾。若不存在则创建新文件
  • mode之文件存储格式
符号意义
t文件以文本存储
b文件以二进制存储

基于此:我们可以将这两部分进行1对1的组合。例如:基于二进制的只读文件(文件必须存在),其 mode=“rb”。再比如,基于文本的可读写文件,新打开会请空内容(文件不存在则创建),其 mode=“w+t”

在引擎中。我利用模板实现了ASCII和Unicode两个版本的Mode生产函数。实现代码如下所示:

//相关字符
const WCHAR TurboEngine::Core::TFile::ms_ReadChar = 'r';
const WCHAR TurboEngine::Core::TFile::ms_WriteChar = 'w';
const WCHAR TurboEngine::Core::TFile::ms_AppendChar = 'a';
const WCHAR TurboEngine::Core::TFile::ms_PlusChar = '+';
const WCHAR TurboEngine::Core::TFile::ms_TextModeChar = 't';
const WCHAR TurboEngine::Core::TFile::ms_BinaryModeChar = 'b';template<typename T>
inline void TFile::GetModeTag(T mode[4], FileMode fileMode, FileAccess fileAccess)
{unsigned int index = 0;switch (fileAccess){case FileAccess::OnlyRead_NotCreate:         mode[index++] = ms_ReadChar;                                break;case FileAccess::ReadWrite_NotCreate:        mode[index++] = ms_ReadChar;   mode[index++] = ms_PlusChar; break;case FileAccess::OnlyWrite_CreateAndClean:   mode[index++] = ms_WriteChar;                               break;case FileAccess::ReadWrite_CreateAndClean:   mode[index++] = ms_WriteChar;  mode[index++] = ms_PlusChar; break;case FileAccess::OnlyAppendWrite_Create:     mode[index++] = ms_AppendChar;                              break;case FileAccess::AppendReadWrite_Create:     mode[index++] = ms_AppendChar; mode[index++] = ms_PlusChar; break;}switch (fileMode){case FileMode::Text:          mode[index++] = ms_TextModeChar;    break;case FileMode::Binary:        mode[index++] = ms_BinaryModeChar;  break;}mode[index] = '\0';
}//用例:
//ASCII版
CHAR mode[4];
GetModeTag<CHAR>(mode,FileMode::Text,FileAccess::OnlyRead_NotCreate);

TFile的定义

//文件类
class TURBO_CORE_API TFile
{
public://字符类型enum class CharType{SingleByte,  //单字节字符DoubleByte   //双字节字符};//文件存储格式enum class FileMode{Text,    //文本格式Binary,  //二进制格式};//文件读写权限enum class FileAccess{OnlyRead_NotCreate,            //(r)  只读模式。文件必须存在。ReadWrite_NotCreate,           //(r+) 读写模式。文件必须存在。文件打开后内容会清零。OnlyWrite_CreateAndClean,      //(w)  只写模式。文件打开后内容会清零。无文件则创建新的文件。ReadWrite_CreateAndClean,      //(w+) 读写模式。无文件则创建新的文件。打开后内容会清零。OnlyAppendWrite_Create,        //(a)  只写于尾。无文件则创建新的文件。AppendReadWrite_Create,        //(a+) 读写于尾。无文件则创建新的文件。};//文件流所在的位置enum class StreamPos : int{Begin = SEEK_SET,    //文件起始的位置Current = SEEK_CUR,    //当前流的位置End = SEEK_END     //文件末尾的位置};public:TFile();~TFile();//打开或创建文件(宽字符版)bool Open(PCWSTR filePath, FileMode fileMode, FileAccess fileAccess);//打开或创建文件bool Open(PCSTR filePath, FileMode fileMode, FileAccess fileAccess);//按照临时文件打开bool OpenAsTempFile();//关闭文件bool Close();//刷新文件流,将文件写入磁盘中bool Flush();//输入一个字符到文件流中(宽字符版)bool PutChar(WCHAR wc, StreamPos basePos = StreamPos::End, long offset = 0);//输入一个字符到文件流中bool PutChar(CHAR wc, StreamPos basePos = StreamPos::End, long offset = 0);//从文件流中获取一个字符(宽字符版)bool GetChar(WCHAR& result, StreamPos basePos = StreamPos::Current, long offset = 0);//从文件流中获取一个字符bool GetChar(CHAR& result, StreamPos basePos = StreamPos::Current, long offset = 0);//输入一个字符串到文件流中(宽字符版)bool PutStringtLine(PCWSTR str, StreamPos basePos = StreamPos::End, long offset = 0);//输入一个字符串到文件流中bool PutStringtLine(PCSTR str, StreamPos basePos = StreamPos::End, long offset = 0);//从文件流中读取一行字符串(宽字符版)bool GetStringLine(PWSTR result, int resBufferSize, StreamPos basePos = StreamPos::Current, long offset = 0);//从文件流中读取一行字符串bool GetStringLine(PSTR result, int resBufferSize, StreamPos basePos = StreamPos::Current, long offset = 0);//格式化输入字符串到文件流中(宽字符版)template<typename ...Args>bool PutStringFormatInEnd(PCWSTR format, Args... args);//格式化输入字符串到文件流中template<typename ...Args>bool PutStringFormatInEnd(PCSTR format, Args... args);//写入二进制块(仅适用于基于二进制存储的文件)bool WriteBinaryBlock(void* mem, size_t blockSize, size_t blockNum);//读取二进制块(仅适用于基于二进制存储的文件)bool ReadBinaryBlock(void* result, size_t blockSize, size_t blockNum);//上次文件流是否是在文件尾部bool IsPrevStreamInPosInEnd();//将文件流读取指针指向流的起始端void BackToStreamBegin();//文件流跳转位置bool SeekStreamPos(StreamPos basePos, long offset);//获取文件流操作最后的错误代码int GetLastErrorState();//清除错误代码void ClearError();//删除文件(宽字符版)bool Delete(PCWSTR filePath);//删除文件bool Delete(PCSTR filePath);//获取文件大小long FileSize();public:inline CharType   GetCharType() { return m_CharType; }inline FileMode   GetFileMode() { return m_FileMode; }inline FileAccess GetFileAccess() { return m_FileAccess; }inline bool       IsTempFile() { return m_IsTempFile; }protected:template<typename T>void GetModeTag(T mode[4], FileMode fileMode, FileAccess fileAccess);static const WCHAR ms_ReadChar;static const WCHAR ms_WriteChar;static const WCHAR ms_AppendChar;static const WCHAR ms_PlusChar;static const WCHAR ms_TextModeChar;static const WCHAR ms_BinaryModeChar;private:CharType     m_CharType;FileMode     m_FileMode;FileAccess   m_FileAccess;FILE*        m_pFile;LONG         m_Offset;bool         m_IsTempFile;
};

Part3:异步文件I/O


异步文件I/O的实现思路如下:

  • 文件系统在第一次使用异步方法时,会开启一个I/O线程。此线程会查询一个队列。队列中即为I/O请求。
  • 主线程调用文件系统的异步函数时,会向队列添加读或写的请求,以供子线程执行。
  • 请求队列可以使用侵入式单链表的数据结构来实现
  • 因为是多线程操作,因此需要注意公有变量的修改同步问题。因为复杂度不是很高,为了效率,可以使用关键段的同步方式。

异步I/O线程

enum class RequestType
{Read,ReadAll,Write,
};//异步文件IO请求()
typedef struct AyncFileIORequest
{RequestType          m_RequestType;PTFile               m_FileHandle;void*                m_RWBuffer;size_t               m_BufferSize;AsyncFileIOCompleted m_CompletedFunc;AyncFileIORequest*   m_NextRequest;SyncTrigger          m_IsCompletedTrigger;AyncFileIORequest(): m_RequestType(RequestType::Read),m_FileHandle(nullptr),m_RWBuffer(nullptr),m_BufferSize(0),m_CompletedFunc(nullptr),m_NextRequest(nullptr),m_IsCompletedTrigger(true, false) {}
}* PAyncFileIORequest;//关键函数的实现
//-------------------------------------------------------------------------------//添加I/O请求
HANDLE AddRequest(RequestType rt, PTFile fileHandle, void* Buffer, size_t bufferSize, AsyncFileIOCompleted completedFunc)
{PAyncFileIORequest  pRequest = TNew AyncFileIORequest();pRequest->m_RequestType = rt;pRequest->m_FileHandle = fileHandle;pRequest->m_RWBuffer = buffer;pRequest->m_BufferSize = bufferSize;pRequest->m_CompletedFunc = completedFunc;pRequest->m_NextRequest = nullptr;m_QueueLock.Lock();if (m_Queue == nullptr)m_Queue = pRequest;else{PAyncFileIORequest pTemp = m_Queue;while (pTemp->m_NextRequest) pTemp = pTemp->m_NextRequest;pTemp->m_NextRequest = pRequest;}m_QueueLock.Unlock();return pRequest->m_IsCompletedTrigger.GetHandle();
}//线程中的主执行函数
DWORD __stdcall TurboEngine::Core::TAsyncFileIOThread::Run()
{PAyncFileIORequest pRequest = nullptr;while (!m_TerminateThreadTrigger.IsTrigger()){pRequest = nullptr;if (m_QueueLock.TryLock()){if (m_Queue != nullptr){pRequest = m_Queue;m_Queue = m_Queue->m_NextRequest;}m_QueueLock.Unlock();}//若pRequst不为空,则表示队列中有未执行的请求if (pRequest != nullptr){switch (pRequest->m_RequestType){case RequestType::Read:pRequest->m_FileHandle->Read(pRequest->m_RWBuffer,1,pRequest->m_BufferSize);break;case RequestType::ReadAll:pRequest->m_FileHandle->ReadAll(pRequest->m_RWBuffer,pRequest->m_BufferSize);break;case RequestType::Write:pRequest->m_FileHandle->Write(pRequest->m_RWBuffer,1,pRequest->m_BufferSize);break;}if (pRequest->m_CompletedFunc != nullptr)(*(pRequest->m_CompletedFunc))(pRequest);pRequest->m_IsCompletedTrigger.SetActive();TDelete pRequest;}pRequest = nullptr;}return 0;
}

文件类中的异步方法

HANDLE TurboEngine::Core::TFile::ReadAsync(void* mem, size_t blockSize, size_t blockNum, AsyncFileIOCompleted completedFunc)
{return AsyncIOHandle().AddRequest(RequestType::Read,this,mem,blockSize * blockNum,completedFunc);
}HANDLE TurboEngine::Core::TFile::WriteAsync(void* result, size_t blockSize, size_t blockNum, AsyncFileIOCompleted completedFunc)
{return AsyncIOHandle().AddRequest(RequestType::Write,this,result,blockSize * blockNum,completedFunc);
}HANDLE TurboEngine::Core::TFile::ReadAllAsync(void* result, size_t resBufferLen, AsyncFileIOCompleted completedFunc)
{return AsyncIOHandle().AddRequest(RequestType::ReadAll,this,result,resBufferLen,completedFunc);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_22338.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据中台开源解决方案(一)

数据中台商业的解决方案有很多,开源框架种类繁多,每一个模块都有很多开源的套件。以查询引擎为例,可以使用的开源工具有MySQL、Redis、Impala、MongoDB、PgSQL等。可以根据实际业务需要,选择合适的开源套件。 可供选择的解决方案太多,重点推荐开源解决方案,框架图如下图所…

【RCJ-2 AC220V 0.015A静态冲击继电器】

系列型号 RCJ-2/48VDC冲击继电器 RCJ-2/110VDC冲击继电器 RCJ-2/220VDC冲击继电器 RCJ-2/100VAC冲击继电器 RCJ-2/127VAC冲击继电器 RCJ-2/220VAC冲击继电器 RCJ-3/220VAC冲击继电器 RCJ-3/127VAC冲击继电器 RCJ-3/100VAC冲击继电器 RCJ-3/220VDC冲击继电器 RCJ-3/110VDC冲击继…

【论文笔记】CIKM‘22 & Amazon | (Navip) 推荐系统中图神经网络的去偏邻域聚合

目录1. Introduction2. Related Work2.1 基于GNN的推荐2.2 逆倾向评分IPS3. 模型3.1 Navip4. 实验结果本文是亚马逊在CIKM 2022的一篇工作 论文地址&#xff1a;https://arxiv.org/pdf/2208.08847.pdf 问题 图形神经网络(GNN)对于曝光偏差的脆弱性导致模型偏差&#xff0c;产生…

09-Pytorch中的序列化容器

目录 1.梯度消失和梯度爆炸 1.1 梯度消失 1.2 梯度爆炸 1.3 解决梯度消失或梯度爆炸的经验 2.nn.Sequential 3.nn.BatchNorm1d 4.nn.Dropout 1.梯度消失和梯度爆炸 在使用pytorch中的序列化容器之前&#xff0c;我们先来了解一下常见的梯度消失和梯度爆炸的问题。 1.1 …

②、HTML 元素学习

HTML 元素 HTML 文档由 HTML 元素定义。 HTML 元素 *开始标签常被称为起始标签&#xff08;opening tag&#xff09;&#xff0c;结束标签常称为闭合标签&#xff08;closing tag&#xff09;。 HTML 元素语法 HTML 元素以开始标签起始HTML 元素以结束标签终止元素的内容是开始…

Class02

Class02 1.自动类型值转换 隐式类型转换 显式类型转换 隐式类型转换&#xff08;自动类型转换&#xff09; 隐式类型转换是小范围向大范围转换 实际上小范围包含在大范围内 整数类型的默认类型为int 浮点数类型的默认类型为double 显式类型转换&#xff08;强制类型转换&am…

linux 预读机制 (linux4.14)

一、基本概念 设计背景 文件一般是顺序访问的&#xff0c;访问[A, B]范围的数据后&#xff0c;接下来很可能访问[B1, BN]数据。由于访问磁盘、flash等存储器件比较耗时&#xff0c;在访问 [A, B]的时候&#xff0c;如果提前把[B1, BN]数据从存储器件读取到ram中&#xff0c;那…

TTN服务器LoRaWAN网关配置流程

在LoRaWAN物联网网络中&#xff0c;LoRaWAN网关起到了绝对核心的作用&#xff0c;它在整个网络中像是一座桥梁建立起网络服务器与终端节点的通信&#xff0c;下面我们将简单介绍如何使用E890-470LG11网关实现TTN服务器与终端节点的通信。 首先&#xff0c;在LoRaWAN通信中&…

亿佰特LoRaWAN入网TTN并订阅MQTT消息

一、LoRaWAN节点入网 1.注册并登录TTN账号。 2.添加网关。Gateway EUI可自定义。 3.切换到亿百特网关配置网页&#xff08;http://192.168.10.1/&#xff09;,配置网关。 4.切抱TTN网页&#xff0c;创建app&#xff0c;Application ID可任意取&#xff0c;只要不重复就行&…

MSRN(多尺度超分辨率重建)

目前的研究倾向于使用更深层次的卷积神经网络来提高性能。然而&#xff0c;盲目增加网络深度不能有效改善网络。更糟糕的是&#xff0c;随着网络深度的增加&#xff0c;训练过程中出现了更多的问题&#xff0c;需要更多的训练技巧。在本文中&#xff0c;我们提出了一种新颖的多…

TRC丨艾美捷TRC 1-Hexyl-2-phenyl-4说明书

1-Alkyl-2-aryl-4-(1-naphthoyl)pyrroles 作为大丨麻素 CB1 和 CB2 受体的高亲和力配体。 艾美捷TRC 1-Hexyl-2-phenyl-4化学性质&#xff1a; 目录号H297400 化学名称1-Hexyl-2-phenyl-4-(1-naphthoyl)pyrrole JWH-147 同义词JWH-147&#xff1b;(1-己基-5-苯基-1H-吡咯-3-基…

数据库06-Redis 强化

目录 Redis 强化 缓存使用原则 缓存淘汰策略 缓存穿透 缓存击穿 缓存雪崩 Redis持久化 RDB:(Redis Database Backup) AOF(Append Only File): Redis存储原理 Redis集群 Redis 强化 缓存使用原则 什么时候,什么样的数据能够保存在Redis中? 1.数据量不能太大 2.使…

计算机操作系统:输入输出管理知识点汇总(简化版)

说明 博客作为笔记备份&#xff0c;不定时更新参考内容为王道考研《计算机操作系统考研复习指导2023》简化版仅进行了知识点汇总&#xff0c;详细知识点没有记录&#xff0c;主要用来搭知识体系框架思维导图为文字版导出的&#xff0c;可将文字内容粘贴到思维导图软件自动生成…

DDL-操作表查询和创建

DDL-操作表查询以及创建 操作表 1.C(Create):创建语法:create table 表名(列名1 数据类型1,列名2 数据类型2,......列名n 数据类型n);注意:最后一列不需要加逗号 数据类型:int:整数类型age int,double:小数类型score double(5,2)date:日期类型,只包含年月日,yyyy…

二维数组、可变长数组、多维数组、函数调用

1.二维数组名称也可以代表数组里第一个存储区的地址。 例子&#xff1a; 输出结果&#xff1a;二维数组的名称二维数组第一个数的地址 二维数组的名称也不可以被赋值。 二维数组名称也可以进行sizeof计算&#xff0c;结果是二维数组里所有存储区的总大小。 例子&#xff1a;…

Spring源码深度解析:三、Spring之Bean的生命周期

Bean的生命周期 创建的生命周期 UserService---->推断构造---->普通对象----->依赖注入---->初始化前(PostConstruct)----->初始化(InitializingBean)---->初始化后(AOP)---->代理对象-----> 放入Map单例池中---->Bean对象 创建一个modle: spring…

Android 简单控件 - -4.按钮控件 button

系列文章目录 文章目录系列文章目录前言1.按钮控件Button1.1按钮控件的新增属性2. 点击事件和长按事件2.1 点击事件2.2 长按事件3. 禁用与恢复按钮总结前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 1.按钮控件Button 按钮控件Button由TextView派…

CPU有缓存一致性协议(MESI),为什么JMM还需要volatile关键字?

这是一个非常好的问题&#xff0c;相信本文能把这个问题讲得清清楚楚 上周我在查阅资料时无意中搜到一篇解释 volatile 用法的博文&#xff0c;这篇博文排得很靠前&#xff0c;不过很遗憾&#xff0c;虽然结论是对的&#xff0c;但分析过程完全错误&#xff0c;而且我发现网上…

网络编程套接字

文章目录一、理解源IP和目的IP二、认识端口号三、理解port端口号和进程ID四、理解IP和port端口号五、理解源端口号和目的端口号六、认识TCP协议七、认识UDP协议八、网络字节序字节处理函数九、socket编程socket的解释socket常见APIsockaddr结构十、UDP套接字通信十一、TCP套接字…

ELK/ELFK日志分析系统部署

一、ELK日志分析系统 ELK是一套完整的日志集中处理解决方案&#xff0c;将Elastic Search、Logstash和Kibana三个开开源工具配置使用&#xff0c;发挥出更强大的用户对日志查询、排序、统计需求&#xff08;还可以加一个filebeat&#xff09;。 1、组成 ①Elasticserach El…