From 5fd3fc75ebcc0c6d45f2525311c6bef889401eeb Mon Sep 17 00:00:00 2001 From: Patedam Date: Wed, 5 Mar 2025 22:49:57 -0500 Subject: [PATCH] IOStream can now be open on a file and write into it. --- Juliet/include/Core/HAL/IO/IOStream.h | 48 +++++ Juliet/src/Core/HAL/IO/IOStream.cpp | 58 ++++++ Juliet/src/Core/HAL/IO/IOStream_Private.h | 11 +- .../src/Core/HAL/IO/Win32/Win32IOStream.cpp | 169 +++++++++++++++++- JulietShaderCompiler/main.cpp | 4 + 5 files changed, 282 insertions(+), 8 deletions(-) diff --git a/Juliet/include/Core/HAL/IO/IOStream.h b/Juliet/include/Core/HAL/IO/IOStream.h index 564e413..844702c 100644 --- a/Juliet/include/Core/HAL/IO/IOStream.h +++ b/Juliet/include/Core/HAL/IO/IOStream.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -8,5 +9,52 @@ namespace Juliet // Opaque type struct IOStream; + struct IOStreamDataPayload + { + }; + + enum class IOStreamStatus : uint8 + { + Ready, + Error, + EndOfFile, + NotReady, + ReadOnly, + WriteOnly + }; + + enum class IOStreamSeekPivot : uint8 + { + Begin, + Current, + End, + Count + }; + + // IOStream can be opened on a file or memory, or anything else. + // Use the interface to make it transparent to the user. + struct IOStreamInterface + { + uint32 Version; + + int64 (*Size)(NonNullPtr data); + + int64 (*Seek)(NonNullPtr data, int64 offset, IOStreamSeekPivot pivot); + size_t (*Read)(NonNullPtr data, void* outBuffer, size_t size, NonNullPtr status); + size_t (*Write)(NonNullPtr data, const void* inBuffer, size_t size, + NonNullPtr status); + bool (*Flush)(NonNullPtr data, NonNullPtr status); + + bool (*Close)(NonNullPtr data); + }; + extern JULIET_API IOStream* IOFromFile(String filename, String mode); + + // Let you use an interface to open any io. Is used internally by IOFromFile + extern JULIET_API IOStream* IOFromInterface(NonNullPtr interface, NonNullPtr payload); + + // Write formatted string into the stream. + extern JULIET_API size_t IOPrintf(NonNullPtr stream, _Printf_format_string_ const char* format, ...); + extern JULIET_API size_t IOWrite(NonNullPtr stream, const void* ptr, size_t size); + } // namespace Juliet diff --git a/Juliet/src/Core/HAL/IO/IOStream.cpp b/Juliet/src/Core/HAL/IO/IOStream.cpp index 060835a..3be114b 100644 --- a/Juliet/src/Core/HAL/IO/IOStream.cpp +++ b/Juliet/src/Core/HAL/IO/IOStream.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include namespace Juliet { @@ -21,4 +23,60 @@ namespace Juliet return Internal::IOFromFile(filename, mode); } + + IOStream* IOFromInterface(NonNullPtr interface, NonNullPtr payload) + { + Assert(interface->Version >= sizeof(*interface.Get())); + + auto stream = static_cast(Calloc(1, sizeof(IOStream))); + if (stream) + { + IOStreamInterface* dstInterface = &stream->Interface; + const IOStreamInterface* srcInterface = interface.Get(); + static_assert(sizeof(*(dstInterface)) == sizeof(*(srcInterface)), "Source and Destination type mismatch"); + MemCopy(dstInterface, srcInterface, sizeof(*srcInterface)); + stream->Data = payload.Get(); + } + return stream; + } + + size_t IOPrintf(NonNullPtr stream, _Printf_format_string_ const char* format, ...) + { + // TODO: Juliet format function should be able to allocate on scratch arena here. + // This buffer should be big enough until then + char formattedBuffer[4096]; + va_list args; + + va_start(args, format); + int writtenSize = vsprintf_s(formattedBuffer, format, args); // Cast to void to ignore the return type. TODO : Juliet format function + va_end(args); + + Assert(writtenSize >= 0); + + return IOWrite(stream, formattedBuffer, static_cast(writtenSize)); + } + + size_t IOWrite(NonNullPtr stream, const void* ptr, size_t size) + { + if (!stream->Interface.Write) + { + stream->Status = IOStreamStatus::ReadOnly; + Log(LogLevel::Error, LogCategory::Core, "Trying to write to a readonly IOStream"); + return 0; + } + + stream->Status = IOStreamStatus::Ready; + + if (size == 0) + { + return 0; + } + + size_t writtenBytes = stream->Interface.Write(stream->Data, ptr, size, &stream->Status); + if ((writtenBytes == 0) && (stream->Status == IOStreamStatus::Ready)) + { + stream->Status = IOStreamStatus::Error; + } + return writtenBytes; + } } // namespace Juliet diff --git a/Juliet/src/Core/HAL/IO/IOStream_Private.h b/Juliet/src/Core/HAL/IO/IOStream_Private.h index 038411c..daa8165 100644 --- a/Juliet/src/Core/HAL/IO/IOStream_Private.h +++ b/Juliet/src/Core/HAL/IO/IOStream_Private.h @@ -1,11 +1,18 @@ #pragma once #include +#include namespace Juliet { - struct IOStream; -} + struct IOStream + { + IOStreamInterface Interface; + IOStreamDataPayload* Data; + IOStreamStatus Status; + }; +} // namespace Juliet + namespace Juliet::Internal { extern JULIET_API IOStream* IOFromFile(String filename, String mode); diff --git a/Juliet/src/Core/HAL/IO/Win32/Win32IOStream.cpp b/Juliet/src/Core/HAL/IO/Win32/Win32IOStream.cpp index 9a950bd..68895b1 100644 --- a/Juliet/src/Core/HAL/IO/Win32/Win32IOStream.cpp +++ b/Juliet/src/Core/HAL/IO/Win32/Win32IOStream.cpp @@ -1,11 +1,129 @@ #include +#include #include #include #include +#include namespace Juliet::Internal { + namespace + { + struct Win32IOStreamDataPayload : IOStreamDataPayload + { + HANDLE Handle; + void* Data; + size_t SizeLeft; + bool IsAppending; + bool ShouldAutoClose; + }; + + constexpr size_t kFileReadBufferSize = 1024; + + int64 FileSize(NonNullPtr payload) + { + auto win32Payload = static_cast(payload.Get()); + LARGE_INTEGER size; + + if (!GetFileSizeEx(win32Payload->Handle, &size)) + { + Log(LogLevel::Error, LogCategory::Core, "IoStream:Size error: %d", GetLastError()); + return 0; + } + + return size.QuadPart; + } + + int64 FileSeek(NonNullPtr payload, int64 offset, IOStreamSeekPivot pivot) + { + auto win32Payload = static_cast(payload.Get()); + if ((pivot == IOStreamSeekPivot::Current) && (win32Payload->SizeLeft > 0)) + { + offset -= static_cast(win32Payload->SizeLeft); + } + win32Payload->SizeLeft = 0; + + DWORD windowsPivot = 0; + switch (pivot) + { + case IOStreamSeekPivot::Begin: windowsPivot = FILE_BEGIN; break; + case IOStreamSeekPivot::Current: windowsPivot = FILE_CURRENT; break; + case IOStreamSeekPivot::End: windowsPivot = FILE_END; break; + case IOStreamSeekPivot::Count: + Log(LogLevel::Error, LogCategory::Core, "IoStream:Seek: Invalid Pivot value"); + return -1; + } + static_assert(ToUnderlying(IOStreamSeekPivot::Count) == 3, "IOStreamSeekPivot::Count must be 3"); + + LARGE_INTEGER windowsOffset; + windowsOffset.QuadPart = offset; + if (!SetFilePointerEx(win32Payload->Handle, windowsOffset, &windowsOffset, windowsPivot)) + { + Log(LogLevel::Error, LogCategory::Core, "IoStream:Seek:Error: %d", GetLastError()); + return 0; + } + return windowsOffset.QuadPart; + } + + size_t FileWrite(NonNullPtr payload, const void* inBuffer, size_t size, + NonNullPtr status) + { + auto win32Payload = static_cast(payload.Get()); + DWORD bytes; + + if (win32Payload->SizeLeft) + { + if (!SetFilePointer(win32Payload->Handle, -static_cast(win32Payload->SizeLeft), nullptr, FILE_CURRENT)) + { + Log(LogLevel::Error, LogCategory::Core, "IoStream:FileWrite:Error seeking in datastream: %d", GetLastError()); + return 0; + } + win32Payload->SizeLeft = 0; + } + + // if in append mode, we must go to the EOF before write + if (win32Payload->IsAppending) + { + LARGE_INTEGER windowsOffset; + windowsOffset.QuadPart = 0; + if (!SetFilePointerEx(win32Payload->Handle, windowsOffset, &windowsOffset, FILE_END)) + { + Log(LogLevel::Error, LogCategory::Core, "IoStream:FileWrite:Error seeking in datastream: %d", GetLastError()); + return 0; + } + } + + if (!WriteFile(win32Payload->Handle, inBuffer, static_cast(size), &bytes, nullptr)) + { + Log(LogLevel::Error, LogCategory::Core, "IoStream:FileWrite:Error writing to datastream: %d", GetLastError()); + return 0; + } + if (bytes == 0 && size > 0) + { + *status = IOStreamStatus::NotReady; + } + return bytes; + } + + bool FileClose(NonNullPtr payload) + { + auto win32Payload = static_cast(payload.Get()); + if (win32Payload->Handle != INVALID_HANDLE_VALUE) + { + if (win32Payload->ShouldAutoClose) + { + CloseHandle(win32Payload->Handle); + } + win32Payload->Handle = INVALID_HANDLE_VALUE; + } + SafeFree(win32Payload->Data); + SafeFree(win32Payload); + return true; + } + + } // namespace + IOStream* IOFromFile(String filename, String mode) { // "r" = reading, file must exist @@ -33,9 +151,10 @@ namespace Juliet::Internal } #endif - DWORD openExisting = ContainsChar(mode, 'r') ? OPEN_EXISTING : 0; - DWORD createAlways = ContainsChar(mode, 'w') ? CREATE_ALWAYS : 0; - DWORD openAlways = ContainsChar(mode, 'a') ? OPEN_ALWAYS : 0; + DWORD openExisting = ContainsChar(mode, 'r') ? OPEN_EXISTING : 0; + DWORD createAlways = ContainsChar(mode, 'w') ? CREATE_ALWAYS : 0; + const bool isAppending = ContainsChar(mode, 'a'); + DWORD openAlways = isAppending ? OPEN_ALWAYS : 0; bool hasPlus = ContainsChar(mode, '+'); DWORD canRead = openExisting || hasPlus ? GENERIC_READ : 0; @@ -49,11 +168,49 @@ namespace Juliet::Internal HANDLE hFile = CreateFileA(CStr(filename), (canWrite | canRead), (canWrite) ? 0 : FILE_SHARE_READ, nullptr, (openExisting | createAlways | openAlways), FILE_ATTRIBUTE_NORMAL, nullptr); - if (FAILED(hFile)) + if (hFile == INVALID_HANDLE_VALUE) { - Log(LogLevel::Error, LogCategory::Core, "IOFromFile: CreateFileW failed"); + Log(LogLevel::Error, LogCategory::Core, "IOFromFile: couldn't open %s", CStr(filename)); + return nullptr; } - return nullptr; + constexpr bool autoClose = true; + auto payload = static_cast(Calloc(1, sizeof(Win32IOStreamDataPayload))); + if (!payload) + { + if (autoClose) + { + CloseHandle(hFile); + } + return nullptr; + } + + IOStreamInterface interface = {}; + interface.Version = sizeof(interface); + if (GetFileType(hFile) == FILE_TYPE_DISK) + { + interface.Size = FileSize; + interface.Seek = FileSeek; + } + interface.Write = FileWrite; + interface.Close = FileClose; + + payload->Handle = hFile; + payload->IsAppending = isAppending; + payload->ShouldAutoClose = autoClose; + + payload->Data = static_cast(Malloc(kFileReadBufferSize)); + if (!payload->Data) + { + interface.Close(payload); + return nullptr; + } + + IOStream* stream = IOFromInterface(&interface, payload); + if (!stream) + { + interface.Close(payload); + } + return stream; } } // namespace Juliet::Internal diff --git a/JulietShaderCompiler/main.cpp b/JulietShaderCompiler/main.cpp index 10ed0b3..3ca9569 100644 --- a/JulietShaderCompiler/main.cpp +++ b/JulietShaderCompiler/main.cpp @@ -45,5 +45,9 @@ void Compile() int main(int argc, char* argv[]) { auto* stream = IOFromFile(ConstString("XF"), ConstString("w")); + + IOPrintf(stream, ":)"); + IOPrintf(stream, "%d%s", 1234, "Hello World!"); + return 0; }