diff --git a/Src/IACore/CMakeLists.txt b/Src/IACore/CMakeLists.txt index dde91b4..4e73eb7 100644 --- a/Src/IACore/CMakeLists.txt +++ b/Src/IACore/CMakeLists.txt @@ -1,4 +1,5 @@ set(SRC_FILES + "imp/cpp/IPC.cpp" "imp/cpp/JSON.cpp" "imp/cpp/IACore.cpp" "imp/cpp/Logger.cpp" diff --git a/Src/IACore/imp/cpp/FileOps.cpp b/Src/IACore/imp/cpp/FileOps.cpp index 9abfded..0cdcb28 100644 --- a/Src/IACore/imp/cpp/FileOps.cpp +++ b/Src/IACore/imp/cpp/FileOps.cpp @@ -28,15 +28,93 @@ namespace IACore #if IA_PLATFORM_WINDOWS ::UnmapViewOfFile(std::get<1>(handles)); ::CloseHandle(std::get<2>(handles)); - ::CloseHandle(std::get<0>(handles)); + + if (std::get<0>(handles) != INVALID_HANDLE_VALUE) + ::CloseHandle(std::get<0>(handles)); #elif IA_PLATFORM_UNIX ::munmap(std::get<1>(handles), (SIZE_T) std::get<2>(handles)); - ::close((INT32) ((UINT64) std::get<0>(handles))); + const auto fd = (INT32) ((UINT64) std::get<0>(handles)); + if (fd != -1) + ::close(fd); #else # error "IACore FileOps does not support this platform" #endif } + Expected FileOps::MapSharedMemory(IN CONST String &name, IN SIZE_T size, IN BOOL isOwner) + { +#if IA_PLATFORM_WINDOWS + int wchars_num = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, NULL, 0); + std::wstring wName(wchars_num, 0); + MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, &wName[0], wchars_num); + + HANDLE hMap = NULL; + if (isOwner) + hMap = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, (DWORD) (size >> 32), + (DWORD) (size & 0xFFFFFFFF), wName.c_str()); + else + hMap = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, wName.c_str()); + + if (hMap == NULL) + return MakeUnexpected( + std::format("Failed to {} shared memory '{}'", isOwner ? "owner" : "consumer", name.c_str())); + + const auto result = static_cast(MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, size)); + if (result == NULL) + { + CloseHandle(hMap); + return MakeUnexpected(std::format("Failed to map view of shared memory '{}'", name.c_str())); + } + + s_mappedFiles[result] = std::make_tuple((PVOID) INVALID_HANDLE_VALUE, (PVOID) result, (PVOID) hMap); + return result; + +#elif IA_PLATFORM_UNIX + int fd = -1; + if (isOwner) + { + fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666); + if (fd != -1) + { + if (ftruncate(fd, size) == -1) + { + close(fd); + shm_unlink(name.c_str()); + return MakeUnexpected(std::format("Failed to truncate shared memory '{}'", name.c_str())); + } + } + } + else + fd = shm_open(name.c_str(), O_RDWR, 0666); + + if (fd == -1) + return MakeUnexpected( + std::format("Failed to {} shared memory '{}'", isOwner ? "owner" : "consumer", name.c_str())); + + void *addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (addr == MAP_FAILED) + { + close(fd); + return MakeUnexpected(std::format("Failed to mmap shared memory '{}'", name.c_str())); + } + + const auto result = static_cast(addr); + + s_mappedFiles[result] = std::make_tuple((PVOID) ((UINT64) fd), (PVOID) addr, (PVOID) size); + return result; + +#else +# error "IACore FileOps does not support this platform" +#endif + } + + VOID FileOps::UnlinkSharedMemory(IN CONST String &name) + { +#if IA_PLATFORM_UNIX + shm_unlink(name.c_str()); +#endif + } + Expected FileOps::MapFile(IN CONST FilePath &path, OUT SIZE_T &size) { #if IA_PLATFORM_WINDOWS @@ -192,7 +270,7 @@ namespace IACore result = "./" + result; return FilePath(result); #else -# error "unreachable. previous checks must have stopped compilation beforehand .unsupported platform." +# error "IACore FileOps does not support this platform" #endif } } // namespace IACore \ No newline at end of file diff --git a/Src/IACore/imp/cpp/IPC.cpp b/Src/IACore/imp/cpp/IPC.cpp new file mode 100644 index 0000000..6bdf851 --- /dev/null +++ b/Src/IACore/imp/cpp/IPC.cpp @@ -0,0 +1,22 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2025 IAS (ias@iasoft.dev) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include + +namespace IACore +{ + +} \ No newline at end of file diff --git a/Src/IACore/imp/cpp/SocketOps.cpp b/Src/IACore/imp/cpp/SocketOps.cpp index 7ef4943..324f45c 100644 --- a/Src/IACore/imp/cpp/SocketOps.cpp +++ b/Src/IACore/imp/cpp/SocketOps.cpp @@ -18,6 +18,54 @@ namespace IACore { + BOOL SocketOps::Listen(IN SocketHandle sock, IN INT32 queueSize) + { + return listen(sock, queueSize) == 0; + } + + SocketHandle SocketOps::CreateUnixSocket() + { + return socket(AF_UNIX, SOCK_STREAM, 0); + } + + BOOL SocketOps::BindUnixSocket(IN SocketHandle sock, IN PCCHAR path) + { + if (!IS_VALID_SOCKET(sock)) + return FALSE; + + UNLINK_FILE(path); + + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + + size_t maxLen = sizeof(addr.sun_path) - 1; + + strncpy(addr.sun_path, path, maxLen); + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1) + return FALSE; + + return TRUE; + } + + BOOL SocketOps::ConnectUnixSocket(IN SocketHandle sock, IN PCCHAR path) + { + if (!IS_VALID_SOCKET(sock)) + return FALSE; + + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + + size_t maxLen = sizeof(addr.sun_path) - 1; + + strncpy(addr.sun_path, path, maxLen); + + if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1) + return FALSE; + + return TRUE; + } + BOOL SocketOps::IsPortAvailable(IN UINT16 port, IN INT32 type) { SocketHandle sock = socket(AF_INET, type, IPPROTO_UDP); diff --git a/Src/IACore/inc/IACore/ADT/RingBuffer.hpp b/Src/IACore/inc/IACore/ADT/RingBuffer.hpp new file mode 100644 index 0000000..81386d7 --- /dev/null +++ b/Src/IACore/inc/IACore/ADT/RingBuffer.hpp @@ -0,0 +1,143 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2025 IAS (ias@iasoft.dev) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +namespace IACore +{ + class RingBufferView + { + public: + STATIC CONSTEXPR UINT16 PACKET_ID_SKIP = 0; + + struct ControlBlock + { + alignas(64) Atomic WriteOffset{0}; + alignas(64) Atomic ReadOffset{0}; + UINT32 Capacity; + + // Padding to ensure data starts on a cache line + UINT8 _padding[64 - sizeof(UINT32) * 3]; + }; + + // All of the data in ring buffer will be stored as packets + struct PacketHeader + { + UINT16 ID{}; + UINT16 PayloadSize{}; + }; + + public: + INLINE RingBufferView(IN Span buffer); + + INLINE BOOL Pop(OUT PacketHeader &outHeader, OUT Span outBuffer); + INLINE BOOL Push(IN UINT16 packetID, IN Span data); + + INLINE ControlBlock *GetControlBlock(); + + private: + PUINT8 m_dataPtr{}; + UINT32 m_capacity{}; + ControlBlock *m_controlBlock{}; + + private: + INLINE VOID WritePacket(IN UINT32 offset, IN UINT16 id, IN PCVOID data, IN UINT16 dataSize); + }; +} // namespace IACore + +namespace IACore +{ + RingBufferView::RingBufferView(IN Span buffer) + { + IA_ASSERT(buffer.size() > sizeof(ControlBlock)); + + m_controlBlock = reinterpret_cast(buffer.data()); + m_dataPtr = buffer.data() + sizeof(ControlBlock); + m_controlBlock->Capacity = m_capacity = buffer.size() - sizeof(ControlBlock); + } + + BOOL RingBufferView::Pop(OUT PacketHeader &outHeader, OUT Span outBuffer) + { + UINT32 currentWriteOffset = m_controlBlock->WriteOffset.load(std::memory_order_acquire); + UINT32 currentReadOffset = m_controlBlock->ReadOffset.load(std::memory_order_acquire); + + if (currentReadOffset == currentWriteOffset) + return false; + + const auto header = reinterpret_cast(m_dataPtr + currentReadOffset); + + if (header->ID == PACKET_ID_SKIP) + { + m_controlBlock->ReadOffset.store(0, std::memory_order_release); + return Pop(outHeader, outBuffer); + } + + outHeader = *header; + if (outHeader.PayloadSize > outBuffer.size()) + return false; + + memcpy(outBuffer.data(), m_dataPtr + currentReadOffset + sizeof(PacketHeader), outHeader.PayloadSize); + m_controlBlock->ReadOffset.store(currentReadOffset + sizeof(PacketHeader) + outHeader.PayloadSize, std::memory_order_release); + + return true; + } + + BOOL RingBufferView::Push(IN UINT16 packetID, IN Span data) + { + IA_ASSERT(data.size() <= UINT16_MAX); + + UINT32 currentWriteOffset = m_controlBlock->WriteOffset.load(std::memory_order_acquire); + UINT32 currentReadOffset = m_controlBlock->ReadOffset.load(std::memory_order_acquire); + + UINT32 requiredSpace = sizeof(PacketHeader) + data.size(); + if (currentWriteOffset + requiredSpace <= m_capacity) + { + WritePacket(currentWriteOffset, packetID, data.data(), static_cast(data.size())); + m_controlBlock->WriteOffset.store(currentWriteOffset + requiredSpace, std::memory_order_release); + return true; + } + + UINT32 remainingAtEnd = m_capacity - currentWriteOffset; + if (requiredSpace < currentReadOffset) + { + if (remainingAtEnd >= sizeof(PacketHeader)) + { + const auto p = PacketHeader{PACKET_ID_SKIP, 0}; + memcpy(m_dataPtr + currentWriteOffset, &p, sizeof(p)); + } + + WritePacket(0, packetID, data.data(), static_cast(data.size())); + m_controlBlock->WriteOffset.store(requiredSpace, std::memory_order_release); + return true; + } + + return false; + } + + RingBufferView::ControlBlock *RingBufferView::GetControlBlock() + { + return m_controlBlock; + } + + VOID RingBufferView::WritePacket(IN UINT32 offset, IN UINT16 id, IN PCVOID data, IN UINT16 dataSize) + { + PacketHeader h = {id, dataSize}; + memcpy(m_dataPtr + offset, &h, sizeof(PacketHeader)); + memcpy(m_dataPtr + offset + sizeof(PacketHeader), data, dataSize); + } +} // namespace IACore \ No newline at end of file diff --git a/Src/IACore/inc/IACore/FileOps.hpp b/Src/IACore/inc/IACore/FileOps.hpp index e3d5960..2e2b483 100644 --- a/Src/IACore/inc/IACore/FileOps.hpp +++ b/Src/IACore/inc/IACore/FileOps.hpp @@ -30,6 +30,10 @@ namespace IACore STATIC VOID UnmapFile(IN PCUINT8 mappedPtr); STATIC Expected MapFile(IN CONST FilePath &path, OUT SIZE_T &size); + // @param `isOwner` TRUE to allocate/truncate. FALSE to just open. + STATIC Expected MapSharedMemory(IN CONST String &name, IN SIZE_T size, IN BOOL isOwner); + STATIC VOID UnlinkSharedMemory(IN CONST String &name); + STATIC Expected StreamFromFile(IN CONST FilePath &path); STATIC Expected StreamToFile(IN CONST FilePath &path, IN BOOL overwrite = false); diff --git a/Src/IACore/inc/IACore/IPC.hpp b/Src/IACore/inc/IACore/IPC.hpp new file mode 100644 index 0000000..cc3d3d4 --- /dev/null +++ b/Src/IACore/inc/IACore/IPC.hpp @@ -0,0 +1,34 @@ +// IACore-OSS; The Core Library for All IA Open Source Projects +// Copyright (C) 2025 IAS (ias@iasoft.dev) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +namespace IACore +{ + class IPC_Node + { + public: + + }; + + class IPC_Server + { + public: + + }; +} \ No newline at end of file diff --git a/Src/IACore/inc/IACore/SocketOps.hpp b/Src/IACore/inc/IACore/SocketOps.hpp index 879d3c6..2fd76d6 100644 --- a/Src/IACore/inc/IACore/SocketOps.hpp +++ b/Src/IACore/inc/IACore/SocketOps.hpp @@ -21,19 +21,24 @@ #if IA_PLATFORM_WINDOWS # include +# include +# include # pragma comment(lib, "ws2_32.lib") # define CLOSE_SOCKET(s) closesocket(s) # define IS_VALID_SOCKET(s) (s != INVALID_SOCKET) +# define UNLINK_FILE(p) DeleteFileA(p) using SocketHandle = SOCKET; #elif IA_PLATFORM_UNIX +# include # include # include # include # define CLOSE_SOCKET(s) close(s) # define IS_VALID_SOCKET(s) (s >= 0) # define INVALID_SOCKET -1 +# define UNLINK_FILE(p) unlink(p) using SocketHandle = int; #else @@ -72,6 +77,13 @@ namespace IACore return IsPortAvailable(port, SOCK_DGRAM); } + STATIC BOOL Listen(IN SocketHandle sock, IN INT32 queueSize = 5); + + STATIC SocketHandle CreateUnixSocket(); + + STATIC BOOL BindUnixSocket(IN SocketHandle sock, IN PCCHAR path); + STATIC BOOL ConnectUnixSocket(IN SocketHandle sock, IN PCCHAR path); + private: STATIC BOOL IsPortAvailable(IN UINT16 port, IN INT32 type); };