IOStream can now be open on a file and write into it.

This commit is contained in:
2025-03-05 22:49:57 -05:00
parent 231fea81dd
commit 5fd3fc75eb
5 changed files with 282 additions and 8 deletions

View File

@@ -1,5 +1,6 @@
#pragma once
#include <Core/Common/NonNullPtr.h>
#include <Core/Common/String.h>
#include <Juliet.h>
@@ -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<IOStreamDataPayload> data);
int64 (*Seek)(NonNullPtr<IOStreamDataPayload> data, int64 offset, IOStreamSeekPivot pivot);
size_t (*Read)(NonNullPtr<IOStreamDataPayload> data, void* outBuffer, size_t size, NonNullPtr<IOStreamStatus> status);
size_t (*Write)(NonNullPtr<IOStreamDataPayload> data, const void* inBuffer, size_t size,
NonNullPtr<IOStreamStatus> status);
bool (*Flush)(NonNullPtr<IOStreamDataPayload> data, NonNullPtr<IOStreamStatus> status);
bool (*Close)(NonNullPtr<IOStreamDataPayload> 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<const IOStreamInterface> interface, NonNullPtr<IOStreamDataPayload> payload);
// Write formatted string into the stream.
extern JULIET_API size_t IOPrintf(NonNullPtr<IOStream> stream, _Printf_format_string_ const char* format, ...);
extern JULIET_API size_t IOWrite(NonNullPtr<IOStream> stream, const void* ptr, size_t size);
} // namespace Juliet

View File

@@ -3,6 +3,8 @@
#include <Core/Common/String.h>
#include <Core/HAL/IO/IOStream.h>
#include <Core/HAL/IO/IOStream_Private.h>
#include <Core/Memory/Allocator.h>
#include <cstdarg>
namespace Juliet
{
@@ -21,4 +23,60 @@ namespace Juliet
return Internal::IOFromFile(filename, mode);
}
IOStream* IOFromInterface(NonNullPtr<const IOStreamInterface> interface, NonNullPtr<IOStreamDataPayload> payload)
{
Assert(interface->Version >= sizeof(*interface.Get()));
auto stream = static_cast<IOStream*>(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<IOStream> 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<size_t>(writtenSize));
}
size_t IOWrite(NonNullPtr<IOStream> 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

View File

@@ -1,11 +1,18 @@
#pragma once
#include <Core/Common/String.h>
#include <Core/HAL/IO/IOStream.h>
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);

View File

@@ -1,11 +1,129 @@
#include <pch.h>
#include <Core/Common/EnumUtils.h>
#include <Core/Common/String.h>
#include <Core/HAL/IO/IOStream.h>
#include <Core/HAL/Win32.h>
#include <Core/Memory/Allocator.h>
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<IOStreamDataPayload> payload)
{
auto win32Payload = static_cast<Win32IOStreamDataPayload*>(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<IOStreamDataPayload> payload, int64 offset, IOStreamSeekPivot pivot)
{
auto win32Payload = static_cast<Win32IOStreamDataPayload*>(payload.Get());
if ((pivot == IOStreamSeekPivot::Current) && (win32Payload->SizeLeft > 0))
{
offset -= static_cast<int64>(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<IOStreamDataPayload> payload, const void* inBuffer, size_t size,
NonNullPtr<IOStreamStatus> status)
{
auto win32Payload = static_cast<Win32IOStreamDataPayload*>(payload.Get());
DWORD bytes;
if (win32Payload->SizeLeft)
{
if (!SetFilePointer(win32Payload->Handle, -static_cast<LONG>(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<DWORD>(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<IOStreamDataPayload> payload)
{
auto win32Payload = static_cast<Win32IOStreamDataPayload*>(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<Win32IOStreamDataPayload*>(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<char*>(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

View File

@@ -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;
}