diff options
-rw-r--r-- | core/CMakeLists.txt | 6 | ||||
-rw-r--r-- | core/src/Model/Assets.cpp | 5 | ||||
-rw-r--r-- | core/src/UI/UI_DatabaseView.cpp | 8 | ||||
-rw-r--r-- | core/src/UI/UI_Items.cpp | 11 | ||||
-rw-r--r-- | core/src/UI/UI_MainWindow.cpp | 8 | ||||
-rw-r--r-- | core/src/UI/UI_Templates.cpp | 8 | ||||
-rw-r--r-- | core/src/UI/UI_Workflows.cpp | 8 | ||||
-rw-r--r-- | core/src/Utils/IO/CstdioFile.cpp | 36 | ||||
-rw-r--r-- | core/src/Utils/IO/CstdioFile.hpp | 17 | ||||
-rw-r--r-- | core/src/Utils/IO/FileStream.cpp | 355 | ||||
-rw-r--r-- | core/src/Utils/IO/FileStream.hpp | 23 | ||||
-rw-r--r-- | core/src/Utils/IO/FileStream_Cstdio.inl | 126 | ||||
-rw-r--r-- | core/src/Utils/IO/FileStream_Custom.inl | 358 |
13 files changed, 587 insertions, 382 deletions
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 9885886..40c2491 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -84,6 +84,7 @@ list(APPEND CPLT_CORE_SOURCES # UI add_source_group(Utils src/Utils/IO/Archive.cpp + src/Utils/IO/CstdioFile.cpp src/Utils/IO/DataStream.cpp src/Utils/Sigslot.cpp src/Utils/StandardDirectories.cpp @@ -215,7 +216,10 @@ function(add_executable_variant TARGET_NAME) if(CMAKE_UNITY_BUILD) message("CpltCore: - using unity build") - set_target_properties(${TARGET_NAME} PROPERTIES UNITY_BUILD_MODE GROUP) + set_target_properties(${TARGET_NAME} PROPERTIES + UNITY_BUILD_MODE GROUP + UNITY_BUILD_UNIQUE_ID "CPLT_UNITY_ID" + ) else() message("CpltCore: - using regular build") endif() diff --git a/core/src/Model/Assets.cpp b/core/src/Model/Assets.cpp index 40f300e..fa29523 100644 --- a/core/src/Model/Assets.cpp +++ b/core/src/Model/Assets.cpp @@ -102,8 +102,11 @@ Project& AssetList::GetConnectedProject() const void AssetList::Reload() { + // TODO fix asset dicovery loading + mPrivate->Assets.clear(); + mPrivate->Cache.clear(); DiscoverFiles([this](SavedAsset asset) -> void { - Create(std::move(asset)); + mPrivate->Assets.insert(asset.Name, std::move(asset)); }); } diff --git a/core/src/UI/UI_DatabaseView.cpp b/core/src/UI/UI_DatabaseView.cpp index 96ef1d7..e128a59 100644 --- a/core/src/UI/UI_DatabaseView.cpp +++ b/core/src/UI/UI_DatabaseView.cpp @@ -16,7 +16,7 @@ #include <memory> #include <vector> -namespace { +namespace CPLT_UNITY_ID { // TODO move to Settings constexpr int kMaxEntriesPerPage = 32; @@ -638,15 +638,15 @@ public: } #pragma clang diagnostic pop }; -} // namespace +} // namespace CPLT_UNITY_ID void UI::DatabaseViewTab() { auto& gs = GlobalStates::GetInstance(); static Project* currentProject = nullptr; - static SalesTableView sales; - static PurchasesTableView purchases; + static CPLT_UNITY_ID::SalesTableView sales; + static CPLT_UNITY_ID::PurchasesTableView purchases; if (currentProject != gs.GetCurrentProject()) { currentProject = gs.GetCurrentProject(); diff --git a/core/src/UI/UI_Items.cpp b/core/src/UI/UI_Items.cpp index 5b84cb8..a33c61b 100644 --- a/core/src/UI/UI_Items.cpp +++ b/core/src/UI/UI_Items.cpp @@ -1,5 +1,4 @@ #include "UI.hpp" -#include "UI.hpp" #include "Model/GlobalStates.hpp" #include "Model/Project.hpp" @@ -9,7 +8,7 @@ #include <imgui.h> #include <imgui_stdlib.h> -namespace { +namespace CPLT_UNITY_ID { enum class ActionResult { @@ -229,7 +228,7 @@ void ItemListEditor(ItemList<T>& list) ItemListEntries<T>(list, selectedIdx); } -} // namespace +} // namespace CPLT_UNITY_ID void UI::ItemsTab() { @@ -237,15 +236,15 @@ void UI::ItemsTab() if (ImGui::BeginTabBar("ItemViewTabs")) { if (ImGui::BeginTabItem(I18N_TEXT("Products", L10N_ITEM_CATEGORY_PRODUCT))) { - ItemListEditor(gs.GetCurrentProject()->Products); + CPLT_UNITY_ID::ItemListEditor(gs.GetCurrentProject()->Products); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(I18N_TEXT("Factories", L10N_ITEM_CATEGORY_FACTORY))) { - ItemListEditor(gs.GetCurrentProject()->Factories); + CPLT_UNITY_ID::ItemListEditor(gs.GetCurrentProject()->Factories); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(I18N_TEXT("Customers", L10N_ITEM_CATEGORY_CUSTOMER))) { - ItemListEditor(gs.GetCurrentProject()->Customers); + CPLT_UNITY_ID::ItemListEditor(gs.GetCurrentProject()->Customers); ImGui::EndTabItem(); } ImGui::EndTabBar(); diff --git a/core/src/UI/UI_MainWindow.cpp b/core/src/UI/UI_MainWindow.cpp index ce6e2e0..d059359 100644 --- a/core/src/UI/UI_MainWindow.cpp +++ b/core/src/UI/UI_MainWindow.cpp @@ -13,7 +13,7 @@ namespace fs = std::filesystem; -namespace { +namespace CPLT_UNITY_ID { void ProjectTab_Normal() { auto& gs = GlobalStates::GetInstance(); @@ -181,7 +181,7 @@ void ProjectTab_NoProject() ImGui::EndPopup(); } } -} // namespace +} // namespace CPLT_UNITY_ID void UI::MainWindow() { @@ -199,9 +199,9 @@ void UI::MainWindow() if (ImGui::BeginTabItem(ICON_FA_FILE " " I18N_TEXT("Project", L10N_MAIN_WINDOW_TAB_PROJECT), nullptr)) { if (gs.HasCurrentProject()) { - ProjectTab_Normal(); + CPLT_UNITY_ID::ProjectTab_Normal(); } else { - ProjectTab_NoProject(); + CPLT_UNITY_ID::ProjectTab_NoProject(); } ImGui::EndTabItem(); } diff --git a/core/src/UI/UI_Templates.cpp b/core/src/UI/UI_Templates.cpp index 88ab5fc..cd15cb7 100644 --- a/core/src/UI/UI_Templates.cpp +++ b/core/src/UI/UI_Templates.cpp @@ -18,7 +18,7 @@ #include <utility> #include <variant> -namespace { +namespace CPLT_UNITY_ID { class TemplateUI { public: @@ -925,13 +925,13 @@ std::unique_ptr<TemplateUI> TemplateUI::CreateByKind(Template::Kind kind) } return nullptr; } -} // namespace +} // namespace CPLT_UNITY_ID void UI::TemplatesTab() { auto& project = *GlobalStates::GetInstance().GetCurrentProject(); - static std::unique_ptr<TemplateUI> openTemplate; + static std::unique_ptr<CPLT_UNITY_ID::TemplateUI> openTemplate; static AssetList::ListState state; bool openedDummy = true; @@ -951,7 +951,7 @@ void UI::TemplatesTab() ImGui::CloseCurrentPopup(); auto tmpl = project.Templates.Load(*state.SelectedAsset); - openTemplate = TemplateUI::CreateByKind(std::move(tmpl)); + openTemplate = CPLT_UNITY_ID::TemplateUI::CreateByKind(std::move(tmpl)); } ImGui::SameLine(); project.Templates.DisplayControls(state); diff --git a/core/src/UI/UI_Workflows.cpp b/core/src/UI/UI_Workflows.cpp index 036e532..c85850a 100644 --- a/core/src/UI/UI_Workflows.cpp +++ b/core/src/UI/UI_Workflows.cpp @@ -20,7 +20,7 @@ namespace ImNodes = ax::NodeEditor; -namespace { +namespace CPLT_UNITY_ID { class WorkflowUI { private: @@ -241,13 +241,13 @@ public: // TODO } }; -} // namespace +} // namespace CPLT_UNITY_ID void UI::WorkflowsTab() { auto& project = *GlobalStates::GetInstance().GetCurrentProject(); - static std::unique_ptr<WorkflowUI> openWorkflow; + static std::unique_ptr<CPLT_UNITY_ID::WorkflowUI> openWorkflow; static AssetList::ListState state; bool openedDummy = true; @@ -267,7 +267,7 @@ void UI::WorkflowsTab() ImGui::CloseCurrentPopup(); auto workflow = project.Workflows.Load(*state.SelectedAsset); - openWorkflow = std::make_unique<WorkflowUI>(std::move(workflow)); + openWorkflow = std::make_unique<CPLT_UNITY_ID::WorkflowUI>(std::move(workflow)); } ImGui::SameLine(); project.Workflows.DisplayControls(state); diff --git a/core/src/Utils/IO/CstdioFile.cpp b/core/src/Utils/IO/CstdioFile.cpp new file mode 100644 index 0000000..0f6378a --- /dev/null +++ b/core/src/Utils/IO/CstdioFile.cpp @@ -0,0 +1,36 @@ +#include "CstdioFile.hpp" + +#include "Utils/Macros.hpp" + +#pragma push_macro("MODE_STRING") +#undef MODE_STRING + +#if defined(_WIN32) +# define MODE_STRING(x) L##x +#else +# define MODE_STRING(x) x +#endif + +namespace CPLT_UNITY_ID { +auto GetModeString(FileUtils::IoMode mode) +{ + switch (mode) { + case FileUtils::IM_Read: return MODE_STRING("rb"); + case FileUtils::IM_WriteAppend: return MODE_STRING("ab"); + case FileUtils::IM_WriteTruncate: return MODE_STRING("wb"); + } + return MODE_STRING(""); +} +} // namespace CPLT_UNITY_ID + +#pragma pop_macro("MODE_STRING") + +FILE* FileUtils::OpenCstdioFile(const std::filesystem::path& path, IoMode mode) +{ +#ifdef _WIN32 + // std::filesystem::path::c_str() returns `const wchar_t*` under Windows, because NT uses UTF-16 natively + return _wfopen(path.c_str(), ::GetModeString(mode)); +#else + return fopen(path.c_str(), CPLT_UNITY_ID::GetModeString(mode)); +#endif +} diff --git a/core/src/Utils/IO/CstdioFile.hpp b/core/src/Utils/IO/CstdioFile.hpp new file mode 100644 index 0000000..e863dd5 --- /dev/null +++ b/core/src/Utils/IO/CstdioFile.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include <cstdio> +#include <filesystem> + +namespace FileUtils { + +enum IoMode +{ + IM_Read, + IM_WriteAppend, + IM_WriteTruncate, +}; + +FILE* OpenCstdioFile(const std::filesystem::path& path, IoMode mode); + +} // namespace FileUtils diff --git a/core/src/Utils/IO/FileStream.cpp b/core/src/Utils/IO/FileStream.cpp index b9ef2a7..8b83712 100644 --- a/core/src/Utils/IO/FileStream.cpp +++ b/core/src/Utils/IO/FileStream.cpp @@ -1,356 +1,7 @@ #include "FileStream.hpp" -#include <cstring> -#include <iostream> - -namespace fs = std::filesystem; - -#if defined(_WIN32) -# define WIN32_LEAN_AND_MEAN -# define NOMINMAX -# include <Windows.h> - -InputFileStream::InputFileStream(const fs::path& path) - : mOsFileHandle{ 0 } -{ - auto handle = reinterpret_cast<HANDLE*>(mOsFileHandle); - - *handle = CreateFileW( - path.c_str(), // fs::path::c_str() returns a wide string on Windows - GENERIC_READ, - /* No sharing */ 0, - /* Use default security*/ nullptr, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - /* No attribute template */ nullptr); - - // TODO handle error -} - -InputFileStream::~InputFileStream() -{ - auto handle = reinterpret_cast<HANDLE*>(mOsFileHandle); - CloseHandle(*handle); -} - -OutputFileStream::OutputFileStream(const fs::path& path, WriteMode mode) - : mOsFileHandle{ 0 } -{ - auto handle = reinterpret_cast<HANDLE*>(mOsFileHandle); - - DWORD creationDisposition; - switch (mode) { - case AppendFile: creationDisposition = OPEN_ALWAYS; break; - case TruncateFile: creationDisposition = CREATE_ALWAYS; break; - } - - *handle = CreateFileW( - path.c_str(), - GENERIC_WRITE, - /* No sharing */ 0, - /* Use default security*/ nullptr, - creationDisposition, - FILE_ATTRIBUTE_NORMAL, - /* No attribute template */ nullptr); - - // TODO handle error -} - -OutputFileStream::~OutputFileStream() -{ - auto handle = reinterpret_cast<HANDLE*>(mOsFileHandle); - CloseHandle(*handle); -} - -static IoResult::ErrorKind MapErrorCodeToIoResult(DWORD error) -{ - switch (error) { - // TODO - - default: - std::cerr << "Unimplemented win32 error code " << error << ", report bug immediately.\n"; - std::abort(); - } -} - -static IoResult ReadBytesDirect(HANDLE hFile, size_t byteCount, std::byte* bytes) -{ - DWORD bytesRead; - BOOL result = ReadFile(hFile, bytes, byteCount, &bytesRead, nullptr); - - if (result) { - return IoResult{ - .Error = IoResult::ERR_None, - .SystemError = 0, - .BytesMoved = bytesRead, - }; - } else { - DWORD errorCode = GetLastError(); - return IoResult{ - .Error = ::MapErrorCodeToIoResult(errorCode), - .SystemError = errorCode, - .BytesMoved = bytesRead, - }; - } -} - -static IoResult WriteBytesDirect(HANDLE hFile, size_t byteCount, const std::byte* bytes) -{ - DWORD bytesWritten; - BOOL result = WriteFile(hFile, bytes, byteCount, &bytesWritten, nullptr); - - if (result) { - return IoResult{ - .Error = IoResult::ERR_None, - .SystemError = 0, - .BytesMoved = bytesWritten, - }; - } else { - DWORD errorCode = GetLastError(); - return IoResult{ - .Error = ::MapErrorCodeToIoResult(errorCode), - .SystemError = errorCode, - .BytesMoved = bytesWritten, - }; - } -} - -#elif defined(__APPLE__) || defined(__linux__) -# include <fcntl.h> -# include <sys/stat.h> -# include <sys/types.h> -# include <unistd.h> - -InputFileStream::InputFileStream(const fs::path& path) - : mOsFileHandle{ 0 } -{ - auto fd = reinterpret_cast<int*>(mOsFileHandle); - *fd = open(path.c_str(), O_RDONLY); -} - -InputFileStream::~InputFileStream() -{ - auto fd = reinterpret_cast<int*>(mOsFileHandle); - close(*fd); -} - -OutputFileStream::OutputFileStream(const fs::path& path, WriteMode mode) - : mOsFileHandle{ 0 } -{ - auto fd = reinterpret_cast<int*>(mOsFileHandle); - - int flags = O_WRONLY | O_CREAT; - switch (mode) { - case AppendFile: flags |= O_APPEND; break; - case TruncateFile: flags |= O_TRUNC; break; - } - - *fd = open(path.c_str(), flags, 0644); -} - -OutputFileStream::~OutputFileStream() -{ - auto fd = reinterpret_cast<int*>(mOsFileHandle); - close(*fd); -} - -static IoResult::ErrorKind MapErrnoToIoResult(int err) -{ - switch (err) { - // TODO - case EFAULT: return IoResult::ERR_UnexpectedEof; - case EPERM: return IoResult::ERR_PermissionDenied; - case ENOSPC: return IoResult::ERR_OutOfSpace; - case EIO: return IoResult::ERR_Other; - - default: - std::cerr << "Unimplemented POSIX errno " << err << ", report bug immediately.\n"; - std::abort(); - } -} - -static IoResult ReadBytesDirect(const char* osFileHandle, size_t byteCount, std::byte* bytes) -{ - int fd = *reinterpret_cast<const int*>(osFileHandle); - int status = read(fd, bytes, byteCount); - - if (status == -1) { - int err = errno; - return IoResult{ - .Error = ::MapErrnoToIoResult(err), - .SystemError = (uint32_t)err, - .BytesMoved = 0, - }; - } else { - return IoResult{ - .Error = IoResult::ERR_None, - .SystemError = 0, - .BytesMoved = (size_t)status, // Equal to number of bytes read - }; - } -} - -static IoResult WriteBytesDirect(const char* osFileHandle, size_t byteCount, const std::byte* bytes) -{ - int fd = *reinterpret_cast<const int*>(osFileHandle); - int status = write(fd, bytes, byteCount); - - if (status == -1) { - int err = errno; - return IoResult{ - .Error = ::MapErrnoToIoResult(err), - .SystemError = (uint32_t)err, - .BytesMoved = 0, - }; - } else { - return IoResult{ - .Error = IoResult::ERR_None, - .SystemError = 0, - .BytesMoved = (size_t)status, // Equal to number of bytes read - }; - } -} - +#if defined(CPLT_FILESTREAM_USE_CSTDIO) +# include "FileStream_Cstdio.inl" #else -# error "Unsupported target platform." +# include "FileStream_Custom.inl" #endif - -int InputFileStream::GetReadInSize() const -{ - return mReadInSize; -} - -void InputFileStream::SetReadInSize(int size) -{ - if (size > mReadInSize) { - mReadInSize = size; - mBuffer = std::make_unique<std::byte[]>(size); - } -} - -bool InputFileStream::IsEof() const -{ - return mEof; -} - -IoResult InputFileStream::ReadBytes(size_t bufferLength, std::byte* buffer) -{ - // TODO reduce duplicated code - - auto bytesMoved = std::min<size_t>(mAvailableBytes, bufferLength); - - // On first call after construction, mFirstByteIdx will equal to mReadInSize, i.e. bytesAvailable == 0 - // and this call to std::memcpy will be no-op - std::memcpy(buffer, &mBuffer[mFirstByteIdx], bytesMoved); - mFirstByteIdx += (int)bytesMoved; - mAvailableBytes -= (int)bytesMoved; - buffer += bytesMoved; - - size_t bytesLeft = bufferLength - bytesMoved; - if (bytesLeft > mReadInSize) { - // Our buffer can't handle rest of the request, just skip the buffering step - - // Read rest of the data into buffer - { - auto result = ::ReadBytesDirect(mOsFileHandle, bytesLeft, buffer); - bytesMoved += result.BytesMoved; - - if (result.Error == IoResult::ERR_None) { - if (result.BytesMoved < mReadInSize) { - mEof = true; - } - } else { - goto end; - } - } - - // Refill our buffer - { - auto result = ::ReadBytesDirect(mOsFileHandle, mReadInSize, mBuffer.get()); - mFirstByteIdx = 0; - mAvailableBytes = (int)result.BytesMoved; - - if (result.Error == IoResult::ERR_None) { - if (result.BytesMoved < mReadInSize) { - mEof = true; - } - } else { - goto end; - } - } - } else if (bytesLeft > 0) { - // Our buffer can handle rest of the request, first buffer than supply the requested data - - // Refill our buffer - { - auto result = ::ReadBytesDirect(mOsFileHandle, mReadInSize, mBuffer.get()); - mFirstByteIdx = 0; - mAvailableBytes = (int)result.BytesMoved; - - if (result.Error == IoResult::ERR_None) { - if (result.BytesMoved < mReadInSize) { - mEof = true; - } - } else { - goto end; - } - } - - // Copy data into buffer - { - std::memcpy(buffer, &mBuffer[mFirstByteIdx], bytesLeft); - mFirstByteIdx += (int)bytesLeft; - bytesMoved += bytesLeft; - buffer += bytesLeft; - } - } else { - // Request completed already - } - -end: - return IoResult{ - .Error = IoResult::ERR_None, - .SystemError = 0, - .BytesMoved = bytesMoved, - }; -} - -int OutputFileStream::GetMaxBufferSize() const -{ - return mMaxBufferSize; -} - -void OutputFileStream::SetMaxBufferSize(int maxSize) -{ - FlushBuffer(); - if (maxSize > mMaxBufferSize) { - mMaxBufferSize = maxSize; - mBuffer = std::make_unique<std::byte[]>(maxSize); - } -} - -IoResult OutputFileStream::WriteBytes(size_t bufferLength, const std::byte* buffer) -{ - if (bufferLength + mCurrentBufferSize > mMaxBufferSize) { - FlushBuffer(); - - if (bufferLength > mMaxBufferSize) { - return ::WriteBytesDirect(mOsFileHandle, bufferLength, buffer); - } - } - - std::memcpy(mBuffer.get() + mCurrentBufferSize, buffer, bufferLength); - mCurrentBufferSize += (int)bufferLength; - - return IoResult{ - .Error = IoResult::ERR_None, - .SystemError = 0, - .BytesMoved = bufferLength, - }; -} - -void OutputFileStream::FlushBuffer() -{ - ::WriteBytesDirect(mOsFileHandle, mCurrentBufferSize, mBuffer.get()); - mCurrentBufferSize = 0; -} diff --git a/core/src/Utils/IO/FileStream.hpp b/core/src/Utils/IO/FileStream.hpp index 5b91632..453ddbe 100644 --- a/core/src/Utils/IO/FileStream.hpp +++ b/core/src/Utils/IO/FileStream.hpp @@ -5,6 +5,9 @@ #include <filesystem> #include <memory> +// TODO switch to custom when unit tests are ready and bugs are fixed +#define CPLT_FILESTREAM_USE_CSTDIO + struct IoResult { enum ErrorKind @@ -25,16 +28,20 @@ struct IoResult class InputFileStream { private: +#if defined(CPLT_FILESTREAM_USE_CSTDIO) + FILE* mFile; +#else alignas(void*) char mOsFileHandle[sizeof(void*)]; // mBuffer is always mReadInSize size std::unique_ptr<std::byte[]> mBuffer; - int mReadInSize; + int mReadInSize = 1024; int mFirstByteIdx = 0; int mAvailableBytes = 0; bool mEof = false; +#endif public: InputFileStream(const std::filesystem::path& path); @@ -42,8 +49,8 @@ public: InputFileStream(const InputFileStream&) = delete; InputFileStream& operator=(const InputFileStream&) = delete; - InputFileStream(InputFileStream&&) = default; - InputFileStream& operator=(InputFileStream&&) = default; + InputFileStream(InputFileStream&&); + InputFileStream& operator=(InputFileStream&&); int GetReadInSize() const; void SetReadInSize(int size); @@ -63,10 +70,14 @@ public: }; private: +#if defined(CPLT_FILESTREAM_USE_CSTDIO) + FILE* mFile; +#else alignas(void*) char mOsFileHandle[sizeof(void*)]; std::unique_ptr<std::byte[]> mBuffer; - int mMaxBufferSize; + int mMaxBufferSize = 1024; int mCurrentBufferSize = 0; +#endif public: OutputFileStream(const std::filesystem::path& path, WriteMode mode); @@ -74,8 +85,8 @@ public: OutputFileStream(const OutputFileStream&) = delete; OutputFileStream& operator=(const OutputFileStream&) = delete; - OutputFileStream(OutputFileStream&&) = default; - OutputFileStream& operator=(OutputFileStream&&) = default; + OutputFileStream(OutputFileStream&&); + OutputFileStream& operator=(OutputFileStream&&); int GetMaxBufferSize() const; void SetMaxBufferSize(int maxSize); diff --git a/core/src/Utils/IO/FileStream_Cstdio.inl b/core/src/Utils/IO/FileStream_Cstdio.inl new file mode 100644 index 0000000..ee55681 --- /dev/null +++ b/core/src/Utils/IO/FileStream_Cstdio.inl @@ -0,0 +1,126 @@ +// Note: included by FileStream.cpp conditionally, not compiled separately +#include "FileStream.hpp" + +#include "Utils/IO/CstdioFile.hpp" + +#include <cstdio> +#include <filesystem> + +namespace fs = std::filesystem; + +InputFileStream::InputFileStream(const fs::path& path) + : mFile{ FileUtils::OpenCstdioFile(path, FileUtils::IM_Read) } +{ +} + +InputFileStream::~InputFileStream() +{ + if (mFile) { + std::fclose(mFile); + } +} + +InputFileStream::InputFileStream(InputFileStream&& that) + : mFile{ that.mFile } +{ + that.mFile = nullptr; +} + +InputFileStream& InputFileStream::operator=(InputFileStream&& that) +{ + if (this == &that) return *this; + + if (mFile) { + std::fclose(mFile); + } + mFile = that.mFile; + that.mFile = nullptr; + + return *this; +} + +OutputFileStream::OutputFileStream(const fs::path& path, WriteMode mode) +{ + switch (mode) { + case AppendFile: mFile = FileUtils::OpenCstdioFile(path, FileUtils::IM_WriteAppend); break; + case TruncateFile: mFile = FileUtils::OpenCstdioFile(path, FileUtils::IM_WriteTruncate); break; + } +} + +OutputFileStream::~OutputFileStream() +{ + if (mFile) { + std::fclose(mFile); + } +} + +OutputFileStream::OutputFileStream(OutputFileStream&& that) + : mFile{ that.mFile } +{ + that.mFile = nullptr; +} + +OutputFileStream& OutputFileStream::operator=(OutputFileStream&& that) +{ + if (this == &that) return *this; + + if (mFile) { + std::fclose(mFile); + } + mFile = that.mFile; + that.mFile = nullptr; + + return *this; +} + +int InputFileStream::GetReadInSize() const +{ + return 0; +} + +void InputFileStream::SetReadInSize(int size) +{ + // No-op +} + +bool InputFileStream::IsEof() const +{ + return std::feof(mFile); +} + +IoResult InputFileStream::ReadBytes(size_t bufferLength, std::byte* buffer) +{ + auto bytesRead = std::fread(buffer, 1, bufferLength, mFile); + + return { + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = bytesRead, + }; +} + +int OutputFileStream::GetMaxBufferSize() const +{ + return 0; +} + +void OutputFileStream::SetMaxBufferSize(int maxSize) +{ + // No-op +} + +IoResult OutputFileStream::WriteBytes(size_t bufferLength, const std::byte* buffer) +{ + auto bytesWritten = std::fwrite(buffer, 1, bufferLength, mFile); + + return IoResult{ + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = bytesWritten, + }; +} + +void OutputFileStream::FlushBuffer() +{ + // No-op +} diff --git a/core/src/Utils/IO/FileStream_Custom.inl b/core/src/Utils/IO/FileStream_Custom.inl new file mode 100644 index 0000000..004dd01 --- /dev/null +++ b/core/src/Utils/IO/FileStream_Custom.inl @@ -0,0 +1,358 @@ +// Note: included by FileStream.cpp conditionally, not compiled separately +#include "FileStream.hpp" + +#include <cstring> +#include <filesystem> +#include <iostream> + +namespace fs = std::filesystem; + +#if defined(_WIN32) +# define WIN32_LEAN_AND_MEAN +# define NOMINMAX +# include <Windows.h> + +InputFileStream::InputFileStream(const fs::path& path) + : mOsFileHandle{ 0 } +{ + auto handle = reinterpret_cast<HANDLE*>(mOsFileHandle); + + *handle = CreateFileW( + path.c_str(), // fs::path::c_str() returns a wide string on Windows + GENERIC_READ, + /* No sharing */ 0, + /* Use default security*/ nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + /* No attribute template */ nullptr); + + // TODO handle error +} + +InputFileStream::~InputFileStream() +{ + auto handle = reinterpret_cast<HANDLE*>(mOsFileHandle); + CloseHandle(*handle); +} + +OutputFileStream::OutputFileStream(const fs::path& path, WriteMode mode) + : mOsFileHandle{ 0 } +{ + auto handle = reinterpret_cast<HANDLE*>(mOsFileHandle); + + DWORD creationDisposition; + switch (mode) { + case AppendFile: creationDisposition = OPEN_ALWAYS; break; + case TruncateFile: creationDisposition = CREATE_ALWAYS; break; + } + + *handle = CreateFileW( + path.c_str(), + GENERIC_WRITE, + /* No sharing */ 0, + /* Use default security*/ nullptr, + creationDisposition, + FILE_ATTRIBUTE_NORMAL, + /* No attribute template */ nullptr); + + // TODO handle error +} + +OutputFileStream::~OutputFileStream() +{ + auto handle = reinterpret_cast<HANDLE*>(mOsFileHandle); + CloseHandle(*handle); +} + +static IoResult::ErrorKind MapErrorCodeToIoResult(DWORD error) +{ + switch (error) { + // TODO + + default: + std::cerr << "Unimplemented win32 error code " << error << ", report bug immediately.\n"; + std::abort(); + } +} + +static IoResult ReadBytesDirect(HANDLE hFile, size_t byteCount, std::byte* bytes) +{ + DWORD bytesRead; + BOOL result = ReadFile(hFile, bytes, byteCount, &bytesRead, nullptr); + + if (result) { + return IoResult{ + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = bytesRead, + }; + } else { + DWORD errorCode = GetLastError(); + return IoResult{ + .Error = ::MapErrorCodeToIoResult(errorCode), + .SystemError = errorCode, + .BytesMoved = bytesRead, + }; + } +} + +static IoResult WriteBytesDirect(HANDLE hFile, size_t byteCount, const std::byte* bytes) +{ + DWORD bytesWritten; + BOOL result = WriteFile(hFile, bytes, byteCount, &bytesWritten, nullptr); + + if (result) { + return IoResult{ + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = bytesWritten, + }; + } else { + DWORD errorCode = GetLastError(); + return IoResult{ + .Error = ::MapErrorCodeToIoResult(errorCode), + .SystemError = errorCode, + .BytesMoved = bytesWritten, + }; + } +} + +#elif defined(__APPLE__) || defined(__linux__) +# include <fcntl.h> +# include <sys/stat.h> +# include <sys/types.h> +# include <unistd.h> + +InputFileStream::InputFileStream(const fs::path& path) + : mOsFileHandle{ 0 } +{ + auto fd = reinterpret_cast<int*>(mOsFileHandle); + *fd = open(path.c_str(), O_RDONLY); +} + +InputFileStream::~InputFileStream() +{ + auto fd = reinterpret_cast<int*>(mOsFileHandle); + close(*fd); +} + +OutputFileStream::OutputFileStream(const fs::path& path, WriteMode mode) + : mOsFileHandle{ 0 } +{ + auto fd = reinterpret_cast<int*>(mOsFileHandle); + + int flags = O_WRONLY | O_CREAT; + switch (mode) { + case AppendFile: flags |= O_APPEND; break; + case TruncateFile: flags |= O_TRUNC; break; + } + + *fd = open(path.c_str(), flags, 0644); +} + +OutputFileStream::~OutputFileStream() +{ + auto fd = reinterpret_cast<int*>(mOsFileHandle); + close(*fd); +} + +static IoResult::ErrorKind MapErrnoToIoResult(int err) +{ + switch (err) { + // TODO + case EFAULT: return IoResult::ERR_UnexpectedEof; + case EPERM: return IoResult::ERR_PermissionDenied; + case ENOSPC: return IoResult::ERR_OutOfSpace; + case EIO: return IoResult::ERR_Other; + + default: + std::cerr << "Unimplemented POSIX errno " << err << ", report bug immediately.\n"; + std::abort(); + } +} + +static IoResult ReadBytesDirect(const char* osFileHandle, size_t byteCount, std::byte* bytes) +{ + int fd = *reinterpret_cast<const int*>(osFileHandle); + int status = read(fd, bytes, byteCount); + + if (status == -1) { + int err = errno; + return IoResult{ + .Error = ::MapErrnoToIoResult(err), + .SystemError = (uint32_t)err, + .BytesMoved = 0, + }; + } else { + return IoResult{ + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = (size_t)status, // Equal to number of bytes read + }; + } +} + +static IoResult WriteBytesDirect(const char* osFileHandle, size_t byteCount, const std::byte* bytes) +{ + int fd = *reinterpret_cast<const int*>(osFileHandle); + int status = write(fd, bytes, byteCount); + + if (status == -1) { + int err = errno; + return IoResult{ + .Error = ::MapErrnoToIoResult(err), + .SystemError = (uint32_t)err, + .BytesMoved = 0, + }; + } else { + return IoResult{ + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = (size_t)status, // Equal to number of bytes read + }; + } +} + +#else +# error "Unsupported target platform." +#endif + +int InputFileStream::GetReadInSize() const +{ + return mReadInSize; +} + +void InputFileStream::SetReadInSize(int size) +{ + if (size > mReadInSize) { + mReadInSize = size; + mBuffer = std::make_unique<std::byte[]>(size); + } +} + +bool InputFileStream::IsEof() const +{ + return mEof; +} + +IoResult InputFileStream::ReadBytes(size_t bufferLength, std::byte* buffer) +{ + // TODO reduce duplicated code + + auto bytesMoved = std::min<size_t>(mAvailableBytes, bufferLength); + + // On first call after construction, mFirstByteIdx will equal to mReadInSize, i.e. bytesAvailable == 0 + // and this call to std::memcpy will be no-op + std::memcpy(buffer, &mBuffer[mFirstByteIdx], bytesMoved); + mFirstByteIdx += (int)bytesMoved; + mAvailableBytes -= (int)bytesMoved; + buffer += bytesMoved; + + size_t bytesLeft = bufferLength - bytesMoved; + if (bytesLeft > mReadInSize) { + // Our buffer can't handle rest of the request, just skip the buffering step + + // Read rest of the data into buffer + { + auto result = ::ReadBytesDirect(mOsFileHandle, bytesLeft, buffer); + bytesMoved += result.BytesMoved; + + if (result.Error == IoResult::ERR_None) { + if (result.BytesMoved < mReadInSize) { + mEof = true; + } + } else { + goto end; + } + } + + // Refill our buffer + { + auto result = ::ReadBytesDirect(mOsFileHandle, mReadInSize, mBuffer.get()); + mFirstByteIdx = 0; + mAvailableBytes = (int)result.BytesMoved; + + if (result.Error == IoResult::ERR_None) { + if (result.BytesMoved < mReadInSize) { + mEof = true; + } + } else { + goto end; + } + } + } else if (bytesLeft > 0) { + // Our buffer can handle rest of the request, first buffer than supply the requested data + + // Refill our buffer + { + auto result = ::ReadBytesDirect(mOsFileHandle, mReadInSize, mBuffer.get()); + mFirstByteIdx = 0; + mAvailableBytes = (int)result.BytesMoved; + + if (result.Error == IoResult::ERR_None) { + if (result.BytesMoved < mReadInSize) { + mEof = true; + } + } else { + goto end; + } + } + + // Copy data into buffer + { + std::memcpy(buffer, &mBuffer[mFirstByteIdx], bytesLeft); + mFirstByteIdx += (int)bytesLeft; + bytesMoved += bytesLeft; + buffer += bytesLeft; + } + } else { + // Request completed already + } + +end: + return IoResult{ + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = bytesMoved, + }; +} + +int OutputFileStream::GetMaxBufferSize() const +{ + return mMaxBufferSize; +} + +void OutputFileStream::SetMaxBufferSize(int maxSize) +{ + FlushBuffer(); + if (maxSize > mMaxBufferSize) { + mMaxBufferSize = maxSize; + mBuffer = std::make_unique<std::byte[]>(maxSize); + } +} + +IoResult OutputFileStream::WriteBytes(size_t bufferLength, const std::byte* buffer) +{ + if (bufferLength + mCurrentBufferSize > mMaxBufferSize) { + FlushBuffer(); + + if (bufferLength > mMaxBufferSize) { + return ::WriteBytesDirect(mOsFileHandle, bufferLength, buffer); + } + } + + std::memcpy(mBuffer.get() + mCurrentBufferSize, buffer, bufferLength); + mCurrentBufferSize += (int)bufferLength; + + return IoResult{ + .Error = IoResult::ERR_None, + .SystemError = 0, + .BytesMoved = bufferLength, + }; +} + +void OutputFileStream::FlushBuffer() +{ + ::WriteBytesDirect(mOsFileHandle, mCurrentBufferSize, mBuffer.get()); + mCurrentBufferSize = 0; +} |