aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/CMakeLists.txt6
-rw-r--r--core/src/Model/Assets.cpp5
-rw-r--r--core/src/UI/UI_DatabaseView.cpp8
-rw-r--r--core/src/UI/UI_Items.cpp11
-rw-r--r--core/src/UI/UI_MainWindow.cpp8
-rw-r--r--core/src/UI/UI_Templates.cpp8
-rw-r--r--core/src/UI/UI_Workflows.cpp8
-rw-r--r--core/src/Utils/IO/CstdioFile.cpp36
-rw-r--r--core/src/Utils/IO/CstdioFile.hpp17
-rw-r--r--core/src/Utils/IO/FileStream.cpp355
-rw-r--r--core/src/Utils/IO/FileStream.hpp23
-rw-r--r--core/src/Utils/IO/FileStream_Cstdio.inl126
-rw-r--r--core/src/Utils/IO/FileStream_Custom.inl358
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;
+}