MemoryMappedFileStreamWindows.cpp //CopyrightEpicGames,Inc.AllRightsReserved. //*INDENT-OFF* #ifdefTRIO_WINDOWS_FILE_MAPPING_AVAILABLE #include"trio/streams/MemoryMappedFileStreamWindows.h" #include"trio/utils/NativeString.h" #include"trio/utils/ScopedEnumEx.h" #include<pma/PolyAllocator.h> #ifdef_MSC_VER #pragmawarning(push) #pragmawarning(disable:43654987) #endif #include<algorithm> #include<cassert> #include<cstddef> #include<cstdint> #include<cstring> #include<ios> #include<limits> #include<type_traits> #ifdef_MSC_VER #pragmawarning(pop) #endif namespacetrio{ namespace{ constexprstd::size_tminViewSizeWindows=65536ul; inlinestd::uint64_tgetFileSizeWindows(constNativeCharacter*path){ WIN32_FILE_ATTRIBUTE_DATAw32fad; if(GetFileAttributesEx(path,GetFileExInfoStandard,&w32fad)==0){ return0ul; } ULARGE_INTEGERsize; size.HighPart=w32fad.nFileSizeHigh; size.LowPart=w32fad.nFileSizeLow; returnstatic_cast<std::uint64_t>(size.QuadPart); } inlinestd::uint64_tgetPageSizeWindows(){ SYSTEM_INFOSystemInfo; GetSystemInfo(&SystemInfo); returnSystemInfo.dwAllocationGranularity; } inlinestd::uint64_talignOffsetWindows(std::uint64_toffset){ conststd::uint64_tpageSize=getPageSizeWindows(); returnoffset/pageSize*pageSize; } classMemoryReaderWindows:publicReadable{ public: explicitMemoryReaderWindows(constchar*source_):source{source_}{} std::size_tread(char*destination,std::size_tsize)override{ CopyMemory(destination,source,size); source+=size; returnsize; } std::size_tread(Writable*destination,std::size_tsize)override{ destination->write(source,size); source+=size; returnsize; } private: constchar*source; }; classMemoryWriterWindows:publicWritable{ public: explicitMemoryWriterWindows(char*destination_):destination{destination_}{} std::size_twrite(constchar*source,std::size_tsize)override{ CopyMemory(destination,source,size); destination+=size; returnsize; } std::size_twrite(Readable*source,std::size_tsize)override{ source->read(destination,size); destination+=size; returnsize; } private: char*destination; }; }//namespace MemoryMappedFileStreamWindows::MemoryMappedFileStreamWindows(constchar*path_,AccessModeaccessMode_,MemoryResource*memRes_): filePath{NativeStringConverter::from(path_,memRes_)}, fileAccessMode{accessMode_}, memRes{memRes_}, file{INVALID_HANDLE_VALUE}, mapping{nullptr}, data{nullptr}, position{}, fileSize{getFileSizeWindows(filePath.c_str())}, viewOffset{}, viewSize{}, delayedMapping{false}, dirty{false}{ } MemoryMappedFileStreamWindows::~MemoryMappedFileStreamWindows(){ delayedMapping=false; flush(); unmapFile(); closeFile(); } MemoryResource*MemoryMappedFileStreamWindows::getMemoryResource(){ returnmemRes; } std::uint64_tMemoryMappedFileStreamWindows::size(){ returnfileSize; } voidMemoryMappedFileStreamWindows::open(){ status->reset(); if(file!=INVALID_HANDLE_VALUE){ status->set(AlreadyOpenError,filePath.c_str()); return; } delayedMapping=false; openFile(); if(file==INVALID_HANDLE_VALUE){ status->set(OpenError,filePath.c_str()); return; } //Retrievefilesize LARGE_INTEGERsize{}; if(GetFileSizeEx(file,&size)==0){ fileSize=0ul; closeFile(); status->set(OpenError,filePath.c_str()); return; } fileSize=static_cast<std::uint64_t>(size.QuadPart); //Mappingof0-lengthfilesisdelayeduntilthefileisresizedtoanon-zerosize. delayedMapping=(fileSize==0ul); if(delayedMapping){ return; } //Createfilemapping mapFile(0ul,fileSize); if(data==nullptr){ status->set(OpenError,filePath.c_str()); delayedMapping=false; unmapFile(); closeFile(); return; } seek(0ul); dirty=false; } voidMemoryMappedFileStreamWindows::close(){ delayedMapping=false; flush(); unmapFile(); closeFile(); } std::uint64_tMemoryMappedFileStreamWindows::tell(){ returnposition; } voidMemoryMappedFileStreamWindows::seek(std::uint64_tposition_){ constboolseekable=((position_==0ul)||(position_<=size()))&&(data!=nullptr); if(!seekable){ status->set(SeekError,filePath.c_str()); return; } position=position_; if((position<viewOffset)||(position>=(viewOffset+viewSize))){ flush(); if(dirty){ return; } unmapFile(); mapFile(position,fileSize-position); } } std::size_tMemoryMappedFileStreamWindows::read(char*destination,std::size_tsize){ if(destination==nullptr){ status->set(ReadError,filePath.c_str()); return0ul; } MemoryWriterWindowswriter{destination}; returnread(&writer,size); } std::size_tMemoryMappedFileStreamWindows::read(Writable*destination,std::size_tsize){ if((destination==nullptr)||!contains(fileAccessMode,AccessMode::Read)){ status->set(ReadError,filePath.c_str()); return0ul; } if(data==nullptr){ if(!delayedMapping){ status->set(ReadError,filePath.c_str()); } return0ul; } conststd::uint64_tbytesAvailable=fileSize-position; conststd::size_tbytesToRead=static_cast<std::size_t>(std::min(static_cast<std::uint64_t>(size),bytesAvailable)); std::size_tbytesRead=0ul; while(bytesRead!=bytesToRead){ conststd::size_tbytesRemaining=bytesToRead-bytesRead; std::size_tviewPosition=static_cast<std::size_t>(position-viewOffset); std::size_tbytesReadable=static_cast<std::size_t>(viewSize-viewPosition); //Iftheviewisexhaustedduringreading,remapanewviewtilltheendoffileifpossible, //startingatthecurrentposition if(bytesReadable==0ul){ unmapFile(); mapFile(position,fileSize-position); if(data==nullptr){ //Failedtomapnewview status->set(ReadError,filePath.c_str()); break; } viewPosition=static_cast<std::size_t>(position-viewOffset); bytesReadable=static_cast<std::size_t>(viewSize-viewPosition); } conststd::size_tchunkSize=std::min(bytesRemaining,bytesReadable); conststd::size_tchunkCopied=destination->write(static_cast<char*>(data)+viewPosition,chunkSize); bytesRead+=chunkCopied; position+=chunkCopied; } returnbytesRead; } std::size_tMemoryMappedFileStreamWindows::write(constchar*source,std::size_tsize){ if(source==nullptr){ status->set(WriteError,filePath.c_str()); return0ul; } MemoryReaderWindowsreader{source}; returnwrite(&reader,size); } std::size_tMemoryMappedFileStreamWindows::write(Readable*source,std::size_tsize){ if((source==nullptr)||!contains(fileAccessMode,AccessMode::Write)){ status->set(WriteError,filePath.c_str()); return0ul; } if((data==nullptr)&&!delayedMapping){ status->set(WriteError,filePath.c_str()); return0ul; } if(size==0ul){ return0ul; } if(position+size>fileSize){ resize(position+size); if(fileSize!=(position+size)){ //Resizenotsuccessful(resizesetsstatusinsuchcases) return0ul; } } std::size_tbytesWritten=0ul; while(bytesWritten!=size){ conststd::size_tbytesRemaining=size-bytesWritten; std::size_tviewPosition=static_cast<std::size_t>(position-viewOffset); std::size_tbytesWritable=static_cast<std::size_t>(viewSize-viewPosition); //Iftheviewisexhaustedduringwriting,remapanewviewtilltheendoffileifpossible, //startingatthecurrentposition if(bytesWritable==0ul){ flush(); if(dirty){ break; } unmapFile(); mapFile(position,fileSize-position); if(data==nullptr){ //Failedtomapnewview status->set(WriteError,filePath.c_str()); break; } viewPosition=static_cast<std::size_t>(position-viewOffset); bytesWritable=static_cast<std::size_t>(viewSize-viewPosition); } conststd::size_tchunkSize=std::min(bytesRemaining,bytesWritable); conststd::size_tchunkCopied=source->read(static_cast<char*>(data)+viewPosition,chunkSize); bytesWritten+=chunkCopied; position+=chunkCopied; } dirty=(bytesWritten>0ul); returnbytesWritten; } voidMemoryMappedFileStreamWindows::flush(){ if((data!=nullptr)&&(fileAccessMode!=AccessMode::Read)){ if(!FlushViewOfFile(data,0ul)){ status->set(WriteError,filePath.c_str()); return; } } dirty=false; } voidMemoryMappedFileStreamWindows::resize(std::uint64_tsize){ if(fileAccessMode==AccessMode::Read){ status->set(WriteError,filePath.c_str()); return; } flush(); if(dirty){ return; } unmapFile(); resizeFile(size); if(fileSize!=size){ status->set(WriteError,filePath.c_str()); return; } //Remapfilefromcurrentposition,tilltheendoffile mapFile(position,fileSize-position); if(data==nullptr){ status->set(WriteError,filePath.c_str()); return; } } voidMemoryMappedFileStreamWindows::openFile(){ DWORDaccess{GENERIC_READ}; access|=(contains(fileAccessMode,AccessMode::Write)?GENERIC_WRITE:access); //0==nosharinginanyway DWORDsharing{}; //Non-existingfilesarecreatedunlessinread-onlymode DWORDcreationDisposition{}; if(fileAccessMode==AccessMode::Read){ creationDisposition=static_cast<DWORD>(OPEN_EXISTING); }else{ creationDisposition=static_cast<DWORD>(OPEN_ALWAYS); } file=CreateFile(filePath.c_str(),access,sharing,nullptr,creationDisposition,FILE_ATTRIBUTE_NORMAL,nullptr); } voidMemoryMappedFileStreamWindows::closeFile(){ if(file!=INVALID_HANDLE_VALUE){ CloseHandle(file); file=INVALID_HANDLE_VALUE; } } voidMemoryMappedFileStreamWindows::mapFile(std::uint64_toffset,std::uint64_tsize){ //Createfilemapping constautoprotect=static_cast<DWORD>(contains(fileAccessMode,AccessMode::Write)?PAGE_READWRITE:PAGE_READONLY); mapping=CreateFileMapping(file,nullptr,protect,0u,0u,nullptr); if(mapping==nullptr){ return; } //Mapaviewofthefilemappingintotheaddressspace DWORDdesiredAccess{}; desiredAccess|=(contains(fileAccessMode,AccessMode::Write)?FILE_MAP_WRITE:desiredAccess); desiredAccess|=(contains(fileAccessMode,AccessMode::Read)?FILE_MAP_READ:desiredAccess); ULARGE_INTEGERalignedOffset{}; alignedOffset.QuadPart=static_cast<decltype(alignedOffset.QuadPart)>(alignOffsetWindows(offset)); //Makesuresizedoesnotexceedsystemlimits std::size_tsafeSize=static_cast<std::size_t>(std::min(static_cast<std::uint64_t>(std::numeric_limits<std::size_t>::max()),size)); //Increaseto-be-mappedsizebythedifferencecausedbyalignment safeSize=std::max(static_cast<std::size_t>(safeSize+(offset-alignedOffset.QuadPart)),safeSize); //Trymappingrequestedsize,butifitfailskeeprepeatingbyhalvingtheviewsizeeachtime(e.g.ifnotenoughVAspace) std::size_tnextSize=safeSize; do{ safeSize=nextSize; data=MapViewOfFile(mapping,desiredAccess,alignedOffset.HighPart,alignedOffset.LowPart,safeSize); if(data!=nullptr){ break; } nextSize=safeSize/2ul; } while(nextSize>minViewSizeWindows); if(data!=nullptr){ viewOffset=alignedOffset.QuadPart; viewSize=safeSize; } } voidMemoryMappedFileStreamWindows::unmapFile(){ if(data!=nullptr){ UnmapViewOfFile(data); data=nullptr; } if(mapping!=nullptr){ CloseHandle(mapping); mapping=nullptr; } viewOffset=0ul; viewSize=0ul; } voidMemoryMappedFileStreamWindows::resizeFile(std::uint64_tsize){ if(file==INVALID_HANDLE_VALUE){ return; } //Seektothenewsize LARGE_INTEGERmoveBy{}; moveBy.QuadPart=static_cast<decltype(moveBy.QuadPart)>(size); if(SetFilePointerEx(file,moveBy,nullptr,FILE_BEGIN)==0){ return; } //Resizethefiletoit'scurrentposition if(SetEndOfFile(file)==0){ return; } fileSize=size; } }//namespacetrio #endif//TRIO_WINDOWS_FILE_MAPPING_AVAILABLE //*INDENT-ON*