Compare commits
21 Commits
d9756df436
...
_old_main
| Author | SHA1 | Date | |
|---|---|---|---|
| 09575d6119 | |||
| 0476ed2392 | |||
| 5ea25c9368 | |||
| 99e2e9bfce | |||
| d18b22d702 | |||
| b3580f3f00 | |||
| 8f2e44eb23 | |||
| 9f75db8f95 | |||
| d3721e9144 | |||
| 4af30facaf | |||
| bd9d75e2a2 | |||
| 4a6630384b | |||
| cf18f0d55c | |||
| c2f67a0dcc | |||
| b8a613daa5 | |||
| ad0a18c945 | |||
| 3de172ff6c | |||
| 0674330fb1 | |||
| 4033cfb710 | |||
| 131ad3120c | |||
| d24c7f3246 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -36,7 +36,6 @@
|
|||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
!.vscode/*.code-snippets
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
@ -47,4 +46,5 @@
|
|||||||
*.vsix
|
*.vsix
|
||||||
|
|
||||||
.cache/
|
.cache/
|
||||||
|
.local/
|
||||||
Build/
|
Build/
|
||||||
|
|||||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -1,9 +0,0 @@
|
|||||||
[submodule "Vendor/mimalloc"]
|
|
||||||
path = Vendor/mimalloc
|
|
||||||
url = https://github.com/microsoft/mimalloc
|
|
||||||
[submodule "Vendor/expected"]
|
|
||||||
path = Vendor/expected
|
|
||||||
url = https://github.com/TartanLlama/expected
|
|
||||||
[submodule "Vendor/unordered_dense"]
|
|
||||||
path = Vendor/unordered_dense
|
|
||||||
url = https://github.com/martinus/unordered_dense
|
|
||||||
127
CMake/FindDeps.cmake
Normal file
127
CMake/FindDeps.cmake
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
httplib
|
||||||
|
GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git
|
||||||
|
GIT_TAG v0.14.3
|
||||||
|
SYSTEM
|
||||||
|
EXCLUDE_FROM_ALL
|
||||||
|
OVERRIDE_FIND_PACKAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
OpenSSL
|
||||||
|
GIT_REPOSITORY https://github.com/janbar/openssl-cmake.git
|
||||||
|
GIT_TAG master
|
||||||
|
SYSTEM
|
||||||
|
EXCLUDE_FROM_ALL
|
||||||
|
OVERRIDE_FIND_PACKAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
nlohmann_json
|
||||||
|
GIT_REPOSITORY https://github.com/nlohmann/json.git
|
||||||
|
GIT_TAG v3.12.0
|
||||||
|
SYSTEM
|
||||||
|
EXCLUDE_FROM_ALL
|
||||||
|
OVERRIDE_FIND_PACKAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
glaze
|
||||||
|
GIT_REPOSITORY https://github.com/stephenberry/glaze.git
|
||||||
|
GIT_TAG v6.1.0
|
||||||
|
SYSTEM
|
||||||
|
EXCLUDE_FROM_ALL
|
||||||
|
OVERRIDE_FIND_PACKAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
simdjson
|
||||||
|
GIT_REPOSITORY https://github.com/simdjson/simdjson.git
|
||||||
|
GIT_TAG v4.2.2
|
||||||
|
SYSTEM
|
||||||
|
EXCLUDE_FROM_ALL
|
||||||
|
OVERRIDE_FIND_PACKAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
ZLIB
|
||||||
|
GIT_REPOSITORY https://github.com/madler/zlib.git
|
||||||
|
GIT_TAG v1.3.1
|
||||||
|
SYSTEM
|
||||||
|
EXCLUDE_FROM_ALL
|
||||||
|
OVERRIDE_FIND_PACKAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
zstd
|
||||||
|
GIT_REPOSITORY https://github.com/facebook/zstd.git
|
||||||
|
GIT_TAG v1.5.7
|
||||||
|
SOURCE_SUBDIR build/cmake
|
||||||
|
SYSTEM
|
||||||
|
EXCLUDE_FROM_ALL
|
||||||
|
OVERRIDE_FIND_PACKAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
mimalloc
|
||||||
|
GIT_REPOSITORY https://github.com/microsoft/mimalloc.git
|
||||||
|
GIT_TAG v3.0.10
|
||||||
|
SYSTEM
|
||||||
|
EXCLUDE_FROM_ALL
|
||||||
|
OVERRIDE_FIND_PACKAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
tl-expected
|
||||||
|
GIT_REPOSITORY https://github.com/TartanLlama/expected.git
|
||||||
|
GIT_TAG v1.3.1
|
||||||
|
SYSTEM
|
||||||
|
EXCLUDE_FROM_ALL
|
||||||
|
OVERRIDE_FIND_PACKAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
unordered_dense
|
||||||
|
GIT_REPOSITORY https://github.com/martinus/unordered_dense.git
|
||||||
|
GIT_TAG v4.8.1
|
||||||
|
SYSTEM
|
||||||
|
EXCLUDE_FROM_ALL
|
||||||
|
OVERRIDE_FIND_PACKAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
find_package(ZLIB REQUIRED)
|
||||||
|
if(TARGET zlibstatic AND NOT TARGET ZLIB::ZLIB)
|
||||||
|
add_library(ZLIB::ZLIB ALIAS zlibstatic)
|
||||||
|
elseif(TARGET zlib AND NOT TARGET ZLIB::ZLIB)
|
||||||
|
add_library(ZLIB::ZLIB ALIAS zlib)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(zstd REQUIRED)
|
||||||
|
find_package(glaze REQUIRED)
|
||||||
|
find_package(simdjson REQUIRED)
|
||||||
|
find_package(nlohmann_json REQUIRED)
|
||||||
|
find_package(unordered_dense REQUIRED)
|
||||||
|
|
||||||
|
find_package(OpenSSL REQUIRED)
|
||||||
|
if(TARGET ssl AND NOT TARGET OpenSSL::SSL)
|
||||||
|
add_library(OpenSSL::SSL ALIAS ssl)
|
||||||
|
message(STATUS "Patched OpenSSL::SSL alias for Curl")
|
||||||
|
endif()
|
||||||
|
if(TARGET crypto AND NOT TARGET OpenSSL::Crypto)
|
||||||
|
add_library(OpenSSL::Crypto ALIAS crypto)
|
||||||
|
message(STATUS "Patched OpenSSL::Crypto alias for Curl")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(MI_BUILD_SHARED ON CACHE BOOL "" FORCE)
|
||||||
|
set(MI_BUILD_STATIC ON CACHE BOOL "" FORCE)
|
||||||
|
set(MI_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
|
find_package(mimalloc REQUIRED)
|
||||||
|
|
||||||
|
set(EXPECTED_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
|
find_package(tl-expected REQUIRED)
|
||||||
|
|
||||||
|
set(HTTPLIB_REQUIRE_OPENSSL ON CACHE BOOL "" FORCE)
|
||||||
|
set(HTTPLIB_REQUIRE_ZLIB ON CACHE BOOL "" FORCE)
|
||||||
|
find_package(httplib REQUIRED)
|
||||||
@ -13,9 +13,24 @@ project(IACore)
|
|||||||
|
|
||||||
enable_language(C)
|
enable_language(C)
|
||||||
|
|
||||||
|
include(CMake/FindDeps.cmake)
|
||||||
|
|
||||||
# Default to ON if root, OFF if dependency
|
# Default to ON if root, OFF if dependency
|
||||||
option(IACore_BUILD_TESTS "Build unit tests" ${PROJECT_IS_TOP_LEVEL})
|
option(IACore_BUILD_TESTS "Build unit tests" ${PROJECT_IS_TOP_LEVEL})
|
||||||
|
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
message(STATUS "Configuring IACore for Debug..")
|
||||||
|
add_compile_options(-O0 -g)
|
||||||
|
add_compile_definitions("__IA_DEBUG=1")
|
||||||
|
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
|
message(STATUS "Configuring IACore for Release..")
|
||||||
|
add_compile_options(-O3 -g0)
|
||||||
|
add_compile_definitions("__IA_DEBUG=0")
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Unknown CMAKE_BUILD_TYPE \"${CMAKE_BUILD_TYPE}\"")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
message(STATUS "Detected Compiler ID: ${CMAKE_CXX_COMPILER_ID}")
|
message(STATUS "Detected Compiler ID: ${CMAKE_CXX_COMPILER_ID}")
|
||||||
# Check if the compiler is MSVC (cl.exe), but allow Clang acting like MSVC (clang-cl)
|
# Check if the compiler is MSVC (cl.exe), but allow Clang acting like MSVC (clang-cl)
|
||||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||||
@ -41,12 +56,15 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU"
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
|
||||||
|
|
||||||
add_subdirectory(Vendor/)
|
|
||||||
|
|
||||||
add_subdirectory(Src/)
|
add_subdirectory(Src/)
|
||||||
|
|
||||||
if(IACore_BUILD_TESTS)
|
if(IACore_BUILD_TESTS)
|
||||||
add_subdirectory(Tests)
|
add_subdirectory(Tests)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# -------------------------------------------------
|
||||||
|
# Local Development Sandboxes (not included in source control)
|
||||||
|
# -------------------------------------------------
|
||||||
|
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.local")
|
||||||
|
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/.local")
|
||||||
|
endif()
|
||||||
|
|||||||
@ -1,6 +1,17 @@
|
|||||||
set(SRC_FILES
|
set(SRC_FILES
|
||||||
|
"imp/cpp/IPC.cpp"
|
||||||
|
"imp/cpp/JSON.cpp"
|
||||||
"imp/cpp/IACore.cpp"
|
"imp/cpp/IACore.cpp"
|
||||||
"imp/cpp/Logger.cpp"
|
"imp/cpp/Logger.cpp"
|
||||||
|
"imp/cpp/FileOps.cpp"
|
||||||
|
"imp/cpp/AsyncOps.cpp"
|
||||||
|
"imp/cpp/DataOps.cpp"
|
||||||
|
"imp/cpp/SocketOps.cpp"
|
||||||
|
"imp/cpp/StringOps.cpp"
|
||||||
|
"imp/cpp/ProcessOps.cpp"
|
||||||
|
"imp/cpp/HttpClient.cpp"
|
||||||
|
"imp/cpp/StreamReader.cpp"
|
||||||
|
"imp/cpp/StreamWriter.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(IACore STATIC ${SRC_FILES})
|
add_library(IACore STATIC ${SRC_FILES})
|
||||||
@ -8,7 +19,20 @@ add_library(IACore STATIC ${SRC_FILES})
|
|||||||
target_include_directories(IACore PUBLIC inc/)
|
target_include_directories(IACore PUBLIC inc/)
|
||||||
target_include_directories(IACore PRIVATE imp/hpp/)
|
target_include_directories(IACore PRIVATE imp/hpp/)
|
||||||
|
|
||||||
target_link_libraries(IACore PUBLIC tl::expected unordered_dense::unordered_dense)
|
target_link_libraries(IACore PUBLIC
|
||||||
|
libzstd_static
|
||||||
|
ZLIB::ZLIB
|
||||||
|
tl::expected
|
||||||
|
glaze::glaze
|
||||||
|
simdjson::simdjson
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
unordered_dense::unordered_dense
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(IACore PRIVATE
|
||||||
|
httplib::httplib
|
||||||
|
OpenSSL::SSL
|
||||||
|
)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(IACore PUBLIC mimalloc-static)
|
target_link_libraries(IACore PUBLIC mimalloc-static)
|
||||||
@ -28,3 +52,8 @@ define_property(TARGET PROPERTY USE_EXCEPTIONS
|
|||||||
BRIEF_DOCS "If ON, this target is allowed to use C++ exceptions."
|
BRIEF_DOCS "If ON, this target is allowed to use C++ exceptions."
|
||||||
FULL_DOCS "Prevents IACore from propagating -fno-exceptions to this target."
|
FULL_DOCS "Prevents IACore from propagating -fno-exceptions to this target."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(IACore PRIVATE
|
||||||
|
CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
|
CPPHTTPLIB_ZLIB_SUPPORT
|
||||||
|
)
|
||||||
|
|||||||
179
Src/IACore/imp/cpp/AsyncOps.cpp
Normal file
179
Src/IACore/imp/cpp/AsyncOps.cpp
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <IACore/AsyncOps.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
Mutex AsyncOps::s_queueMutex;
|
||||||
|
ConditionVariable AsyncOps::s_wakeCondition;
|
||||||
|
Vector<JoiningThread> AsyncOps::s_scheduleWorkers;
|
||||||
|
Deque<AsyncOps::ScheduledTask> AsyncOps::s_highPriorityQueue;
|
||||||
|
Deque<AsyncOps::ScheduledTask> AsyncOps::s_normalPriorityQueue;
|
||||||
|
|
||||||
|
VOID AsyncOps::RunTask(IN Function<VOID()> task)
|
||||||
|
{
|
||||||
|
JoiningThread(task).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID AsyncOps::InitializeScheduler(IN UINT8 workerCount)
|
||||||
|
{
|
||||||
|
if (!workerCount)
|
||||||
|
workerCount = std::max((UINT32) 2, std::thread::hardware_concurrency() - 2);
|
||||||
|
for (UINT32 i = 0; i < workerCount; i++)
|
||||||
|
s_scheduleWorkers.emplace_back(AsyncOps::ScheduleWorkerLoop, i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID AsyncOps::TerminateScheduler()
|
||||||
|
{
|
||||||
|
for (auto &w : s_scheduleWorkers)
|
||||||
|
{
|
||||||
|
w.request_stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
s_wakeCondition.notify_all();
|
||||||
|
|
||||||
|
for (auto &w : s_scheduleWorkers)
|
||||||
|
{
|
||||||
|
if (w.joinable())
|
||||||
|
{
|
||||||
|
w.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s_scheduleWorkers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID AsyncOps::ScheduleTask(IN Function<VOID(IN WorkerID workerID)> task, IN TaskTag tag, IN Schedule *schedule,
|
||||||
|
IN Priority priority)
|
||||||
|
{
|
||||||
|
IA_ASSERT(s_scheduleWorkers.size() && "Scheduler must be initialized before calling this function");
|
||||||
|
|
||||||
|
schedule->Counter.fetch_add(1);
|
||||||
|
{
|
||||||
|
ScopedLock lock(s_queueMutex);
|
||||||
|
if (priority == Priority::High)
|
||||||
|
s_highPriorityQueue.emplace_back(ScheduledTask{tag, schedule, IA_MOVE(task)});
|
||||||
|
else
|
||||||
|
s_normalPriorityQueue.emplace_back(ScheduledTask{tag, schedule, IA_MOVE(task)});
|
||||||
|
}
|
||||||
|
s_wakeCondition.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID AsyncOps::CancelTasksOfTag(IN TaskTag tag)
|
||||||
|
{
|
||||||
|
ScopedLock lock(s_queueMutex);
|
||||||
|
|
||||||
|
auto cancelFromQueue = [&](Deque<ScheduledTask> &queue) {
|
||||||
|
for (auto it = queue.begin(); it != queue.end(); /* no increment here */)
|
||||||
|
{
|
||||||
|
if (it->Tag == tag)
|
||||||
|
{
|
||||||
|
if (it->ScheduleHandle->Counter.fetch_sub(1) == 1)
|
||||||
|
it->ScheduleHandle->Counter.notify_all();
|
||||||
|
|
||||||
|
it = queue.erase(it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelFromQueue(s_highPriorityQueue);
|
||||||
|
cancelFromQueue(s_normalPriorityQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID AsyncOps::WaitForScheduleCompletion(IN Schedule *schedule)
|
||||||
|
{
|
||||||
|
IA_ASSERT(s_scheduleWorkers.size() && "Scheduler must be initialized before calling this function");
|
||||||
|
|
||||||
|
while (schedule->Counter.load() > 0)
|
||||||
|
{
|
||||||
|
ScheduledTask task;
|
||||||
|
BOOL foundTask{FALSE};
|
||||||
|
{
|
||||||
|
UniqueLock lock(s_queueMutex);
|
||||||
|
if (!s_highPriorityQueue.empty())
|
||||||
|
{
|
||||||
|
task = IA_MOVE(s_highPriorityQueue.front());
|
||||||
|
s_highPriorityQueue.pop_front();
|
||||||
|
foundTask = TRUE;
|
||||||
|
}
|
||||||
|
else if (!s_normalPriorityQueue.empty())
|
||||||
|
{
|
||||||
|
task = IA_MOVE(s_normalPriorityQueue.front());
|
||||||
|
s_normalPriorityQueue.pop_front();
|
||||||
|
foundTask = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (foundTask)
|
||||||
|
{
|
||||||
|
task.Task(MainThreadWorkerID);
|
||||||
|
if (task.ScheduleHandle->Counter.fetch_sub(1) == 1)
|
||||||
|
task.ScheduleHandle->Counter.notify_all();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto currentVal = schedule->Counter.load();
|
||||||
|
if (currentVal > 0)
|
||||||
|
schedule->Counter.wait(currentVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncOps::WorkerID AsyncOps::GetWorkerCount()
|
||||||
|
{
|
||||||
|
return static_cast<WorkerID>(s_scheduleWorkers.size() + 1); // +1 for MainThread (Work Stealing)
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID AsyncOps::ScheduleWorkerLoop(IN StopToken stopToken, IN WorkerID workerID)
|
||||||
|
{
|
||||||
|
while (!stopToken.stop_requested())
|
||||||
|
{
|
||||||
|
ScheduledTask task;
|
||||||
|
BOOL foundTask{FALSE};
|
||||||
|
{
|
||||||
|
UniqueLock lock(s_queueMutex);
|
||||||
|
|
||||||
|
s_wakeCondition.wait(lock, [&stopToken] {
|
||||||
|
return !s_highPriorityQueue.empty() || !s_normalPriorityQueue.empty() || stopToken.stop_requested();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (stopToken.stop_requested() && s_highPriorityQueue.empty() && s_normalPriorityQueue.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!s_highPriorityQueue.empty())
|
||||||
|
{
|
||||||
|
task = IA_MOVE(s_highPriorityQueue.front());
|
||||||
|
s_highPriorityQueue.pop_front();
|
||||||
|
foundTask = TRUE;
|
||||||
|
}
|
||||||
|
else if (!s_normalPriorityQueue.empty())
|
||||||
|
{
|
||||||
|
task = IA_MOVE(s_normalPriorityQueue.front());
|
||||||
|
s_normalPriorityQueue.pop_front();
|
||||||
|
foundTask = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (foundTask)
|
||||||
|
{
|
||||||
|
task.Task(workerID);
|
||||||
|
if (task.ScheduleHandle->Counter.fetch_sub(1) == 1)
|
||||||
|
task.ScheduleHandle->Counter.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
319
Src/IACore/imp/cpp/DataOps.cpp
Normal file
319
Src/IACore/imp/cpp/DataOps.cpp
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <IACore/DataOps.hpp>
|
||||||
|
|
||||||
|
#include <zlib.h>
|
||||||
|
#include <zstd.h>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
STATIC CONSTEXPR UINT32 CRC32_TABLE[] = {
|
||||||
|
/* CRC polynomial 0xedb88320 */
|
||||||
|
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832,
|
||||||
|
0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
|
||||||
|
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a,
|
||||||
|
0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||||
|
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
|
||||||
|
0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
|
||||||
|
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
|
||||||
|
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||||
|
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4,
|
||||||
|
0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
||||||
|
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074,
|
||||||
|
0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||||
|
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525,
|
||||||
|
0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||||
|
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
|
||||||
|
0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||||
|
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76,
|
||||||
|
0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
|
||||||
|
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6,
|
||||||
|
0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||||
|
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
|
||||||
|
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
|
||||||
|
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7,
|
||||||
|
0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||||
|
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
|
||||||
|
0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
|
||||||
|
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330,
|
||||||
|
0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||||
|
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
|
||||||
|
};
|
||||||
|
|
||||||
|
// FNV-1a 32-bit Constants
|
||||||
|
constexpr uint32_t FNV1A_32_PRIME = 0x01000193; // 16777619
|
||||||
|
constexpr uint32_t FNV1A_32_OFFSET = 0x811c9dc5; // 2166136261
|
||||||
|
|
||||||
|
UINT32 DataOps::Hash(IN CONST String &string)
|
||||||
|
{
|
||||||
|
uint32_t hash = FNV1A_32_OFFSET;
|
||||||
|
for (char c : string)
|
||||||
|
{
|
||||||
|
hash ^= static_cast<uint8_t>(c);
|
||||||
|
hash *= FNV1A_32_PRIME;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT32 DataOps::Hash(IN Span<CONST UINT8> data)
|
||||||
|
{
|
||||||
|
uint32_t hash = FNV1A_32_OFFSET;
|
||||||
|
const uint8_t *ptr = static_cast<const uint8_t *>(data.data());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < data.size(); ++i)
|
||||||
|
{
|
||||||
|
hash ^= ptr[i];
|
||||||
|
hash *= FNV1A_32_PRIME;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT32 DataOps::CRC32(IN Span<CONST UINT8> _data)
|
||||||
|
{
|
||||||
|
UINT32 crc32 = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
#define UPDC32(octet, crc) (CRC32_TABLE[((crc) ^ ((UINT8) octet)) & 0xff] ^ ((crc) >> 8))
|
||||||
|
|
||||||
|
auto data = _data.data();
|
||||||
|
auto size = _data.size();
|
||||||
|
for (; size; --size, ++data)
|
||||||
|
{
|
||||||
|
crc32 = UPDC32(*data, crc32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef UPDC32
|
||||||
|
|
||||||
|
return ~crc32;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataOps::CompressionType DataOps::DetectCompression(IN Span<CONST UINT8> data)
|
||||||
|
{
|
||||||
|
if (data.size() < 2)
|
||||||
|
return CompressionType::None;
|
||||||
|
|
||||||
|
// Check for GZIP Magic Number (0x1F 0x8B)
|
||||||
|
if (data[0] == 0x1F && data[1] == 0x8B)
|
||||||
|
return CompressionType::Gzip;
|
||||||
|
|
||||||
|
// Check for ZLIB Magic Number (starts with 0x78)
|
||||||
|
// 0x78 = Deflate compression with 32k window size
|
||||||
|
// Valid second bytes: 0x01 (Fastest), 0x9C (Default), 0xDA (Best)
|
||||||
|
if (data[0] == 0x78 && (data[1] == 0x01 || data[1] == 0x9C || data[1] == 0xDA))
|
||||||
|
return CompressionType::Zlib;
|
||||||
|
|
||||||
|
return CompressionType::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<Vector<UINT8>, String> DataOps::ZlibInflate(IN Span<CONST UINT8> data)
|
||||||
|
{
|
||||||
|
z_stream zs{};
|
||||||
|
zs.zalloc = Z_NULL;
|
||||||
|
zs.zfree = Z_NULL;
|
||||||
|
zs.opaque = Z_NULL;
|
||||||
|
|
||||||
|
// 15 + 32 = Auto-detect Gzip or Zlib
|
||||||
|
if (inflateInit2(&zs, 15 + 32) != Z_OK)
|
||||||
|
return MakeUnexpected("Failed to initialize zlib inflate");
|
||||||
|
|
||||||
|
zs.next_in = const_cast<Bytef *>(data.data());
|
||||||
|
zs.avail_in = static_cast<uInt>(data.size());
|
||||||
|
|
||||||
|
Vector<UINT8> outBuffer;
|
||||||
|
// Start with 2x input size.
|
||||||
|
// Small packets compress well, so maybe 4x for very small inputs.
|
||||||
|
size_t guessSize = data.size() < 1024 ? data.size() * 4 : data.size() * 2;
|
||||||
|
outBuffer.resize(guessSize);
|
||||||
|
|
||||||
|
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
|
||||||
|
zs.avail_out = static_cast<uInt>(outBuffer.size());
|
||||||
|
|
||||||
|
int ret;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (zs.avail_out == 0)
|
||||||
|
{
|
||||||
|
size_t currentPos = zs.total_out;
|
||||||
|
|
||||||
|
size_t newSize = outBuffer.size() * 2;
|
||||||
|
outBuffer.resize(newSize);
|
||||||
|
|
||||||
|
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data() + currentPos);
|
||||||
|
|
||||||
|
zs.avail_out = static_cast<uInt>(newSize - currentPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = inflate(&zs, Z_NO_FLUSH);
|
||||||
|
|
||||||
|
} while (ret == Z_OK);
|
||||||
|
|
||||||
|
inflateEnd(&zs);
|
||||||
|
|
||||||
|
if (ret != Z_STREAM_END)
|
||||||
|
return MakeUnexpected("Failed to inflate: corrupt data or stream error");
|
||||||
|
|
||||||
|
outBuffer.resize(zs.total_out);
|
||||||
|
|
||||||
|
return outBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<Vector<UINT8>, String> DataOps::ZlibDeflate(IN Span<CONST UINT8> data)
|
||||||
|
{
|
||||||
|
z_stream zs{};
|
||||||
|
zs.zalloc = Z_NULL;
|
||||||
|
zs.zfree = Z_NULL;
|
||||||
|
zs.opaque = Z_NULL;
|
||||||
|
|
||||||
|
if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK)
|
||||||
|
return MakeUnexpected("Failed to initialize zlib deflate");
|
||||||
|
|
||||||
|
zs.next_in = const_cast<Bytef *>(data.data());
|
||||||
|
zs.avail_in = static_cast<uInt>(data.size());
|
||||||
|
|
||||||
|
Vector<UINT8> outBuffer;
|
||||||
|
|
||||||
|
outBuffer.resize(deflateBound(&zs, data.size()));
|
||||||
|
|
||||||
|
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
|
||||||
|
zs.avail_out = static_cast<uInt>(outBuffer.size());
|
||||||
|
|
||||||
|
int ret = deflate(&zs, Z_FINISH);
|
||||||
|
|
||||||
|
if (ret != Z_STREAM_END)
|
||||||
|
{
|
||||||
|
deflateEnd(&zs);
|
||||||
|
return MakeUnexpected("Failed to deflate, ran out of buffer memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
outBuffer.resize(zs.total_out);
|
||||||
|
|
||||||
|
deflateEnd(&zs);
|
||||||
|
return outBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<Vector<UINT8>, String> DataOps::ZstdInflate(IN Span<CONST UINT8> data)
|
||||||
|
{
|
||||||
|
unsigned long long const contentSize = ZSTD_getFrameContentSize(data.data(), data.size());
|
||||||
|
|
||||||
|
if (contentSize == ZSTD_CONTENTSIZE_ERROR)
|
||||||
|
return MakeUnexpected("Failed to inflate: Not valid ZSTD compressed data");
|
||||||
|
|
||||||
|
if (contentSize != ZSTD_CONTENTSIZE_UNKNOWN)
|
||||||
|
{
|
||||||
|
// FAST PATH: We know the size
|
||||||
|
Vector<UINT8> outBuffer;
|
||||||
|
outBuffer.resize(static_cast<size_t>(contentSize));
|
||||||
|
|
||||||
|
size_t const dSize = ZSTD_decompress(outBuffer.data(), outBuffer.size(), data.data(), data.size());
|
||||||
|
|
||||||
|
if (ZSTD_isError(dSize))
|
||||||
|
return MakeUnexpected(std::format("Failed to inflate: {}", ZSTD_getErrorName(dSize)));
|
||||||
|
|
||||||
|
return outBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ZSTD_DCtx *dctx = ZSTD_createDCtx();
|
||||||
|
Vector<UINT8> outBuffer;
|
||||||
|
outBuffer.resize(data.size() * 2);
|
||||||
|
|
||||||
|
ZSTD_inBuffer input = {data.data(), data.size(), 0};
|
||||||
|
ZSTD_outBuffer output = {outBuffer.data(), outBuffer.size(), 0};
|
||||||
|
|
||||||
|
size_t ret;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
ret = ZSTD_decompressStream(dctx, &output, &input);
|
||||||
|
|
||||||
|
if (ZSTD_isError(ret))
|
||||||
|
{
|
||||||
|
ZSTD_freeDCtx(dctx);
|
||||||
|
return MakeUnexpected(std::format("Failed to inflate: {}", ZSTD_getErrorName(ret)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.pos == output.size)
|
||||||
|
{
|
||||||
|
size_t newSize = outBuffer.size() * 2;
|
||||||
|
outBuffer.resize(newSize);
|
||||||
|
output.dst = outBuffer.data();
|
||||||
|
output.size = newSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (ret != 0);
|
||||||
|
|
||||||
|
outBuffer.resize(output.pos);
|
||||||
|
ZSTD_freeDCtx(dctx);
|
||||||
|
|
||||||
|
return outBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<Vector<UINT8>, String> DataOps::ZstdDeflate(IN Span<CONST UINT8> data)
|
||||||
|
{
|
||||||
|
size_t const maxDstSize = ZSTD_compressBound(data.size());
|
||||||
|
|
||||||
|
Vector<UINT8> outBuffer;
|
||||||
|
outBuffer.resize(maxDstSize);
|
||||||
|
|
||||||
|
size_t const compressedSize = ZSTD_compress(outBuffer.data(), maxDstSize, data.data(), data.size(), 3);
|
||||||
|
|
||||||
|
if (ZSTD_isError(compressedSize))
|
||||||
|
return MakeUnexpected(std::format("Failed to deflate: {}", ZSTD_getErrorName(compressedSize)));
|
||||||
|
|
||||||
|
outBuffer.resize(compressedSize);
|
||||||
|
return outBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<Vector<UINT8>, String> DataOps::GZipDeflate(IN Span<CONST UINT8> data)
|
||||||
|
{
|
||||||
|
z_stream zs{};
|
||||||
|
zs.zalloc = Z_NULL;
|
||||||
|
zs.zfree = Z_NULL;
|
||||||
|
zs.opaque = Z_NULL;
|
||||||
|
|
||||||
|
// WindowBits = 15 + 16 (31) -> This forces GZIP encoding
|
||||||
|
// MemLevel = 8 (default)
|
||||||
|
// Strategy = Z_DEFAULT_STRATEGY
|
||||||
|
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK)
|
||||||
|
return MakeUnexpected("Failed to initialize gzip deflate");
|
||||||
|
|
||||||
|
zs.next_in = const_cast<Bytef *>(data.data());
|
||||||
|
zs.avail_in = static_cast<uInt>(data.size());
|
||||||
|
|
||||||
|
Vector<UINT8> outBuffer;
|
||||||
|
|
||||||
|
outBuffer.resize(deflateBound(&zs, data.size()) + 1024); // Additional 1KB buffer for safety
|
||||||
|
|
||||||
|
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
|
||||||
|
zs.avail_out = static_cast<uInt>(outBuffer.size());
|
||||||
|
|
||||||
|
int ret = deflate(&zs, Z_FINISH);
|
||||||
|
|
||||||
|
if (ret != Z_STREAM_END)
|
||||||
|
{
|
||||||
|
deflateEnd(&zs);
|
||||||
|
return MakeUnexpected("Failed to deflate");
|
||||||
|
}
|
||||||
|
|
||||||
|
outBuffer.resize(zs.total_out);
|
||||||
|
|
||||||
|
deflateEnd(&zs);
|
||||||
|
return outBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<Vector<UINT8>, String> DataOps::GZipInflate(IN Span<CONST UINT8> data)
|
||||||
|
{
|
||||||
|
return ZlibInflate(data);
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
277
Src/IACore/imp/cpp/FileOps.cpp
Normal file
277
Src/IACore/imp/cpp/FileOps.cpp
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <IACore/FileOps.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
UnorderedMap<PCUINT8, Tuple<PVOID, PVOID, PVOID>> FileOps::s_mappedFiles;
|
||||||
|
|
||||||
|
VOID FileOps::UnmapFile(IN PCUINT8 mappedPtr)
|
||||||
|
{
|
||||||
|
if (!s_mappedFiles.contains(mappedPtr))
|
||||||
|
return;
|
||||||
|
const auto handles = s_mappedFiles.extract(mappedPtr)->second;
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
::UnmapViewOfFile(std::get<1>(handles));
|
||||||
|
::CloseHandle(std::get<2>(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));
|
||||||
|
const auto fd = (INT32) ((UINT64) std::get<0>(handles));
|
||||||
|
if (fd != -1)
|
||||||
|
::close(fd);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<PUINT8, String> 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<PUINT8>(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<PUINT8>(addr);
|
||||||
|
|
||||||
|
s_mappedFiles[result] = std::make_tuple((PVOID) ((UINT64) fd), (PVOID) addr, (PVOID) size);
|
||||||
|
return result;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID FileOps::UnlinkSharedMemory(IN CONST String &name)
|
||||||
|
{
|
||||||
|
if (name.empty())
|
||||||
|
return;
|
||||||
|
#if IA_PLATFORM_UNIX
|
||||||
|
shm_unlink(name.c_str());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<PCUINT8, String> FileOps::MapFile(IN CONST FilePath &path, OUT SIZE_T &size)
|
||||||
|
{
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
|
||||||
|
const auto handle = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
||||||
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
|
||||||
|
|
||||||
|
if (handle == INVALID_HANDLE_VALUE)
|
||||||
|
return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.c_str()));
|
||||||
|
|
||||||
|
LARGE_INTEGER fileSize;
|
||||||
|
if (!GetFileSizeEx(handle, &fileSize))
|
||||||
|
{
|
||||||
|
CloseHandle(handle);
|
||||||
|
return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.c_str()));
|
||||||
|
}
|
||||||
|
size = static_cast<size_t>(fileSize.QuadPart);
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
CloseHandle(handle);
|
||||||
|
return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto hmap = CreateFileMappingW(handle, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||||
|
if (hmap == NULL)
|
||||||
|
{
|
||||||
|
CloseHandle(handle);
|
||||||
|
return MakeUnexpected(std::format("Failed to memory map {}", path.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto result = static_cast<PCUINT8>(MapViewOfFile(hmap, FILE_MAP_READ, 0, 0, 0));
|
||||||
|
if (result == NULL)
|
||||||
|
{
|
||||||
|
CloseHandle(handle);
|
||||||
|
CloseHandle(hmap);
|
||||||
|
return MakeUnexpected(std::format("Failed to memory map {}", path.c_str()));
|
||||||
|
}
|
||||||
|
s_mappedFiles[result] = std::make_tuple((PVOID) handle, (PVOID) result, (PVOID) hmap);
|
||||||
|
return result;
|
||||||
|
|
||||||
|
#elif IA_PLATFORM_UNIX
|
||||||
|
|
||||||
|
const auto handle = open(path.c_str(), O_RDONLY);
|
||||||
|
if (handle == -1)
|
||||||
|
return MakeUnexpected(std::format("Failed to open {} for memory mapping", path.c_str()));
|
||||||
|
struct stat sb;
|
||||||
|
if (fstat(handle, &sb) == -1)
|
||||||
|
{
|
||||||
|
close(handle);
|
||||||
|
return MakeUnexpected(std::format("Failed to get stats of {} for memory mapping", path.c_str()));
|
||||||
|
}
|
||||||
|
size = static_cast<size_t>(sb.st_size);
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
close(handle);
|
||||||
|
return MakeUnexpected(std::format("Failed to get size of {} for memory mapping", path.c_str()));
|
||||||
|
}
|
||||||
|
void *addr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, handle, 0);
|
||||||
|
if (addr == MAP_FAILED)
|
||||||
|
{
|
||||||
|
close(handle);
|
||||||
|
return MakeUnexpected(std::format("Failed to memory map {}", path.c_str()));
|
||||||
|
}
|
||||||
|
const auto result = static_cast<PCUINT8>(addr);
|
||||||
|
madvise(addr, size, MADV_SEQUENTIAL);
|
||||||
|
s_mappedFiles[result] = std::make_tuple((PVOID) ((UINT64) handle), (PVOID) addr, (PVOID) size);
|
||||||
|
return result;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<StreamWriter, String> FileOps::StreamToFile(IN CONST FilePath &path, IN BOOL overwrite)
|
||||||
|
{
|
||||||
|
if (!overwrite && FileSystem::exists(path))
|
||||||
|
return MakeUnexpected(std::format("File aready exists: {}", path.c_str()));
|
||||||
|
return StreamWriter(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<StreamReader, String> FileOps::StreamFromFile(IN CONST FilePath &path)
|
||||||
|
{
|
||||||
|
if (!FileSystem::exists(path))
|
||||||
|
return MakeUnexpected(std::format("File does not exist: {}", path.c_str()));
|
||||||
|
return StreamReader(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<String, String> FileOps::ReadTextFile(IN CONST FilePath &path)
|
||||||
|
{
|
||||||
|
const auto f = fopen(path.c_str(), "r");
|
||||||
|
if (!f)
|
||||||
|
return MakeUnexpected(std::format("Failed to open file: {}", path.c_str()));
|
||||||
|
String result;
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
result.resize(ftell(f));
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
fread(result.data(), 1, result.size(), f);
|
||||||
|
fclose(f);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<Vector<UINT8>, String> FileOps::ReadBinaryFile(IN CONST FilePath &path)
|
||||||
|
{
|
||||||
|
const auto f = fopen(path.c_str(), "rb");
|
||||||
|
if (!f)
|
||||||
|
return MakeUnexpected(std::format("Failed to open file: {}", path.c_str()));
|
||||||
|
Vector<UINT8> result;
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
result.resize(ftell(f));
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
fread(result.data(), 1, result.size(), f);
|
||||||
|
fclose(f);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<SIZE_T, String> FileOps::WriteTextFile(IN CONST FilePath &path, IN CONST String &contents,
|
||||||
|
IN BOOL overwrite)
|
||||||
|
{
|
||||||
|
if (!overwrite && FileSystem::exists(path))
|
||||||
|
return MakeUnexpected(std::format("File aready exists: {}", path.c_str()));
|
||||||
|
const auto f = fopen(path.c_str(), "w");
|
||||||
|
if (!f)
|
||||||
|
return MakeUnexpected(std::format("Failed to write to file: {}", path.c_str()));
|
||||||
|
const auto result = fwrite(contents.data(), 1, contents.size(), f);
|
||||||
|
fputc(0, f);
|
||||||
|
fclose(f);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<SIZE_T, String> FileOps::WriteBinaryFile(IN CONST FilePath &path, IN Span<UINT8> contents,
|
||||||
|
IN BOOL overwrite)
|
||||||
|
{
|
||||||
|
if (!overwrite && FileSystem::exists(path))
|
||||||
|
return MakeUnexpected(std::format("File aready exists: {}", path.c_str()));
|
||||||
|
|
||||||
|
const auto f = fopen(path.c_str(), "wb");
|
||||||
|
if (!f)
|
||||||
|
return MakeUnexpected(std::format("Failed to write to file: {}", path.c_str()));
|
||||||
|
const auto result = fwrite(contents.data(), 1, contents.size(), f);
|
||||||
|
fclose(f);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
FilePath FileOps::NormalizeExecutablePath(IN CONST FilePath &path)
|
||||||
|
{
|
||||||
|
FilePath result = path;
|
||||||
|
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
if (!result.has_extension())
|
||||||
|
result.replace_extension(".exe");
|
||||||
|
|
||||||
|
#elif IA_PLATFORM_UNIX
|
||||||
|
if (result.extension() == ".exe")
|
||||||
|
result.replace_extension("");
|
||||||
|
|
||||||
|
if (result.is_relative())
|
||||||
|
{
|
||||||
|
String pathStr = result.string();
|
||||||
|
if (!pathStr.starts_with("./") && !pathStr.starts_with("../"))
|
||||||
|
result = "./" + pathStr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
244
Src/IACore/imp/cpp/HttpClient.cpp
Normal file
244
Src/IACore/imp/cpp/HttpClient.cpp
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <IACore/HttpClient.hpp>
|
||||||
|
#include <IACore/DataOps.hpp>
|
||||||
|
|
||||||
|
#include <httplib.h>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
httplib::Headers BuildHeaders(IN Span<CONST HttpClient::Header> headers, IN PCCHAR defaultContentType)
|
||||||
|
{
|
||||||
|
httplib::Headers out;
|
||||||
|
bool hasContentType = false;
|
||||||
|
|
||||||
|
for (const auto &h : headers)
|
||||||
|
{
|
||||||
|
std::string key = HttpClient::HeaderTypeToString(h.first);
|
||||||
|
out.emplace(key, h.second);
|
||||||
|
|
||||||
|
if (h.first == HttpClient::EHeaderType::CONTENT_TYPE)
|
||||||
|
hasContentType = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasContentType && defaultContentType)
|
||||||
|
out.emplace("Content-Type", defaultContentType);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpClient::HttpClient(IN CONST String &host)
|
||||||
|
: m_client(new httplib::Client(host)), m_lastResponseCode(EResponseCode::INTERNAL_SERVER_ERROR)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpClient::~HttpClient()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
String HttpClient::PreprocessResponse(IN CONST String &response)
|
||||||
|
{
|
||||||
|
const auto responseBytes = Span<CONST UINT8>{(PCUINT8) response.data(), response.size()};
|
||||||
|
const auto compression = DataOps::DetectCompression(responseBytes);
|
||||||
|
switch (compression)
|
||||||
|
{
|
||||||
|
case DataOps::CompressionType::Gzip: {
|
||||||
|
const auto data = DataOps::GZipInflate(responseBytes);
|
||||||
|
if (!data)
|
||||||
|
return response;
|
||||||
|
return String((PCCHAR) data->data(), data->size());
|
||||||
|
}
|
||||||
|
|
||||||
|
case DataOps::CompressionType::Zlib: {
|
||||||
|
const auto data = DataOps::ZlibInflate(responseBytes);
|
||||||
|
if (!data)
|
||||||
|
return response;
|
||||||
|
return String((PCCHAR) data->data(), data->size());
|
||||||
|
}
|
||||||
|
|
||||||
|
case DataOps::CompressionType::None:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<String, String> HttpClient::RawGet(IN CONST String &path, IN Span<CONST Header> headers,
|
||||||
|
IN PCCHAR defaultContentType)
|
||||||
|
{
|
||||||
|
auto httpHeaders = BuildHeaders(headers, defaultContentType);
|
||||||
|
|
||||||
|
static_cast<httplib::Client *>(m_client)->enable_server_certificate_verification(false);
|
||||||
|
auto res = static_cast<httplib::Client *>(m_client)->Get(
|
||||||
|
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), httpHeaders);
|
||||||
|
|
||||||
|
if (res)
|
||||||
|
{
|
||||||
|
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
||||||
|
if (res->status >= 200 && res->status < 300)
|
||||||
|
return PreprocessResponse(res->body);
|
||||||
|
else
|
||||||
|
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
|
||||||
|
}
|
||||||
|
|
||||||
|
return MakeUnexpected(std::format("Network Error: {}", httplib::to_string(res.error())));
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<String, String> HttpClient::RawPost(IN CONST String &path, IN Span<CONST Header> headers,
|
||||||
|
IN CONST String &body, IN PCCHAR defaultContentType)
|
||||||
|
{
|
||||||
|
auto httpHeaders = BuildHeaders(headers, defaultContentType);
|
||||||
|
|
||||||
|
String contentType = defaultContentType;
|
||||||
|
if (httpHeaders.count("Content-Type"))
|
||||||
|
{
|
||||||
|
const auto t = httpHeaders.find("Content-Type");
|
||||||
|
contentType = t->second;
|
||||||
|
httpHeaders.erase(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static_cast<httplib::Client *>(m_client)->set_keep_alive(true);
|
||||||
|
static_cast<httplib::Client *>(m_client)->enable_server_certificate_verification(false);
|
||||||
|
auto res = static_cast<httplib::Client *>(m_client)->Post(
|
||||||
|
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), httpHeaders, body,
|
||||||
|
contentType.c_str());
|
||||||
|
|
||||||
|
if (res)
|
||||||
|
{
|
||||||
|
m_lastResponseCode = static_cast<EResponseCode>(res->status);
|
||||||
|
if (res->status >= 200 && res->status < 300)
|
||||||
|
return PreprocessResponse(res->body);
|
||||||
|
else
|
||||||
|
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
|
||||||
|
}
|
||||||
|
|
||||||
|
return MakeUnexpected(std::format("Network Error: {}", httplib::to_string(res.error())));
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
HttpClient::Header HttpClient::CreateHeader(IN EHeaderType key, IN CONST String &value)
|
||||||
|
{
|
||||||
|
return std::make_pair(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
String HttpClient::UrlEncode(IN CONST String &value)
|
||||||
|
{
|
||||||
|
std::stringstream escaped;
|
||||||
|
escaped.fill('0');
|
||||||
|
escaped << std::hex << std::uppercase;
|
||||||
|
|
||||||
|
for (char c : value)
|
||||||
|
{
|
||||||
|
if (std::isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' || c == '.' || c == '~')
|
||||||
|
escaped << c;
|
||||||
|
else
|
||||||
|
escaped << '%' << std::setw(2) << static_cast<int>(static_cast<unsigned char>(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
return escaped.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
String HttpClient::UrlDecode(IN CONST String &value)
|
||||||
|
{
|
||||||
|
String result;
|
||||||
|
result.reserve(value.length());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < value.length(); ++i)
|
||||||
|
{
|
||||||
|
if (value[i] == '%' && i + 2 < value.length())
|
||||||
|
{
|
||||||
|
std::string hexStr = value.substr(i + 1, 2);
|
||||||
|
char decodedChar = static_cast<char>(std::strtol(hexStr.c_str(), nullptr, 16));
|
||||||
|
result += decodedChar;
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
else if (value[i] == '+')
|
||||||
|
result += ' ';
|
||||||
|
else
|
||||||
|
result += value[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String HttpClient::HeaderTypeToString(IN EHeaderType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case EHeaderType::ACCEPT:
|
||||||
|
return "Accept";
|
||||||
|
case EHeaderType::ACCEPT_CHARSET:
|
||||||
|
return "Accept-Charset";
|
||||||
|
case EHeaderType::ACCEPT_ENCODING:
|
||||||
|
return "Accept-Encoding";
|
||||||
|
case EHeaderType::ACCEPT_LANGUAGE:
|
||||||
|
return "Accept-Language";
|
||||||
|
case EHeaderType::AUTHORIZATION:
|
||||||
|
return "Authorization";
|
||||||
|
case EHeaderType::CACHE_CONTROL:
|
||||||
|
return "Cache-Control";
|
||||||
|
case EHeaderType::CONNECTION:
|
||||||
|
return "Connection";
|
||||||
|
case EHeaderType::CONTENT_LENGTH:
|
||||||
|
return "Content-Length";
|
||||||
|
case EHeaderType::CONTENT_TYPE:
|
||||||
|
return "Content-Type";
|
||||||
|
case EHeaderType::COOKIE:
|
||||||
|
return "Cookie";
|
||||||
|
case EHeaderType::DATE:
|
||||||
|
return "Date";
|
||||||
|
case EHeaderType::EXPECT:
|
||||||
|
return "Expect";
|
||||||
|
case EHeaderType::HOST:
|
||||||
|
return "Host";
|
||||||
|
case EHeaderType::IF_MATCH:
|
||||||
|
return "If-Match";
|
||||||
|
case EHeaderType::IF_MODIFIED_SINCE:
|
||||||
|
return "If-Modified-Since";
|
||||||
|
case EHeaderType::IF_NONE_MATCH:
|
||||||
|
return "If-None-Match";
|
||||||
|
case EHeaderType::ORIGIN:
|
||||||
|
return "Origin";
|
||||||
|
case EHeaderType::PRAGMA:
|
||||||
|
return "Pragma";
|
||||||
|
case EHeaderType::PROXY_AUTHORIZATION:
|
||||||
|
return "Proxy-Authorization";
|
||||||
|
case EHeaderType::RANGE:
|
||||||
|
return "Range";
|
||||||
|
case EHeaderType::REFERER:
|
||||||
|
return "Referer";
|
||||||
|
case EHeaderType::TE:
|
||||||
|
return "TE";
|
||||||
|
case EHeaderType::UPGRADE:
|
||||||
|
return "Upgrade";
|
||||||
|
case EHeaderType::USER_AGENT:
|
||||||
|
return "User-Agent";
|
||||||
|
case EHeaderType::VIA:
|
||||||
|
return "Via";
|
||||||
|
case EHeaderType::WARNING:
|
||||||
|
return "Warning";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL HttpClient::IsSuccessResponseCode(IN EResponseCode code)
|
||||||
|
{
|
||||||
|
return (INT32) code >= 200 && (INT32) code < 300;
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
@ -15,10 +15,67 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#include <IACore/IACore.hpp>
|
#include <IACore/IACore.hpp>
|
||||||
|
#include <IACore/Logger.hpp>
|
||||||
|
|
||||||
#include <mimalloc-new-delete.h>
|
#include <mimalloc-new-delete.h>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
{
|
{
|
||||||
|
HighResTimePoint g_startTime{};
|
||||||
|
std::thread::id g_mainThreadID{};
|
||||||
|
|
||||||
}
|
VOID Initialize()
|
||||||
|
{
|
||||||
|
g_mainThreadID = std::this_thread::get_id();
|
||||||
|
g_startTime = HighResClock::now();
|
||||||
|
Logger::Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID Terminate()
|
||||||
|
{
|
||||||
|
Logger::Terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT64 GetUnixTime()
|
||||||
|
{
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
return std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT64 GetTicksCount()
|
||||||
|
{
|
||||||
|
return std::chrono::duration_cast<std::chrono::milliseconds>(HighResClock::now() - g_startTime).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
FLOAT64 GetSecondsCount()
|
||||||
|
{
|
||||||
|
return std::chrono::duration_cast<std::chrono::seconds>(HighResClock::now() - g_startTime).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
FLOAT32 GetRandom()
|
||||||
|
{
|
||||||
|
return static_cast<FLOAT32>(rand()) / static_cast<FLOAT32>(RAND_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT32 GetRandom(IN UINT32 seed)
|
||||||
|
{
|
||||||
|
srand(seed);
|
||||||
|
return (UINT32) GetRandom(0, UINT32_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
INT64 GetRandom(IN INT64 min, IN INT64 max)
|
||||||
|
{
|
||||||
|
const auto t = static_cast<FLOAT32>(rand()) / static_cast<FLOAT32>(RAND_MAX);
|
||||||
|
return min + (max - min) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID Sleep(IN UINT64 milliseconds)
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL IsMainThread()
|
||||||
|
{
|
||||||
|
return std::this_thread::get_id() == g_mainThreadID;
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
450
Src/IACore/imp/cpp/IPC.cpp
Normal file
450
Src/IACore/imp/cpp/IPC.cpp
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <IACore/IPC.hpp>
|
||||||
|
|
||||||
|
#include <IACore/FileOps.hpp>
|
||||||
|
#include <IACore/StringOps.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
struct IPC_ConnectionDescriptor
|
||||||
|
{
|
||||||
|
String SocketPath;
|
||||||
|
String SharedMemPath;
|
||||||
|
UINT32 SharedMemSize;
|
||||||
|
|
||||||
|
String Serialize() CONST
|
||||||
|
{
|
||||||
|
return std::format("{}|{}|{}|", SocketPath, SharedMemPath, SharedMemSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC IPC_ConnectionDescriptor Deserialize(IN CONST String &data)
|
||||||
|
{
|
||||||
|
enum class EParseState
|
||||||
|
{
|
||||||
|
SocketPath,
|
||||||
|
SharedMemPath,
|
||||||
|
SharedMemSize
|
||||||
|
};
|
||||||
|
|
||||||
|
IPC_ConnectionDescriptor result{};
|
||||||
|
|
||||||
|
SIZE_T t{};
|
||||||
|
EParseState state{EParseState::SocketPath};
|
||||||
|
for (SIZE_T i = 0; i < data.size(); i++)
|
||||||
|
{
|
||||||
|
if (data[i] != '|')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case EParseState::SocketPath:
|
||||||
|
result.SocketPath = data.substr(t, i - t);
|
||||||
|
state = EParseState::SharedMemPath;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EParseState::SharedMemPath:
|
||||||
|
result.SharedMemPath = data.substr(t, i - t);
|
||||||
|
state = EParseState::SharedMemSize;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EParseState::SharedMemSize: {
|
||||||
|
if (std::from_chars(&data[t], &data[i], result.SharedMemSize).ec != std::errc{})
|
||||||
|
return {};
|
||||||
|
goto done_parsing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
done_parsing:
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace IACore
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
IPC_Node::~IPC_Node()
|
||||||
|
{
|
||||||
|
SocketOps::Close(m_socket); // SocketOps gracefully handles INVALID_SOCKET
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<VOID, String> IPC_Node::Connect(IN PCCHAR connectionString)
|
||||||
|
{
|
||||||
|
auto desc = IPC_ConnectionDescriptor::Deserialize(connectionString);
|
||||||
|
m_shmName = desc.SharedMemPath;
|
||||||
|
|
||||||
|
m_socket = SocketOps::CreateUnixSocket();
|
||||||
|
if (!SocketOps::ConnectUnixSocket(m_socket, desc.SocketPath.c_str()))
|
||||||
|
return MakeUnexpected("Failed to create an unix socket");
|
||||||
|
|
||||||
|
auto mapRes = FileOps::MapSharedMemory(desc.SharedMemPath, desc.SharedMemSize, FALSE);
|
||||||
|
if (!mapRes.has_value())
|
||||||
|
return MakeUnexpected("Failed to map the shared memory");
|
||||||
|
|
||||||
|
m_sharedMemory = mapRes.value();
|
||||||
|
|
||||||
|
auto *layout = reinterpret_cast<IPC_SharedMemoryLayout *>(m_sharedMemory);
|
||||||
|
|
||||||
|
if (layout->Meta.Magic != 0x49414950) // "IAIP"
|
||||||
|
return MakeUnexpected("Invalid shared memory header signature");
|
||||||
|
|
||||||
|
if (layout->Meta.Version != 1)
|
||||||
|
return MakeUnexpected("IPC version mismatch");
|
||||||
|
|
||||||
|
PUINT8 moniDataPtr = m_sharedMemory + layout->MONI_DataOffset;
|
||||||
|
PUINT8 minoDataPtr = m_sharedMemory + layout->MINO_DataOffset;
|
||||||
|
|
||||||
|
MONI = std::make_unique<RingBufferView>(
|
||||||
|
&layout->MONI_Control, Span<UINT8>(moniDataPtr, static_cast<size_t>(layout->MONI_DataSize)), FALSE);
|
||||||
|
|
||||||
|
MINO = std::make_unique<RingBufferView>(
|
||||||
|
&layout->MINO_Control, Span<UINT8>(minoDataPtr, static_cast<size_t>(layout->MINO_DataSize)), FALSE);
|
||||||
|
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
u_long mode = 1;
|
||||||
|
ioctlsocket(m_socket, FIONBIO, &mode);
|
||||||
|
#else
|
||||||
|
fcntl(m_socket, F_SETFL, O_NONBLOCK);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_recieveBuffer.resize(UINT16_MAX + 1);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID IPC_Node::Update()
|
||||||
|
{
|
||||||
|
if (!MONI)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RingBufferView::PacketHeader header;
|
||||||
|
|
||||||
|
// Process all available messages from Manager
|
||||||
|
while (MONI->Pop(header, Span<UINT8>(m_recieveBuffer.data(), m_recieveBuffer.size())))
|
||||||
|
OnPacket(header.ID, {m_recieveBuffer.data(), header.PayloadSize});
|
||||||
|
|
||||||
|
UINT8 signal;
|
||||||
|
const auto res = recv(m_socket, &signal, 1, 0);
|
||||||
|
if (res == 1)
|
||||||
|
OnSignal(signal);
|
||||||
|
else if (res == 0 || (res < 0 && errno != EWOULDBLOCK))
|
||||||
|
{
|
||||||
|
SocketOps::Close(m_socket);
|
||||||
|
FileOps::UnlinkSharedMemory(m_shmName);
|
||||||
|
|
||||||
|
// Manager disconnected, exit immediately
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID IPC_Node::SendSignal(IN UINT8 signal)
|
||||||
|
{
|
||||||
|
if (IS_VALID_SOCKET(m_socket))
|
||||||
|
send(m_socket, (const char *) &signal, sizeof(signal), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID IPC_Node::SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload)
|
||||||
|
{
|
||||||
|
MINO->Push(packetID, payload);
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
VOID IPC_Manager::NodeSession::SendSignal(IN UINT8 signal)
|
||||||
|
{
|
||||||
|
if (IS_VALID_SOCKET(DataSocket))
|
||||||
|
send(DataSocket, (const char *) &signal, sizeof(signal), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID IPC_Manager::NodeSession::SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload)
|
||||||
|
{
|
||||||
|
MONI->Push(packetID, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC_Manager::IPC_Manager()
|
||||||
|
{
|
||||||
|
// SocketOps is smart enough to handle multiple inits
|
||||||
|
SocketOps::Initialize();
|
||||||
|
|
||||||
|
m_recieveBuffer.resize(UINT16_MAX + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC_Manager::~IPC_Manager()
|
||||||
|
{
|
||||||
|
for (auto &session : m_activeSessions)
|
||||||
|
{
|
||||||
|
ProcessOps::TerminateProcess(session->ProcessHandle);
|
||||||
|
FileOps::UnmapFile(session->MappedPtr);
|
||||||
|
FileOps::UnlinkSharedMemory(session->SharedMemName);
|
||||||
|
SocketOps::Close(session->DataSocket);
|
||||||
|
}
|
||||||
|
m_activeSessions.clear();
|
||||||
|
|
||||||
|
for (auto &session : m_pendingSessions)
|
||||||
|
{
|
||||||
|
ProcessOps::TerminateProcess(session->ProcessHandle);
|
||||||
|
FileOps::UnmapFile(session->MappedPtr);
|
||||||
|
FileOps::UnlinkSharedMemory(session->SharedMemName);
|
||||||
|
SocketOps::Close(session->ListenerSocket);
|
||||||
|
}
|
||||||
|
m_pendingSessions.clear();
|
||||||
|
|
||||||
|
// SocketOps is smart enough to handle multiple terminates
|
||||||
|
SocketOps::Terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID IPC_Manager::Update()
|
||||||
|
{
|
||||||
|
const auto now = SteadyClock::now();
|
||||||
|
|
||||||
|
for (INT32 i = m_pendingSessions.size() - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
auto &session = m_pendingSessions[i];
|
||||||
|
|
||||||
|
if (now - session->CreationTime > std::chrono::seconds(5))
|
||||||
|
{
|
||||||
|
ProcessOps::TerminateProcess(session->ProcessHandle);
|
||||||
|
|
||||||
|
FileOps::UnmapFile(session->MappedPtr);
|
||||||
|
FileOps::UnlinkSharedMemory(session->SharedMemName);
|
||||||
|
SocketOps::Close(session->DataSocket);
|
||||||
|
|
||||||
|
m_pendingSessions.erase(m_pendingSessions.begin() + i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketHandle newSock = accept(session->ListenerSocket, NULL, NULL);
|
||||||
|
|
||||||
|
if (IS_VALID_SOCKET(newSock))
|
||||||
|
{
|
||||||
|
session->DataSocket = newSock;
|
||||||
|
session->IsReady = TRUE;
|
||||||
|
|
||||||
|
// Set Data Socket to Non-Blocking
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
u_long mode = 1;
|
||||||
|
ioctlsocket(session->DataSocket, FIONBIO, &mode);
|
||||||
|
#else
|
||||||
|
fcntl(session->DataSocket, F_SETFL, O_NONBLOCK);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SocketOps::Close(session->ListenerSocket);
|
||||||
|
session->ListenerSocket = INVALID_SOCKET;
|
||||||
|
|
||||||
|
const auto sessionID = session->ProcessHandle->ID.load();
|
||||||
|
const auto sessionPtr = session.get();
|
||||||
|
m_activeSessions.push_back(std::move(session));
|
||||||
|
m_pendingSessions.erase(m_pendingSessions.begin() + i);
|
||||||
|
m_activeSessionMap[sessionID] = sessionPtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (INT32 i = m_activeSessions.size() - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
auto &node = m_activeSessions[i];
|
||||||
|
|
||||||
|
auto nodeID = node->ProcessHandle->ID.load();
|
||||||
|
|
||||||
|
RingBufferView::PacketHeader header;
|
||||||
|
|
||||||
|
while (node->MINO->Pop(header, Span<UINT8>(m_recieveBuffer.data(), m_recieveBuffer.size())))
|
||||||
|
OnPacket(nodeID, header.ID, {m_recieveBuffer.data(), header.PayloadSize});
|
||||||
|
|
||||||
|
UINT8 signal;
|
||||||
|
const auto res = recv(node->DataSocket, &signal, 1, 0);
|
||||||
|
|
||||||
|
if (res == 1)
|
||||||
|
OnSignal(nodeID, signal);
|
||||||
|
|
||||||
|
if (res == 0 || (res < 0 && errno != EWOULDBLOCK))
|
||||||
|
{
|
||||||
|
ProcessOps::TerminateProcess(node->ProcessHandle);
|
||||||
|
|
||||||
|
FileOps::UnmapFile(node->MappedPtr);
|
||||||
|
FileOps::UnlinkSharedMemory(node->SharedMemName);
|
||||||
|
SocketOps::Close(node->DataSocket);
|
||||||
|
|
||||||
|
m_activeSessions.erase(m_activeSessions.begin() + i);
|
||||||
|
m_activeSessionMap.erase(nodeID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<NativeProcessID, String> IPC_Manager::SpawnNode(IN CONST FilePath &executablePath,
|
||||||
|
IN UINT32 sharedMemorySize)
|
||||||
|
{
|
||||||
|
auto session = std::make_unique<NodeSession>();
|
||||||
|
|
||||||
|
static Atomic<UINT32> s_idGen{0};
|
||||||
|
UINT32 sid = ++s_idGen;
|
||||||
|
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
char tempPath[MAX_PATH];
|
||||||
|
GetTempPathA(MAX_PATH, tempPath);
|
||||||
|
String sockPath = std::format("{}\\ia_sess_{}.sock", tempPath, sid);
|
||||||
|
#else
|
||||||
|
String sockPath = std::format("/tmp/ia_sess_{}.sock", sid);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
session->ListenerSocket = SocketOps::CreateUnixSocket();
|
||||||
|
if (!SocketOps::BindUnixSocket(session->ListenerSocket, sockPath.c_str()))
|
||||||
|
return MakeUnexpected("Failed to bind unique socket");
|
||||||
|
|
||||||
|
if (listen(session->ListenerSocket, 1) != 0)
|
||||||
|
return MakeUnexpected("Failed to listen on unqiue socket");
|
||||||
|
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
u_long mode = 1;
|
||||||
|
ioctlsocket(session->ListenerSocket, FIONBIO, &mode);
|
||||||
|
#else
|
||||||
|
fcntl(session->ListenerSocket, F_SETFL, O_NONBLOCK);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
String shmName = std::format("ia_shm_{}", sid);
|
||||||
|
auto mapRes = FileOps::MapSharedMemory(shmName, sharedMemorySize, TRUE);
|
||||||
|
if (!mapRes.has_value())
|
||||||
|
return MakeUnexpected("Failed to map shared memory");
|
||||||
|
|
||||||
|
PUINT8 mappedPtr = mapRes.value();
|
||||||
|
|
||||||
|
auto *layout = reinterpret_cast<IPC_SharedMemoryLayout *>(mappedPtr);
|
||||||
|
|
||||||
|
layout->Meta.Magic = 0x49414950;
|
||||||
|
layout->Meta.Version = 1;
|
||||||
|
layout->Meta.TotalSize = sharedMemorySize;
|
||||||
|
|
||||||
|
UINT64 headerSize = IPC_SharedMemoryLayout::GetHeaderSize();
|
||||||
|
UINT64 usableBytes = sharedMemorySize - headerSize;
|
||||||
|
|
||||||
|
UINT64 halfSize = (usableBytes / 2);
|
||||||
|
halfSize -= (halfSize % 64);
|
||||||
|
|
||||||
|
layout->MONI_DataOffset = headerSize;
|
||||||
|
layout->MONI_DataSize = halfSize;
|
||||||
|
|
||||||
|
layout->MINO_DataOffset = headerSize + halfSize;
|
||||||
|
layout->MINO_DataSize = halfSize;
|
||||||
|
|
||||||
|
session->MONI = std::make_unique<RingBufferView>(
|
||||||
|
&layout->MONI_Control, Span<UINT8>(mappedPtr + layout->MONI_DataOffset, layout->MONI_DataSize), TRUE);
|
||||||
|
|
||||||
|
session->MINO = std::make_unique<RingBufferView>(
|
||||||
|
&layout->MINO_Control, Span<UINT8>(mappedPtr + layout->MINO_DataOffset, layout->MINO_DataSize), TRUE);
|
||||||
|
|
||||||
|
IPC_ConnectionDescriptor desc;
|
||||||
|
desc.SocketPath = sockPath;
|
||||||
|
desc.SharedMemPath = shmName;
|
||||||
|
desc.SharedMemSize = sharedMemorySize;
|
||||||
|
|
||||||
|
String args = std::format("\"{}\"", desc.Serialize());
|
||||||
|
|
||||||
|
session->ProcessHandle = ProcessOps::SpawnProcessAsync(
|
||||||
|
FileOps::NormalizeExecutablePath(executablePath), args,
|
||||||
|
[sid](IN StringView line) {
|
||||||
|
UNUSED(sid);
|
||||||
|
UNUSED(line);
|
||||||
|
#if __IA_DEBUG
|
||||||
|
puts(std::format(__CC_MAGENTA "[Node:{}:STDOUT|STDERR]: {}" __CC_DEFAULT, sid, line).c_str());
|
||||||
|
#endif
|
||||||
|
},
|
||||||
|
[sid](IN Expected<INT32, String> result) {
|
||||||
|
UNUSED(sid);
|
||||||
|
UNUSED(result);
|
||||||
|
#if __IA_DEBUG
|
||||||
|
if (!result)
|
||||||
|
puts(std::format(__CC_RED "Failed to spawn Node: {} with error '{}'" __CC_DEFAULT, sid, result.error()).c_str());
|
||||||
|
else
|
||||||
|
puts(std::format(__CC_RED "[Node: {}]: Exited with code {}" __CC_DEFAULT, sid, *result).c_str());
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
|
||||||
|
// Give some time for child node to stablize
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
if (!session->ProcessHandle->IsActive())
|
||||||
|
return MakeUnexpected(std::format("Failed to spawn the child process \"{}\"", executablePath.string()));
|
||||||
|
|
||||||
|
auto processID = session->ProcessHandle->ID.load();
|
||||||
|
|
||||||
|
session->CreationTime = SteadyClock::now();
|
||||||
|
m_pendingSessions.push_back(std::move(session));
|
||||||
|
|
||||||
|
return processID;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL IPC_Manager::WaitTillNodeIsOnline(IN NativeProcessID nodeID)
|
||||||
|
{
|
||||||
|
BOOL isPending = true;
|
||||||
|
while (isPending)
|
||||||
|
{
|
||||||
|
isPending = false;
|
||||||
|
for (auto it = m_pendingSessions.begin(); it != m_pendingSessions.end(); it++)
|
||||||
|
{
|
||||||
|
if (it->get()->ProcessHandle->ID.load() == nodeID)
|
||||||
|
{
|
||||||
|
isPending = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
return m_activeSessionMap.contains(nodeID);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID IPC_Manager::ShutdownNode(IN NativeProcessID nodeID)
|
||||||
|
{
|
||||||
|
const auto itNode = m_activeSessionMap.find(nodeID);
|
||||||
|
if (itNode == m_activeSessionMap.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto &node = itNode->second;
|
||||||
|
|
||||||
|
ProcessOps::TerminateProcess(node->ProcessHandle);
|
||||||
|
FileOps::UnmapFile(node->MappedPtr);
|
||||||
|
FileOps::UnlinkSharedMemory(node->SharedMemName);
|
||||||
|
SocketOps::Close(node->DataSocket);
|
||||||
|
|
||||||
|
for (auto it = m_activeSessions.begin(); it != m_activeSessions.end(); it++)
|
||||||
|
{
|
||||||
|
if (it->get() == node)
|
||||||
|
{
|
||||||
|
m_activeSessions.erase(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_activeSessionMap.erase(itNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID IPC_Manager::SendSignal(IN NativeProcessID node, IN UINT8 signal)
|
||||||
|
{
|
||||||
|
const auto itNode = m_activeSessionMap.find(node);
|
||||||
|
if (itNode == m_activeSessionMap.end())
|
||||||
|
return;
|
||||||
|
itNode->second->SendSignal(signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID IPC_Manager::SendPacket(IN NativeProcessID node, IN UINT16 packetID, IN Span<CONST UINT8> payload)
|
||||||
|
{
|
||||||
|
const auto itNode = m_activeSessionMap.find(node);
|
||||||
|
if (itNode == m_activeSessionMap.end())
|
||||||
|
return;
|
||||||
|
itNode->second->SendPacket(packetID, payload);
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
43
Src/IACore/imp/cpp/JSON.cpp
Normal file
43
Src/IACore/imp/cpp/JSON.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <IACore/JSON.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
Expected<nlohmann::json, String> JSON::Parse(IN CONST String &json)
|
||||||
|
{
|
||||||
|
const auto parseResult = nlohmann::json::parse(json, nullptr, false, true);
|
||||||
|
if (parseResult.is_discarded())
|
||||||
|
return MakeUnexpected("Failed to parse JSON");
|
||||||
|
return parseResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<Pair<simdjson::dom::parser, simdjson::dom::object>, String> JSON::ParseReadOnly(IN CONST String &json)
|
||||||
|
{
|
||||||
|
simdjson::error_code error{};
|
||||||
|
simdjson::dom::parser parser;
|
||||||
|
simdjson::dom::object object;
|
||||||
|
if ((error = parser.parse(json).get(object)))
|
||||||
|
return MakeUnexpected(std::format("Failed to parse JSON : {}", simdjson::error_message(error)));
|
||||||
|
return std::make_pair(IA_MOVE(parser), IA_MOVE(object));
|
||||||
|
}
|
||||||
|
|
||||||
|
String JSON::Encode(IN nlohmann::json data)
|
||||||
|
{
|
||||||
|
return data.dump();
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
@ -15,34 +15,36 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#include <IACore/Logger.hpp>
|
#include <IACore/Logger.hpp>
|
||||||
#include <IACore/File.hpp>
|
#include <IACore/IACore.hpp>
|
||||||
|
#include <IACore/FileOps.hpp>
|
||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
{
|
{
|
||||||
Logger::ELogLevel Logger::s_logLevel{Logger::ELogLevel::WARN};
|
Logger::ELogLevel Logger::s_logLevel{Logger::ELogLevel::WARN};
|
||||||
HRTimePoint Logger::s_startTime{};
|
std::ofstream Logger::s_logFile{};
|
||||||
IACore::File *Logger::s_logFile{};
|
|
||||||
|
|
||||||
VOID Logger::Initialize()
|
VOID Logger::Initialize()
|
||||||
{
|
{
|
||||||
s_startTime = HRClock::now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID Logger::Terminate()
|
VOID Logger::Terminate()
|
||||||
{
|
{
|
||||||
if (s_logFile)
|
if (s_logFile.is_open())
|
||||||
{
|
{
|
||||||
s_logFile->Close();
|
s_logFile.flush();
|
||||||
delete s_logFile;
|
s_logFile.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL Logger::EnableLoggingToDisk(IN PCCHAR filePath)
|
BOOL Logger::EnableLoggingToDisk(IN PCCHAR filePath)
|
||||||
{
|
{
|
||||||
if (s_logFile)
|
if (s_logFile.is_open())
|
||||||
return true;
|
{
|
||||||
s_logFile = new IACore::File(filePath, IACore::File::EOpenFlags::Write);
|
s_logFile.flush();
|
||||||
return s_logFile->IsOpen();
|
s_logFile.close();
|
||||||
|
}
|
||||||
|
s_logFile.open(filePath);
|
||||||
|
return s_logFile.is_open();
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID Logger::SetLogLevel(IN ELogLevel logLevel)
|
VOID Logger::SetLogLevel(IN ELogLevel logLevel)
|
||||||
@ -50,16 +52,22 @@ namespace IACore
|
|||||||
s_logLevel = logLevel;
|
s_logLevel = logLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VOID Logger::FlushLogs()
|
||||||
|
{
|
||||||
|
std::cout.flush();
|
||||||
|
if (s_logFile)
|
||||||
|
s_logFile.flush();
|
||||||
|
}
|
||||||
|
|
||||||
VOID Logger::LogInternal(IN PCCHAR prefix, IN PCCHAR tag, IN String &&msg)
|
VOID Logger::LogInternal(IN PCCHAR prefix, IN PCCHAR tag, IN String &&msg)
|
||||||
{
|
{
|
||||||
std::chrono::duration<double> elapsed = HRClock::now() - s_startTime;
|
const auto outLine = std::format("[{:>8.3f}]: [{}]: {}", GetSecondsCount(), tag, msg);
|
||||||
double timestamp = elapsed.count();
|
|
||||||
const auto outLine = std::format("[{:>8.3f}]: [{}]: {}", timestamp, tag, msg);
|
|
||||||
std::cout << prefix << outLine << "\033[39m\n";
|
std::cout << prefix << outLine << "\033[39m\n";
|
||||||
if (s_logFile)
|
if (s_logFile)
|
||||||
{
|
{
|
||||||
s_logFile->GetStreamHandle()->write(outLine.data(), outLine.size());
|
s_logFile.write(outLine.data(), outLine.size());
|
||||||
s_logFile->GetStreamHandle()->put('\n');
|
s_logFile.put('\n');
|
||||||
|
s_logFile.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
324
Src/IACore/imp/cpp/ProcessOps.cpp
Normal file
324
Src/IACore/imp/cpp/ProcessOps.cpp
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <IACore/ProcessOps.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Output Buffering Helper
|
||||||
|
// Splits raw chunks into lines, preserving partial lines across chunks
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
struct LineBuffer
|
||||||
|
{
|
||||||
|
String Accumulator;
|
||||||
|
Function<VOID(StringView)> &Callback;
|
||||||
|
|
||||||
|
VOID Append(IN PCCHAR data, IN SIZE_T size);
|
||||||
|
VOID Flush();
|
||||||
|
};
|
||||||
|
} // namespace IACore
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
NativeProcessID ProcessOps::GetCurrentProcessID()
|
||||||
|
{
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
return ::GetCurrentProcessId();
|
||||||
|
#else
|
||||||
|
return getpid();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<INT32, String> ProcessOps::SpawnProcessSync(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(IN StringView line)> onOutputLineCallback)
|
||||||
|
{
|
||||||
|
Atomic<NativeProcessID> id;
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
return SpawnProcessWindows(command, args, onOutputLineCallback, id);
|
||||||
|
#else
|
||||||
|
return SpawnProcessPosix(command, args, onOutputLineCallback, id);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedPtr<ProcessHandle> ProcessOps::SpawnProcessAsync(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(IN StringView line)> onOutputLineCallback,
|
||||||
|
IN Function<VOID(Expected<INT32, String>)> onFinishCallback)
|
||||||
|
{
|
||||||
|
SharedPtr<ProcessHandle> handle = std::make_shared<ProcessHandle>();
|
||||||
|
handle->IsRunning = true;
|
||||||
|
|
||||||
|
handle->ThreadHandle =
|
||||||
|
JoiningThread([=, h = handle.get(), cmd = IA_MOVE(command), args = std::move(args)]() mutable {
|
||||||
|
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
auto result = SpawnProcessWindows(cmd, args, onOutputLineCallback, h->ID);
|
||||||
|
#else
|
||||||
|
auto result = SpawnProcessPosix(cmd, args, onOutputLineCallback, h->ID);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
h->IsRunning = false;
|
||||||
|
|
||||||
|
if (!onFinishCallback)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
onFinishCallback(MakeUnexpected(result.error()));
|
||||||
|
else
|
||||||
|
onFinishCallback(*result);
|
||||||
|
});
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID ProcessOps::TerminateProcess(IN CONST SharedPtr<ProcessHandle> &handle)
|
||||||
|
{
|
||||||
|
if (!handle || !handle->IsActive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
NativeProcessID pid = handle->ID.load();
|
||||||
|
if (pid == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
|
||||||
|
if (hProcess != NULL)
|
||||||
|
{
|
||||||
|
TerminateProcess(hProcess, 9);
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
kill(pid, SIGKILL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
Expected<INT32, String> ProcessOps::SpawnProcessWindows(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(StringView)> onOutputLineCallback,
|
||||||
|
OUT Atomic<NativeProcessID> &id)
|
||||||
|
{
|
||||||
|
SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; // Allow inheritance
|
||||||
|
HANDLE hRead = NULL, hWrite = NULL;
|
||||||
|
|
||||||
|
if (!CreatePipe(&hRead, &hWrite, &saAttr, 0))
|
||||||
|
return tl::make_unexpected("Failed to create pipe");
|
||||||
|
|
||||||
|
// Ensure the read handle to the pipe for STDOUT is NOT inherited
|
||||||
|
if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0))
|
||||||
|
return tl::make_unexpected("Failed to secure pipe handles");
|
||||||
|
|
||||||
|
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
|
||||||
|
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||||
|
si.hStdOutput = hWrite;
|
||||||
|
si.hStdError = hWrite; // Merge stderr
|
||||||
|
si.hStdInput = NULL; // No input
|
||||||
|
|
||||||
|
PROCESS_INFORMATION pi = {0};
|
||||||
|
|
||||||
|
// Windows command line needs to be mutable and concatenated
|
||||||
|
String commandLine = BuildString("\"", command, "\" ", args);
|
||||||
|
|
||||||
|
BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
|
||||||
|
|
||||||
|
// Important: Close write end in parent, otherwise ReadFile never returns EOF!
|
||||||
|
CloseHandle(hWrite);
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
CloseHandle(hRead);
|
||||||
|
return tl::make_unexpected(String("CreateProcess failed: ") + std::to_string(GetLastError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
id.store(pi.dwProcessId);
|
||||||
|
|
||||||
|
// Read Loop
|
||||||
|
LineBuffer lineBuf{"", onOutputLineCallback};
|
||||||
|
DWORD bytesRead;
|
||||||
|
CHAR buffer[4096];
|
||||||
|
|
||||||
|
while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0)
|
||||||
|
{
|
||||||
|
lineBuf.Append(buffer, bytesRead);
|
||||||
|
}
|
||||||
|
lineBuf.Flush();
|
||||||
|
|
||||||
|
// NOW we wait for exit code
|
||||||
|
DWORD exitCode = 0;
|
||||||
|
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||||
|
GetExitCodeProcess(pi.hProcess, &exitCode);
|
||||||
|
|
||||||
|
CloseHandle(pi.hProcess);
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
CloseHandle(hRead);
|
||||||
|
id.store(0);
|
||||||
|
|
||||||
|
return static_cast<INT32>(exitCode);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if IA_PLATFORM_UNIX
|
||||||
|
Expected<INT32, String> ProcessOps::SpawnProcessPosix(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(StringView)> onOutputLineCallback,
|
||||||
|
OUT Atomic<NativeProcessID> &id)
|
||||||
|
{
|
||||||
|
int pipefd[2];
|
||||||
|
if (pipe(pipefd) == -1)
|
||||||
|
return tl::make_unexpected("Failed to create pipe");
|
||||||
|
|
||||||
|
pid_t pid = fork();
|
||||||
|
|
||||||
|
if (pid == -1)
|
||||||
|
{
|
||||||
|
return tl::make_unexpected("Failed to fork process");
|
||||||
|
}
|
||||||
|
else if (pid == 0)
|
||||||
|
{
|
||||||
|
// --- Child Process ---
|
||||||
|
close(pipefd[0]);
|
||||||
|
|
||||||
|
dup2(pipefd[1], STDOUT_FILENO);
|
||||||
|
dup2(pipefd[1], STDERR_FILENO);
|
||||||
|
close(pipefd[1]);
|
||||||
|
|
||||||
|
// --- ARGUMENT PARSING START ---
|
||||||
|
std::vector<std::string> argStorage; // To keep strings alive
|
||||||
|
std::vector<char *> argv;
|
||||||
|
|
||||||
|
std::string cmdStr = command;
|
||||||
|
argv.push_back(cmdStr.data());
|
||||||
|
|
||||||
|
// Manual Quote-Aware Splitter
|
||||||
|
std::string currentToken;
|
||||||
|
bool inQuotes = false;
|
||||||
|
|
||||||
|
for (char c : args)
|
||||||
|
{
|
||||||
|
if (c == '\"')
|
||||||
|
{
|
||||||
|
inQuotes = !inQuotes;
|
||||||
|
// Determine if you want to keep the quotes or strip them.
|
||||||
|
// Usually for execvp, you strip them so the shell receives the raw content.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == ' ' && !inQuotes)
|
||||||
|
{
|
||||||
|
if (!currentToken.empty())
|
||||||
|
{
|
||||||
|
argStorage.push_back(currentToken);
|
||||||
|
currentToken.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentToken += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!currentToken.empty())
|
||||||
|
{
|
||||||
|
argStorage.push_back(currentToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build char* array from the std::string storage
|
||||||
|
for (auto &s : argStorage)
|
||||||
|
{
|
||||||
|
argv.push_back(s.data());
|
||||||
|
}
|
||||||
|
argv.push_back(nullptr);
|
||||||
|
// --- ARGUMENT PARSING END ---
|
||||||
|
|
||||||
|
execvp(argv[0], argv.data());
|
||||||
|
_exit(127);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// --- Parent Process ---
|
||||||
|
id.store(pid);
|
||||||
|
|
||||||
|
close(pipefd[1]);
|
||||||
|
|
||||||
|
LineBuffer lineBuf{"", onOutputLineCallback};
|
||||||
|
char buffer[4096];
|
||||||
|
ssize_t count;
|
||||||
|
|
||||||
|
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0)
|
||||||
|
{
|
||||||
|
lineBuf.Append(buffer, count);
|
||||||
|
}
|
||||||
|
lineBuf.Flush();
|
||||||
|
close(pipefd[0]);
|
||||||
|
|
||||||
|
int status;
|
||||||
|
waitpid(pid, &status, 0);
|
||||||
|
|
||||||
|
id.store(0);
|
||||||
|
if (WIFEXITED(status))
|
||||||
|
return WEXITSTATUS(status);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} // namespace IACore
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
VOID LineBuffer::Append(IN PCCHAR data, IN SIZE_T size)
|
||||||
|
{
|
||||||
|
SIZE_T start = 0;
|
||||||
|
for (SIZE_T i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
if (data[i] == '\n' || data[i] == '\r')
|
||||||
|
{
|
||||||
|
// Flush Accumulator + current chunk
|
||||||
|
if (!Accumulator.empty())
|
||||||
|
{
|
||||||
|
Accumulator.append(data + start, i - start);
|
||||||
|
if (!Accumulator.empty())
|
||||||
|
Callback(Accumulator);
|
||||||
|
Accumulator.clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Zero copy optimization for pure lines in one chunk
|
||||||
|
if (i > start)
|
||||||
|
Callback(StringView(data + start, i - start));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip \r\n sequence if needed, or just start next
|
||||||
|
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n')
|
||||||
|
i++;
|
||||||
|
start = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save remaining partial line
|
||||||
|
if (start < size)
|
||||||
|
{
|
||||||
|
Accumulator.append(data + start, size - start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID LineBuffer::Flush()
|
||||||
|
{
|
||||||
|
if (!Accumulator.empty())
|
||||||
|
{
|
||||||
|
Callback(Accumulator);
|
||||||
|
Accumulator.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
97
Src/IACore/imp/cpp/SocketOps.cpp
Normal file
97
Src/IACore/imp/cpp/SocketOps.cpp
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <IACore/SocketOps.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
INT32 SocketOps::s_initCount{0};
|
||||||
|
|
||||||
|
VOID SocketOps::Close(IN SocketHandle sock)
|
||||||
|
{
|
||||||
|
if (sock == INVALID_SOCKET)
|
||||||
|
return;
|
||||||
|
CLOSE_SOCKET(sock);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (!IS_VALID_SOCKET(sock))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
sockaddr_in addr{};
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(port);
|
||||||
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
|
||||||
|
bool isFree = false;
|
||||||
|
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == 0)
|
||||||
|
isFree = true;
|
||||||
|
|
||||||
|
CLOSE_SOCKET(sock);
|
||||||
|
|
||||||
|
return isFree;
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
59
Src/IACore/imp/cpp/StreamReader.cpp
Normal file
59
Src/IACore/imp/cpp/StreamReader.cpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <IACore/StreamReader.hpp>
|
||||||
|
#include <IACore/FileOps.hpp>
|
||||||
|
#include <IACore/Logger.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
StreamReader::StreamReader(IN CONST FilePath &path) : m_storageType(EStorageType::OWNING_MMAP)
|
||||||
|
{
|
||||||
|
const auto t = FileOps::MapFile(path, m_dataSize);
|
||||||
|
if (!t)
|
||||||
|
{
|
||||||
|
Logger::Error("Failed to memory map file {}", path.string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_data = *t;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamReader::StreamReader(IN Vector<UINT8> &&data)
|
||||||
|
: m_owningVector(IA_MOVE(data)), m_storageType(EStorageType::OWNING_VECTOR)
|
||||||
|
{
|
||||||
|
m_data = m_owningVector.data();
|
||||||
|
m_dataSize = m_owningVector.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamReader::StreamReader(IN Span<CONST UINT8> data)
|
||||||
|
: m_data(data.data()), m_dataSize(data.size()), m_storageType(EStorageType::NON_OWNING)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamReader::~StreamReader()
|
||||||
|
{
|
||||||
|
switch (m_storageType)
|
||||||
|
{
|
||||||
|
case EStorageType::OWNING_MMAP:
|
||||||
|
FileOps::UnmapFile(m_data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EStorageType::NON_OWNING:
|
||||||
|
case EStorageType::OWNING_VECTOR:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
97
Src/IACore/imp/cpp/StreamWriter.cpp
Normal file
97
Src/IACore/imp/cpp/StreamWriter.cpp
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <IACore/StreamWriter.hpp>
|
||||||
|
#include <IACore/Logger.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
StreamWriter::StreamWriter() : m_storageType(EStorageType::OWNING_VECTOR)
|
||||||
|
{
|
||||||
|
m_owningVector.resize(m_capacity = 256);
|
||||||
|
m_buffer = m_owningVector.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamWriter::StreamWriter(IN Span<UINT8> data)
|
||||||
|
: m_buffer(data.data()), m_capacity(data.size()), m_storageType(EStorageType::NON_OWNING)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamWriter::StreamWriter(IN CONST FilePath &path) : m_filePath(path), m_storageType(EStorageType::OWNING_FILE)
|
||||||
|
{
|
||||||
|
IA_RELEASE_ASSERT(!path.empty());
|
||||||
|
const auto f = fopen(m_filePath.c_str(), "wb");
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
Logger::Error("Failed to open file for writing {}", m_filePath.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fputc(0, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
m_owningVector.resize(m_capacity = 256);
|
||||||
|
m_buffer = m_owningVector.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamWriter::~StreamWriter()
|
||||||
|
{
|
||||||
|
switch (m_storageType)
|
||||||
|
{
|
||||||
|
case EStorageType::OWNING_FILE: {
|
||||||
|
IA_RELEASE_ASSERT(!m_filePath.empty());
|
||||||
|
const auto f = fopen(m_filePath.c_str(), "wb");
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
Logger::Error("Failed to open file for writing {}", m_filePath.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fwrite(m_owningVector.data(), 1, m_owningVector.size(), f);
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EStorageType::OWNING_VECTOR:
|
||||||
|
case EStorageType::NON_OWNING:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define HANDLE_OUT_OF_CAPACITY(_size) \
|
||||||
|
if B_UNLIKELY ((m_cursor + _size) > m_capacity) \
|
||||||
|
{ \
|
||||||
|
if (m_storageType == EStorageType::NON_OWNING) \
|
||||||
|
return false; \
|
||||||
|
m_owningVector.resize(m_capacity + (_size << 1)); \
|
||||||
|
m_capacity = m_owningVector.size(); \
|
||||||
|
m_buffer = m_owningVector.data(); \
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL StreamWriter::Write(IN UINT8 byte, IN SIZE_T count)
|
||||||
|
{
|
||||||
|
HANDLE_OUT_OF_CAPACITY(count);
|
||||||
|
memset(&m_buffer[m_cursor], byte, count);
|
||||||
|
m_cursor += count;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL StreamWriter::Write(IN PCVOID buffer, IN SIZE_T size)
|
||||||
|
{
|
||||||
|
HANDLE_OUT_OF_CAPACITY(size);
|
||||||
|
memcpy(&m_buffer[m_cursor], buffer, size);
|
||||||
|
m_cursor += size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
101
Src/IACore/imp/cpp/StringOps.cpp
Normal file
101
Src/IACore/imp/cpp/StringOps.cpp
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <IACore/StringOps.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
CONST String BASE64_CHAR_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
|
||||||
|
String StringOps::EncodeBase64(IN Span<CONST UINT8> data)
|
||||||
|
{
|
||||||
|
String result;
|
||||||
|
result.reserve(((data.size() + 2) / 3) * 4);
|
||||||
|
for (size_t i = 0; i < data.size(); i += 3)
|
||||||
|
{
|
||||||
|
uint32_t value = 0;
|
||||||
|
INT32 num_bytes = 0;
|
||||||
|
for (INT32 j = 0; j < 3 && (i + j) < data.size(); ++j)
|
||||||
|
{
|
||||||
|
value = (value << 8) | data[i + j];
|
||||||
|
num_bytes++;
|
||||||
|
}
|
||||||
|
for (INT32 j = 0; j < num_bytes + 1; ++j)
|
||||||
|
{
|
||||||
|
if (j < 4)
|
||||||
|
{
|
||||||
|
result += BASE64_CHAR_TABLE[(value >> (6 * (3 - j))) & 0x3F];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (num_bytes < 3)
|
||||||
|
{
|
||||||
|
for (INT32 j = 0; j < (3 - num_bytes); ++j)
|
||||||
|
{
|
||||||
|
result += '=';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<UINT8> StringOps::DecodeBase64(IN CONST String &data)
|
||||||
|
{
|
||||||
|
Vector<UINT8> result;
|
||||||
|
|
||||||
|
CONST AUTO isBase64 = [](UINT8 c) { return (isalnum(c) || (c == '+') || (c == '/')); };
|
||||||
|
|
||||||
|
INT32 in_len = data.size();
|
||||||
|
INT32 i = 0, j = 0, in_ = 0;
|
||||||
|
UINT8 tmpBuf0[4], tmpBuf1[3];
|
||||||
|
|
||||||
|
while (in_len-- && (data[in_] != '=') && isBase64(data[in_]))
|
||||||
|
{
|
||||||
|
tmpBuf0[i++] = data[in_];
|
||||||
|
in_++;
|
||||||
|
if (i == 4)
|
||||||
|
{
|
||||||
|
for (i = 0; i < 4; i++)
|
||||||
|
tmpBuf0[i] = BASE64_CHAR_TABLE.find(tmpBuf0[i]);
|
||||||
|
|
||||||
|
tmpBuf1[0] = (tmpBuf0[0] << 2) + ((tmpBuf0[1] & 0x30) >> 4);
|
||||||
|
tmpBuf1[1] = ((tmpBuf0[1] & 0xf) << 4) + ((tmpBuf0[2] & 0x3c) >> 2);
|
||||||
|
tmpBuf1[2] = ((tmpBuf0[2] & 0x3) << 6) + tmpBuf0[3];
|
||||||
|
|
||||||
|
for (i = 0; (i < 3); i++)
|
||||||
|
result.push_back(tmpBuf1[i]);
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i)
|
||||||
|
{
|
||||||
|
for (j = i; j < 4; j++)
|
||||||
|
tmpBuf0[j] = 0;
|
||||||
|
|
||||||
|
for (j = 0; j < 4; j++)
|
||||||
|
tmpBuf0[j] = BASE64_CHAR_TABLE.find(tmpBuf0[j]);
|
||||||
|
|
||||||
|
tmpBuf1[0] = (tmpBuf0[0] << 2) + ((tmpBuf0[1] & 0x30) >> 4);
|
||||||
|
tmpBuf1[1] = ((tmpBuf0[1] & 0xf) << 4) + ((tmpBuf0[2] & 0x3c) >> 2);
|
||||||
|
tmpBuf1[2] = ((tmpBuf0[2] & 0x3) << 6) + tmpBuf0[3];
|
||||||
|
|
||||||
|
for (j = 0; (j < i - 1); j++)
|
||||||
|
result.push_back(tmpBuf1[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
226
Src/IACore/inc/IACore/ADT/RingBuffer.hpp
Normal file
226
Src/IACore/inc/IACore/ADT/RingBuffer.hpp
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
class RingBufferView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
STATIC CONSTEXPR UINT16 PACKET_ID_SKIP = 0;
|
||||||
|
|
||||||
|
struct ControlBlock
|
||||||
|
{
|
||||||
|
struct alignas(64)
|
||||||
|
{
|
||||||
|
Atomic<UINT32> WriteOffset{0};
|
||||||
|
} Producer;
|
||||||
|
|
||||||
|
struct alignas(64)
|
||||||
|
{
|
||||||
|
Atomic<UINT32> ReadOffset{0};
|
||||||
|
// Capacity is effectively constant after init,
|
||||||
|
// so it doesn't cause false sharing invalidations.
|
||||||
|
UINT32 Capacity{0};
|
||||||
|
} Consumer;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(offsetof(ControlBlock, Consumer) == 64, "False sharing detected in ControlBlock");
|
||||||
|
|
||||||
|
// All of the data in ring buffer will be stored as packets
|
||||||
|
struct PacketHeader
|
||||||
|
{
|
||||||
|
PacketHeader() : ID(0), PayloadSize(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PacketHeader(IN UINT16 id) : ID(id), PayloadSize(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PacketHeader(IN UINT16 id, IN UINT16 payloadSize) : ID(id), PayloadSize(payloadSize)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT16 ID{};
|
||||||
|
UINT16 PayloadSize{};
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
INLINE RingBufferView(IN Span<UINT8> buffer, IN BOOL isOwner);
|
||||||
|
INLINE RingBufferView(IN ControlBlock *controlBlock, IN Span<UINT8> buffer, IN BOOL isOwner);
|
||||||
|
|
||||||
|
INLINE INT32 Pop(OUT PacketHeader &outHeader, OUT Span<UINT8> outBuffer);
|
||||||
|
INLINE BOOL Push(IN UINT16 packetID, IN Span<CONST UINT8> data);
|
||||||
|
|
||||||
|
INLINE ControlBlock *GetControlBlock();
|
||||||
|
|
||||||
|
private:
|
||||||
|
PUINT8 m_dataPtr{};
|
||||||
|
UINT32 m_capacity{};
|
||||||
|
ControlBlock *m_controlBlock{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
INLINE VOID WriteWrapped(IN UINT32 offset, IN PCVOID data, IN UINT32 size);
|
||||||
|
INLINE VOID ReadWrapped(IN UINT32 offset, OUT PVOID outData, IN UINT32 size);
|
||||||
|
};
|
||||||
|
} // namespace IACore
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
RingBufferView::RingBufferView(IN Span<UINT8> buffer, IN BOOL isOwner)
|
||||||
|
{
|
||||||
|
IA_ASSERT(buffer.size() > sizeof(ControlBlock));
|
||||||
|
|
||||||
|
m_controlBlock = reinterpret_cast<ControlBlock *>(buffer.data());
|
||||||
|
m_dataPtr = buffer.data() + sizeof(ControlBlock);
|
||||||
|
|
||||||
|
m_capacity = static_cast<UINT32>(buffer.size()) - sizeof(ControlBlock);
|
||||||
|
|
||||||
|
if (isOwner)
|
||||||
|
{
|
||||||
|
m_controlBlock->Consumer.Capacity = m_capacity;
|
||||||
|
m_controlBlock->Producer.WriteOffset.store(0, std::memory_order_release);
|
||||||
|
m_controlBlock->Consumer.ReadOffset.store(0, std::memory_order_release);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
IA_ASSERT(m_controlBlock->Consumer.Capacity == m_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
RingBufferView::RingBufferView(IN ControlBlock *controlBlock, IN Span<UINT8> buffer, IN BOOL isOwner)
|
||||||
|
{
|
||||||
|
IA_ASSERT(controlBlock != nullptr);
|
||||||
|
IA_ASSERT(buffer.size() > 0);
|
||||||
|
|
||||||
|
m_controlBlock = controlBlock;
|
||||||
|
m_dataPtr = buffer.data();
|
||||||
|
m_capacity = static_cast<UINT32>(buffer.size());
|
||||||
|
|
||||||
|
if (isOwner)
|
||||||
|
{
|
||||||
|
m_controlBlock->Consumer.Capacity = m_capacity;
|
||||||
|
m_controlBlock->Producer.WriteOffset.store(0, std::memory_order_release);
|
||||||
|
m_controlBlock->Consumer.ReadOffset.store(0, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
INT32 RingBufferView::Pop(OUT PacketHeader &outHeader, OUT Span<UINT8> outBuffer)
|
||||||
|
{
|
||||||
|
UINT32 write = m_controlBlock->Producer.WriteOffset.load(std::memory_order_acquire);
|
||||||
|
UINT32 read = m_controlBlock->Consumer.ReadOffset.load(std::memory_order_relaxed);
|
||||||
|
UINT32 cap = m_capacity;
|
||||||
|
|
||||||
|
if (read == write)
|
||||||
|
return 0; // Empty
|
||||||
|
|
||||||
|
ReadWrapped(read, &outHeader, sizeof(PacketHeader));
|
||||||
|
|
||||||
|
if (outHeader.PayloadSize > outBuffer.size())
|
||||||
|
return -static_cast<INT32>(outHeader.PayloadSize);
|
||||||
|
|
||||||
|
if (outHeader.PayloadSize > 0)
|
||||||
|
{
|
||||||
|
UINT32 dataReadOffset = (read + sizeof(PacketHeader)) % cap;
|
||||||
|
ReadWrapped(dataReadOffset, outBuffer.data(), outHeader.PayloadSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move read pointer forward
|
||||||
|
UINT32 newReadOffset = (read + sizeof(PacketHeader) + outHeader.PayloadSize) % cap;
|
||||||
|
m_controlBlock->Consumer.ReadOffset.store(newReadOffset, std::memory_order_release);
|
||||||
|
|
||||||
|
return outHeader.PayloadSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL RingBufferView::Push(IN UINT16 packetID, IN Span<CONST UINT8> data)
|
||||||
|
{
|
||||||
|
IA_ASSERT(data.size() <= UINT16_MAX);
|
||||||
|
|
||||||
|
const UINT32 totalSize = sizeof(PacketHeader) + static_cast<UINT32>(data.size());
|
||||||
|
|
||||||
|
UINT32 read = m_controlBlock->Consumer.ReadOffset.load(std::memory_order_acquire);
|
||||||
|
UINT32 write = m_controlBlock->Producer.WriteOffset.load(std::memory_order_relaxed);
|
||||||
|
UINT32 cap = m_capacity;
|
||||||
|
|
||||||
|
UINT32 freeSpace = (read <= write) ? (m_capacity - write) + read : (read - write);
|
||||||
|
|
||||||
|
// Ensure to always leave 1 byte empty to prevent Read == Write ambiguity (Wait-Free Ring Buffer standard)
|
||||||
|
if (freeSpace <= totalSize)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
PacketHeader header{packetID, static_cast<UINT16>(data.size())};
|
||||||
|
WriteWrapped(write, &header, sizeof(PacketHeader));
|
||||||
|
|
||||||
|
UINT32 dataWriteOffset = (write + sizeof(PacketHeader)) % cap;
|
||||||
|
|
||||||
|
if (data.size() > 0)
|
||||||
|
{
|
||||||
|
WriteWrapped(dataWriteOffset, data.data(), static_cast<UINT32>(data.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT32 newWriteOffset = (dataWriteOffset + data.size()) % cap;
|
||||||
|
m_controlBlock->Producer.WriteOffset.store(newWriteOffset, std::memory_order_release);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
RingBufferView::ControlBlock *RingBufferView::GetControlBlock()
|
||||||
|
{
|
||||||
|
return m_controlBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID RingBufferView::WriteWrapped(IN UINT32 offset, IN PCVOID data, IN UINT32 size)
|
||||||
|
{
|
||||||
|
if (offset + size <= m_capacity)
|
||||||
|
{
|
||||||
|
// Contiguous write
|
||||||
|
memcpy(m_dataPtr + offset, data, size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Split write
|
||||||
|
UINT32 firstChunk = m_capacity - offset;
|
||||||
|
UINT32 secondChunk = size - firstChunk;
|
||||||
|
|
||||||
|
const UINT8 *src = static_cast<const UINT8 *>(data);
|
||||||
|
|
||||||
|
memcpy(m_dataPtr + offset, src, firstChunk);
|
||||||
|
memcpy(m_dataPtr, src + firstChunk, secondChunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID RingBufferView::ReadWrapped(IN UINT32 offset, OUT PVOID outData, IN UINT32 size)
|
||||||
|
{
|
||||||
|
if (offset + size <= m_capacity)
|
||||||
|
{
|
||||||
|
// Contiguous read
|
||||||
|
memcpy(outData, m_dataPtr + offset, size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Split read
|
||||||
|
UINT32 firstChunk = m_capacity - offset;
|
||||||
|
UINT32 secondChunk = size - firstChunk;
|
||||||
|
|
||||||
|
UINT8 *dst = static_cast<UINT8 *>(outData);
|
||||||
|
|
||||||
|
memcpy(dst, m_dataPtr + offset, firstChunk);
|
||||||
|
memcpy(dst + firstChunk, m_dataPtr, secondChunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
74
Src/IACore/inc/IACore/AsyncOps.hpp
Normal file
74
Src/IACore/inc/IACore/AsyncOps.hpp
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
class AsyncOps
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using TaskTag = UINT64;
|
||||||
|
using WorkerID = UINT16;
|
||||||
|
|
||||||
|
STATIC CONSTEXPR WorkerID MainThreadWorkerID = 0;
|
||||||
|
|
||||||
|
enum class Priority : UINT8
|
||||||
|
{
|
||||||
|
High,
|
||||||
|
Normal
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Schedule
|
||||||
|
{
|
||||||
|
Atomic<INT32> Counter{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
STATIC VOID InitializeScheduler(IN UINT8 workerCount = 0);
|
||||||
|
STATIC VOID TerminateScheduler();
|
||||||
|
|
||||||
|
STATIC VOID ScheduleTask(IN Function<VOID(IN WorkerID workerID)> task, IN TaskTag tag, IN Schedule *schedule,
|
||||||
|
IN Priority priority = Priority::Normal);
|
||||||
|
|
||||||
|
STATIC VOID CancelTasksOfTag(IN TaskTag tag);
|
||||||
|
|
||||||
|
STATIC VOID WaitForScheduleCompletion(IN Schedule *schedule);
|
||||||
|
|
||||||
|
STATIC VOID RunTask(IN Function<VOID()> task);
|
||||||
|
|
||||||
|
STATIC WorkerID GetWorkerCount();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ScheduledTask
|
||||||
|
{
|
||||||
|
TaskTag Tag{};
|
||||||
|
Schedule *ScheduleHandle{};
|
||||||
|
Function<VOID(IN WorkerID workerID)> Task{};
|
||||||
|
};
|
||||||
|
|
||||||
|
STATIC VOID ScheduleWorkerLoop(IN StopToken stopToken, IN WorkerID workerID);
|
||||||
|
|
||||||
|
private:
|
||||||
|
STATIC Mutex s_queueMutex;
|
||||||
|
STATIC ConditionVariable s_wakeCondition;
|
||||||
|
STATIC Vector<JoiningThread> s_scheduleWorkers;
|
||||||
|
STATIC Deque<ScheduledTask> s_highPriorityQueue;
|
||||||
|
STATIC Deque<ScheduledTask> s_normalPriorityQueue;
|
||||||
|
};
|
||||||
|
} // namespace IACore
|
||||||
@ -1,101 +0,0 @@
|
|||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
|
||||||
|
|
||||||
namespace IACore {
|
|
||||||
|
|
||||||
class BinaryReader {
|
|
||||||
public:
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Construction (Zero Copy)
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Accepts Vector<UINT8>, std::array, or C-arrays automatically
|
|
||||||
BinaryReader(std::span<const UINT8> data)
|
|
||||||
: m_span(data), m_cursor(0) {}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Core API
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Generic Primitive Reader (Read<UINT32>(), Read<FLOAT32>(), etc.)
|
|
||||||
template <typename T>
|
|
||||||
NO_DISCARD("Check for EOF")
|
|
||||||
Expected<T, String> Read() {
|
|
||||||
constexpr SIZE_T size = sizeof(T);
|
|
||||||
|
|
||||||
if B_UNLIKELY((m_cursor + size > m_span.size())) {
|
|
||||||
return MakeUnexpected(String("Unexpected EOF reading type"));
|
|
||||||
}
|
|
||||||
|
|
||||||
T value;
|
|
||||||
// SAFE: memcpy handles alignment issues on ARM/Android automatically.
|
|
||||||
// Modern compilers optimize this into a single register load instruction.
|
|
||||||
std::memcpy(&value, &m_span[m_cursor], size);
|
|
||||||
|
|
||||||
m_cursor += size;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Batch Read (Copy to external buffer)
|
|
||||||
tl::expected<void, String> Read(PVOID buffer, SIZE_T size) {
|
|
||||||
if B_UNLIKELY((m_cursor + size > m_span.size())) {
|
|
||||||
return tl::make_unexpected(String("Unexpected EOF reading buffer"));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::memcpy(buffer, &m_span[m_cursor], size);
|
|
||||||
m_cursor += size;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// String Reader (Null Terminated or Length Prefixed)
|
|
||||||
tl::expected<String, String> ReadString(SIZE_T length) {
|
|
||||||
if B_UNLIKELY((m_cursor + length > m_span.size())) {
|
|
||||||
return tl::make_unexpected(String("Unexpected EOF reading string"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create string from current pointer
|
|
||||||
String str(REINTERPRET(&m_span[m_cursor], const char*), length);
|
|
||||||
m_cursor += length;
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Navigation
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
VOID Skip(SIZE_T amount) {
|
|
||||||
// Clamp to end
|
|
||||||
m_cursor = std::min(m_cursor + amount, m_span.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
VOID Seek(SIZE_T pos) {
|
|
||||||
if (pos > m_span.size()) m_cursor = m_span.size();
|
|
||||||
else m_cursor = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
SIZE_T Cursor() CONST { return m_cursor; }
|
|
||||||
SIZE_T Remaining() CONST { return m_span.size() - m_cursor; }
|
|
||||||
BOOL IsEOF() CONST { return m_cursor >= m_span.size(); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::span<const UINT8> m_span;
|
|
||||||
SIZE_T m_cursor;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,100 +0,0 @@
|
|||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
|
||||||
|
|
||||||
namespace IACore {
|
|
||||||
|
|
||||||
class BinaryWriter {
|
|
||||||
public:
|
|
||||||
// Mode 1: Append to a Vector (Growing)
|
|
||||||
BinaryWriter(Vector<UINT8>& target) : m_buffer(&target), m_span({}), m_cursor(target.size()), m_isVector(true) {}
|
|
||||||
|
|
||||||
// Mode 2: Write into existing fixed memory (No allocs)
|
|
||||||
BinaryWriter(std::span<UINT8> target) : m_buffer(nullptr), m_span(target), m_cursor(0), m_isVector(false) {}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Core API
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Append T (Handling Endianness automatically if needed)
|
|
||||||
template <typename T>
|
|
||||||
VOID Write(T value) {
|
|
||||||
// If we are Little Endian (x86/ARM), this compiles to a raw MOV/memcpy.
|
|
||||||
// If we need specific Endianness (Network Byte Order), use std::byteswap (C++23)
|
|
||||||
// or a simple swap helper.
|
|
||||||
// For a game engine, we usually stick to Native Endian (LE) for speed.
|
|
||||||
|
|
||||||
CONST SIZE_T size = sizeof(T);
|
|
||||||
|
|
||||||
if (m_isVector) {
|
|
||||||
// Vector guarantees contiguous memory, but push_back is slow for primitives.
|
|
||||||
// We resize and memcpy.
|
|
||||||
SIZE_T currentPos = m_buffer->size();
|
|
||||||
m_buffer->resize(currentPos + size);
|
|
||||||
std::memcpy(m_buffer->data() + currentPos, &value, size);
|
|
||||||
} else {
|
|
||||||
// Fixed Buffer Safety Check
|
|
||||||
if B_UNLIKELY((m_cursor + size > m_span.size())) {
|
|
||||||
IA_PANIC("BinaryWriter Overflow");
|
|
||||||
}
|
|
||||||
std::memcpy(m_span.data() + m_cursor, &value, size);
|
|
||||||
m_cursor += size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Random Access Write (Replaces put32, put16, etc.)
|
|
||||||
template <typename T>
|
|
||||||
VOID WriteAt(SIZE_T position, T value) {
|
|
||||||
PUINT8 ptr = GetPtrAt(position);
|
|
||||||
if (ptr) {
|
|
||||||
std::memcpy(ptr, &value, sizeof(T));
|
|
||||||
} else {
|
|
||||||
IA_PANIC("BinaryWriter Out of Bounds Write");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VOID WriteBytes(CONST PVOID data, SIZE_T size) {
|
|
||||||
if (m_isVector) {
|
|
||||||
SIZE_T currentPos = m_buffer->size();
|
|
||||||
m_buffer->resize(currentPos + size);
|
|
||||||
std::memcpy(m_buffer->data() + currentPos, data, size);
|
|
||||||
} else {
|
|
||||||
if B_UNLIKELY((m_cursor + size > m_span.size())) IA_PANIC("Overflow");
|
|
||||||
std::memcpy(m_span.data() + m_cursor, data, size);
|
|
||||||
m_cursor += size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PUINT8 GetPtrAt(SIZE_T pos) {
|
|
||||||
if (m_isVector) {
|
|
||||||
if (pos >= m_buffer->size()) return nullptr;
|
|
||||||
return m_buffer->data() + pos;
|
|
||||||
} else {
|
|
||||||
if (pos >= m_span.size()) return nullptr;
|
|
||||||
return m_span.data() + pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Vector<UINT8>* m_buffer;
|
|
||||||
std::span<UINT8> m_span;
|
|
||||||
SIZE_T m_cursor;
|
|
||||||
BOOL m_isVector;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
50
Src/IACore/inc/IACore/DataOps.hpp
Normal file
50
Src/IACore/inc/IACore/DataOps.hpp
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
class DataOps
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class CompressionType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Gzip,
|
||||||
|
Zlib
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
STATIC UINT32 Hash(IN CONST String &string);
|
||||||
|
STATIC UINT32 Hash(IN Span<CONST UINT8> data);
|
||||||
|
|
||||||
|
STATIC UINT32 CRC32(IN Span<CONST UINT8> data);
|
||||||
|
|
||||||
|
STATIC CompressionType DetectCompression(IN Span<CONST UINT8> data);
|
||||||
|
|
||||||
|
STATIC Expected<Vector<UINT8>, String> GZipInflate(IN Span<CONST UINT8> data);
|
||||||
|
STATIC Expected<Vector<UINT8>, String> GZipDeflate(IN Span<CONST UINT8> data);
|
||||||
|
|
||||||
|
STATIC Expected<Vector<UINT8>, String> ZlibInflate(IN Span<CONST UINT8> data);
|
||||||
|
STATIC Expected<Vector<UINT8>, String> ZlibDeflate(IN Span<CONST UINT8> data);
|
||||||
|
|
||||||
|
STATIC Expected<Vector<UINT8>, String> ZstdInflate(IN Span<CONST UINT8> data);
|
||||||
|
STATIC Expected<Vector<UINT8>, String> ZstdDeflate(IN Span<CONST UINT8> data);
|
||||||
|
};
|
||||||
|
} // namespace IACore
|
||||||
@ -1,357 +0,0 @@
|
|||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
namespace IACore
|
|
||||||
{
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
class File
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
// Modern mapping of flags to standard IO streams
|
|
||||||
enum class EOpenFlags : UINT32
|
|
||||||
{
|
|
||||||
Read = 1 << 0, // std::ios::in
|
|
||||||
Write = 1 << 1, // std::ios::out
|
|
||||||
Binary = 1 << 2, // std::ios::binary
|
|
||||||
Append = 1 << 3, // std::ios::app
|
|
||||||
Trunc = 1 << 4 // std::ios::trunc
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Static Helper API (The "One-Liners")
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Reads entire file into a binary vector
|
|
||||||
NO_DISCARD("Handle the error")
|
|
||||||
|
|
||||||
STATIC tl::expected<Vector<UINT8>, String> ReadToVector(CONST String &path)
|
|
||||||
{
|
|
||||||
// 1. Check File Existence & Size
|
|
||||||
std::error_code ec;
|
|
||||||
auto fileSize = fs::file_size(path, ec);
|
|
||||||
|
|
||||||
if (ec)
|
|
||||||
{
|
|
||||||
return tl::make_unexpected(String("File not found or inaccessible: ") + path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Open Stream
|
|
||||||
std::ifstream file(path, std::ios::binary);
|
|
||||||
if (!file.is_open())
|
|
||||||
{
|
|
||||||
return tl::make_unexpected(String("Failed to open file handle: ") + path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Read
|
|
||||||
Vector<UINT8> buffer;
|
|
||||||
buffer.resize(CAST(fileSize, SIZE_T));
|
|
||||||
file.read(REINTERPRET(buffer.data(), char *), fileSize);
|
|
||||||
if (file.fail())
|
|
||||||
return tl::make_unexpected(String("Read error: ") + path);
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads entire file into a String
|
|
||||||
NO_DISCARD("Handle the error")
|
|
||||||
|
|
||||||
STATIC tl::expected<String, String> ReadToString(CONST String &path)
|
|
||||||
{
|
|
||||||
// Reuse the binary logic to avoid code duplication, then cast
|
|
||||||
auto result = ReadToVector(path);
|
|
||||||
if (!result)
|
|
||||||
{
|
|
||||||
return tl::make_unexpected(result.error());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Efficient move into string (reinterpret_cast approach or move)
|
|
||||||
// Since Vector<UINT8> and String memory layout isn't guaranteed identical, we copy.
|
|
||||||
// (Though in many STL implementations you could theoretically steal the buffer, copying is safer)
|
|
||||||
String str(result->begin(), result->end());
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Path Utilities (Replaces manual parsing)
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Old: ExtractFilenameFromPath<true/false>
|
|
||||||
// New: true -> filename(), false -> stem()
|
|
||||||
template<BOOL includeExtension = true> STATIC String ExtractFilename(CONST String &pathStr)
|
|
||||||
{
|
|
||||||
fs::path p(pathStr);
|
|
||||||
if CONSTEXPR (includeExtension)
|
|
||||||
{
|
|
||||||
return p.filename().string(); // "sprite.png"
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return p.stem().string(); // "sprite"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old: ExtractDirectoryFromPath
|
|
||||||
STATIC String ExtractDirectory(CONST String &pathStr)
|
|
||||||
{
|
|
||||||
fs::path p(pathStr);
|
|
||||||
return p.parent_path().string(); // "assets/textures"
|
|
||||||
}
|
|
||||||
|
|
||||||
STATIC BOOL Exists(CONST String &pathStr)
|
|
||||||
{
|
|
||||||
std::error_code ec;
|
|
||||||
return fs::exists(pathStr, ec);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Instance API (For streaming/partial reads)
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
File() = default;
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
File(CONST String &path, EOpenFlags flags)
|
|
||||||
#else
|
|
||||||
File(CONST String &path, UINT32 flags)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
UNUSED(Open(path, flags));
|
|
||||||
}
|
|
||||||
|
|
||||||
~File()
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
tl::expected<void, String> Open(CONST String &path, EOpenFlags flags)
|
|
||||||
#else
|
|
||||||
tl::expected<void, String> Open(CONST String &path, UINT32 flags)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
Close(); // Ensure previous handle is closed
|
|
||||||
|
|
||||||
std::ios_base::openmode mode = static_cast<std::ios_base::openmode>(0);
|
|
||||||
if ((UINT32) flags & (UINT32) EOpenFlags::Read)
|
|
||||||
mode |= std::ios::in;
|
|
||||||
if ((UINT32) flags & (UINT32) EOpenFlags::Write)
|
|
||||||
mode |= std::ios::out;
|
|
||||||
if ((UINT32) flags & (UINT32) EOpenFlags::Binary)
|
|
||||||
mode |= std::ios::binary;
|
|
||||||
if ((UINT32) flags & (UINT32) EOpenFlags::Append)
|
|
||||||
mode |= std::ios::app;
|
|
||||||
if ((UINT32) flags & (UINT32) EOpenFlags::Trunc)
|
|
||||||
mode |= std::ios::trunc;
|
|
||||||
|
|
||||||
m_fs.open(path, mode);
|
|
||||||
|
|
||||||
if (!m_fs.is_open())
|
|
||||||
{
|
|
||||||
return tl::make_unexpected(String("Failed to open file: ") + path);
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
VOID Close()
|
|
||||||
{
|
|
||||||
if (m_fs.is_open())
|
|
||||||
{
|
|
||||||
m_fs.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOL IsOpen() CONST
|
|
||||||
{
|
|
||||||
return m_fs.is_open();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns number of bytes read
|
|
||||||
SIZE_T Read(PVOID buffer, SIZE_T size)
|
|
||||||
{
|
|
||||||
if B_UNLIKELY (!m_fs.is_open())
|
|
||||||
return 0;
|
|
||||||
m_fs.read(REINTERPRET(buffer, char *), size);
|
|
||||||
return static_cast<SIZE_T>(m_fs.gcount());
|
|
||||||
}
|
|
||||||
|
|
||||||
VOID Write(CONST PVOID buffer, SIZE_T size)
|
|
||||||
{
|
|
||||||
if B_UNLIKELY (!m_fs.is_open())
|
|
||||||
return;
|
|
||||||
m_fs.write(REINTERPRET(buffer, const char *), size);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::fstream *GetStreamHandle()
|
|
||||||
{
|
|
||||||
return &m_fs;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::fstream m_fs;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MMFile
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MMFile() = default;
|
|
||||||
|
|
||||||
// RAII - Automatically unmap on destruction
|
|
||||||
~MMFile()
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable copy (managing ownership of raw handles is messy)
|
|
||||||
MMFile(const MMFile &) = delete;
|
|
||||||
MMFile &operator=(const MMFile &) = delete;
|
|
||||||
|
|
||||||
// Open and Map
|
|
||||||
bool Map(const std::string &filepath)
|
|
||||||
{
|
|
||||||
Close(); // Cleanup any existing map
|
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS > 0
|
|
||||||
// 1. Open File
|
|
||||||
hFile_ = ::CreateFileA(filepath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
|
|
||||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
||||||
if (hFile_ == INVALID_HANDLE_VALUE)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// 2. Get File Size
|
|
||||||
LARGE_INTEGER size;
|
|
||||||
if (!::GetFileSizeEx(hFile_, &size))
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
size_ = static_cast<size_t>(size.QuadPart);
|
|
||||||
|
|
||||||
// 3. Create Mapping Object
|
|
||||||
hMapping_ = ::CreateFileMappingA(hFile_, nullptr, PAGE_READWRITE, 0, 0, nullptr);
|
|
||||||
if (!hMapping_)
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Map View
|
|
||||||
data_ = ::MapViewOfFile(hMapping_, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
||||||
if (!data_)
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else // LINUX / POSIX
|
|
||||||
// 1. Open File
|
|
||||||
fd_ = ::open(filepath.c_str(), O_RDWR);
|
|
||||||
if (fd_ == -1)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// 2. Get File Size
|
|
||||||
struct stat sb;
|
|
||||||
if (fstat(fd_, &sb) == -1)
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
size_ = static_cast<size_t>(sb.st_size);
|
|
||||||
|
|
||||||
// 3. mmap
|
|
||||||
// PROT_READ: Read only
|
|
||||||
// MAP_PRIVATE: Copy-on-write (safe if you accidentally modify, though we return const)
|
|
||||||
data_ = ::mmap(nullptr, size_, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd_, 0);
|
|
||||||
if (data_ == MAP_FAILED)
|
|
||||||
{
|
|
||||||
data_ = nullptr;
|
|
||||||
Close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Close()
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
if (data_)
|
|
||||||
{
|
|
||||||
::UnmapViewOfFile(data_);
|
|
||||||
data_ = nullptr;
|
|
||||||
}
|
|
||||||
if (hMapping_)
|
|
||||||
{
|
|
||||||
::CloseHandle(hMapping_);
|
|
||||||
hMapping_ = nullptr;
|
|
||||||
}
|
|
||||||
if (hFile_ != INVALID_HANDLE_VALUE)
|
|
||||||
{
|
|
||||||
::CloseHandle(hFile_);
|
|
||||||
hFile_ = INVALID_HANDLE_VALUE;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (data_)
|
|
||||||
{
|
|
||||||
::munmap(data_, size_);
|
|
||||||
data_ = nullptr;
|
|
||||||
}
|
|
||||||
if (fd_ != -1)
|
|
||||||
{
|
|
||||||
::close(fd_);
|
|
||||||
fd_ = -1;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
size_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accessors
|
|
||||||
const void *GetData() const
|
|
||||||
{
|
|
||||||
return data_;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t GetSize() const
|
|
||||||
{
|
|
||||||
return size_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsValid() const
|
|
||||||
{
|
|
||||||
return data_ != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void *data_ = nullptr;
|
|
||||||
size_t size_ = 0;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
HANDLE hFile_ = INVALID_HANDLE_VALUE;
|
|
||||||
HANDLE hMapping_ = nullptr;
|
|
||||||
#else
|
|
||||||
int fd_ = -1;
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
} // namespace IACore
|
|
||||||
50
Src/IACore/inc/IACore/FileOps.hpp
Normal file
50
Src/IACore/inc/IACore/FileOps.hpp
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <IACore/StreamReader.hpp>
|
||||||
|
#include <IACore/StreamWriter.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
class FileOps
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
STATIC FilePath NormalizeExecutablePath(IN CONST FilePath &path);
|
||||||
|
|
||||||
|
public:
|
||||||
|
STATIC VOID UnmapFile(IN PCUINT8 mappedPtr);
|
||||||
|
STATIC Expected<PCUINT8, String> MapFile(IN CONST FilePath &path, OUT SIZE_T &size);
|
||||||
|
|
||||||
|
// @param `isOwner` TRUE to allocate/truncate. FALSE to just open.
|
||||||
|
STATIC Expected<PUINT8, String> MapSharedMemory(IN CONST String &name, IN SIZE_T size, IN BOOL isOwner);
|
||||||
|
STATIC VOID UnlinkSharedMemory(IN CONST String &name);
|
||||||
|
|
||||||
|
STATIC Expected<StreamReader, String> StreamFromFile(IN CONST FilePath &path);
|
||||||
|
STATIC Expected<StreamWriter, String> StreamToFile(IN CONST FilePath &path, IN BOOL overwrite = false);
|
||||||
|
|
||||||
|
STATIC Expected<String, String> ReadTextFile(IN CONST FilePath &path);
|
||||||
|
STATIC Expected<Vector<UINT8>, String> ReadBinaryFile(IN CONST FilePath &path);
|
||||||
|
STATIC Expected<SIZE_T, String> WriteTextFile(IN CONST FilePath &path, IN CONST String &contents,
|
||||||
|
IN BOOL overwrite = false);
|
||||||
|
STATIC Expected<SIZE_T, String> WriteBinaryFile(IN CONST FilePath &path, IN Span<UINT8> contents,
|
||||||
|
IN BOOL overwrite = false);
|
||||||
|
|
||||||
|
private:
|
||||||
|
STATIC UnorderedMap<PCUINT8, Tuple<PVOID, PVOID, PVOID>> s_mappedFiles;
|
||||||
|
};
|
||||||
|
} // namespace IACore
|
||||||
196
Src/IACore/inc/IACore/HttpClient.hpp
Normal file
196
Src/IACore/inc/IACore/HttpClient.hpp
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <IACore/JSON.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
class HttpClient
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class EHeaderType
|
||||||
|
{
|
||||||
|
ACCEPT,
|
||||||
|
ACCEPT_CHARSET,
|
||||||
|
ACCEPT_ENCODING,
|
||||||
|
ACCEPT_LANGUAGE,
|
||||||
|
AUTHORIZATION,
|
||||||
|
CACHE_CONTROL,
|
||||||
|
CONNECTION,
|
||||||
|
CONTENT_LENGTH,
|
||||||
|
CONTENT_TYPE,
|
||||||
|
COOKIE,
|
||||||
|
DATE,
|
||||||
|
EXPECT,
|
||||||
|
HOST,
|
||||||
|
IF_MATCH,
|
||||||
|
IF_MODIFIED_SINCE,
|
||||||
|
IF_NONE_MATCH,
|
||||||
|
ORIGIN,
|
||||||
|
PRAGMA,
|
||||||
|
PROXY_AUTHORIZATION,
|
||||||
|
RANGE,
|
||||||
|
REFERER,
|
||||||
|
TE,
|
||||||
|
UPGRADE,
|
||||||
|
USER_AGENT,
|
||||||
|
VIA,
|
||||||
|
WARNING
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class EResponseCode : INT32
|
||||||
|
{
|
||||||
|
// 1xx Informational
|
||||||
|
CONTINUE = 100,
|
||||||
|
SWITCHING_PROTOCOLS = 101,
|
||||||
|
PROCESSING = 102,
|
||||||
|
EARLY_HINTS = 103,
|
||||||
|
|
||||||
|
// 2xx Success
|
||||||
|
OK = 200,
|
||||||
|
CREATED = 201,
|
||||||
|
ACCEPTED = 202,
|
||||||
|
NON_AUTHORITATIVE_INFORMATION = 203,
|
||||||
|
NO_CONTENT = 204,
|
||||||
|
RESET_CONTENT = 205,
|
||||||
|
PARTIAL_CONTENT = 206,
|
||||||
|
MULTI_STATUS = 207,
|
||||||
|
ALREADY_REPORTED = 208,
|
||||||
|
IM_USED = 226,
|
||||||
|
|
||||||
|
// 3xx Redirection
|
||||||
|
MULTIPLE_CHOICES = 300,
|
||||||
|
MOVED_PERMANENTLY = 301,
|
||||||
|
FOUND = 302,
|
||||||
|
SEE_OTHER = 303,
|
||||||
|
NOT_MODIFIED = 304,
|
||||||
|
USE_PROXY = 305,
|
||||||
|
TEMPORARY_REDIRECT = 307,
|
||||||
|
PERMANENT_REDIRECT = 308,
|
||||||
|
|
||||||
|
// 4xx Client Error
|
||||||
|
BAD_REQUEST = 400,
|
||||||
|
UNAUTHORIZED = 401,
|
||||||
|
PAYMENT_REQUIRED = 402,
|
||||||
|
FORBIDDEN = 403,
|
||||||
|
NOT_FOUND = 404,
|
||||||
|
METHOD_NOT_ALLOWED = 405,
|
||||||
|
NOT_ACCEPTABLE = 406,
|
||||||
|
PROXY_AUTHENTICATION_REQUIRED = 407,
|
||||||
|
REQUEST_TIMEOUT = 408,
|
||||||
|
CONFLICT = 409,
|
||||||
|
GONE = 410,
|
||||||
|
LENGTH_REQUIRED = 411,
|
||||||
|
PRECONDITION_FAILED = 412,
|
||||||
|
PAYLOAD_TOO_LARGE = 413,
|
||||||
|
URI_TOO_LONG = 414,
|
||||||
|
UNSUPPORTED_MEDIA_TYPE = 415,
|
||||||
|
RANGE_NOT_SATISFIABLE = 416,
|
||||||
|
EXPECTATION_FAILED = 417,
|
||||||
|
IM_A_TEAPOT = 418,
|
||||||
|
MISDIRECTED_REQUEST = 421,
|
||||||
|
UNPROCESSABLE_ENTITY = 422,
|
||||||
|
LOCKED = 423,
|
||||||
|
FAILED_DEPENDENCY = 424,
|
||||||
|
TOO_EARLY = 425,
|
||||||
|
UPGRADE_REQUIRED = 426,
|
||||||
|
PRECONDITION_REQUIRED = 428,
|
||||||
|
TOO_MANY_REQUESTS = 429,
|
||||||
|
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
|
||||||
|
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
|
||||||
|
|
||||||
|
// 5xx Server Error
|
||||||
|
INTERNAL_SERVER_ERROR = 500,
|
||||||
|
NOT_IMPLEMENTED = 501,
|
||||||
|
BAD_GATEWAY = 502,
|
||||||
|
SERVICE_UNAVAILABLE = 503,
|
||||||
|
GATEWAY_TIMEOUT = 504,
|
||||||
|
HTTP_VERSION_NOT_SUPPORTED = 505,
|
||||||
|
VARIANT_ALSO_NEGOTIATES = 506,
|
||||||
|
INSUFFICIENT_STORAGE = 507,
|
||||||
|
LOOP_DETECTED = 508,
|
||||||
|
NOT_EXTENDED = 510,
|
||||||
|
NETWORK_AUTHENTICATION_REQUIRED = 511
|
||||||
|
};
|
||||||
|
|
||||||
|
using Header = KeyValuePair<EHeaderType, String>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
HttpClient(IN CONST String &host);
|
||||||
|
~HttpClient();
|
||||||
|
|
||||||
|
public:
|
||||||
|
Expected<String, String> RawGet(IN CONST String &path, IN Span<CONST Header> headers,
|
||||||
|
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
|
||||||
|
Expected<String, String> RawPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST String &body,
|
||||||
|
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
|
||||||
|
|
||||||
|
template<typename _response_type>
|
||||||
|
Expected<_response_type, String> JsonGet(IN CONST String &path, IN Span<CONST Header> headers);
|
||||||
|
|
||||||
|
template<typename _payload_type, typename _response_type>
|
||||||
|
Expected<_response_type, String> JsonPost(IN CONST String &path, IN Span<CONST Header> headers,
|
||||||
|
IN CONST _payload_type &body);
|
||||||
|
|
||||||
|
public:
|
||||||
|
STATIC String UrlEncode(IN CONST String &value);
|
||||||
|
STATIC String UrlDecode(IN CONST String &value);
|
||||||
|
|
||||||
|
STATIC String HeaderTypeToString(IN EHeaderType type);
|
||||||
|
STATIC Header CreateHeader(IN EHeaderType key, IN CONST String &value);
|
||||||
|
|
||||||
|
STATIC BOOL IsSuccessResponseCode(IN EResponseCode code);
|
||||||
|
|
||||||
|
public:
|
||||||
|
EResponseCode LastResponseCode()
|
||||||
|
{
|
||||||
|
return m_lastResponseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
PVOID m_client{};
|
||||||
|
EResponseCode m_lastResponseCode;
|
||||||
|
|
||||||
|
private:
|
||||||
|
String PreprocessResponse(IN CONST String& response);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename _response_type>
|
||||||
|
Expected<_response_type, String> HttpClient::JsonGet(IN CONST String &path, IN Span<CONST Header> headers)
|
||||||
|
{
|
||||||
|
const auto rawResponse = RawGet(path, headers, "application/json");
|
||||||
|
if (!rawResponse)
|
||||||
|
return MakeUnexpected(rawResponse.error());
|
||||||
|
if (LastResponseCode() != EResponseCode::OK)
|
||||||
|
return MakeUnexpected(std::format("Server responded with code {}", (INT32) LastResponseCode()));
|
||||||
|
return JSON::ParseToStruct<_response_type>(*rawResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename _payload_type, typename _response_type>
|
||||||
|
Expected<_response_type, String> HttpClient::JsonPost(IN CONST String &path, IN Span<CONST Header> headers,
|
||||||
|
IN CONST _payload_type &body)
|
||||||
|
{
|
||||||
|
const auto encodedBody = IA_TRY(JSON::EncodeStruct(body));
|
||||||
|
const auto rawResponse = RawPost(path, headers, encodedBody, "application/json");
|
||||||
|
if (!rawResponse)
|
||||||
|
return MakeUnexpected(rawResponse.error());
|
||||||
|
if (LastResponseCode() != EResponseCode::OK)
|
||||||
|
return MakeUnexpected(std::format("Server responded with code {}", (INT32) LastResponseCode()));
|
||||||
|
return JSON::ParseToStruct<_response_type>(*rawResponse);
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
@ -22,6 +22,21 @@
|
|||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
{
|
{
|
||||||
|
// Must be called from main thread
|
||||||
|
VOID Initialize();
|
||||||
|
// Must be called from same thread as Initialize
|
||||||
|
VOID Terminate();
|
||||||
|
|
||||||
|
UINT64 GetUnixTime();
|
||||||
|
UINT64 GetTicksCount();
|
||||||
|
FLOAT64 GetSecondsCount();
|
||||||
|
|
||||||
|
FLOAT32 GetRandom();
|
||||||
|
UINT32 GetRandom(IN UINT32 seed);
|
||||||
|
INT64 GetRandom(IN INT64 min, IN INT64 max);
|
||||||
|
|
||||||
|
BOOL IsMainThread();
|
||||||
|
VOID Sleep(IN UINT64 milliseconds);
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
150
Src/IACore/inc/IACore/IPC.hpp
Normal file
150
Src/IACore/inc/IACore/IPC.hpp
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <IACore/ADT/RingBuffer.hpp>
|
||||||
|
#include <IACore/ProcessOps.hpp>
|
||||||
|
#include <IACore/SocketOps.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
using IPC_PacketHeader = RingBufferView::PacketHeader;
|
||||||
|
|
||||||
|
struct alignas(64) IPC_SharedMemoryLayout
|
||||||
|
{
|
||||||
|
// =========================================================
|
||||||
|
// SECTION 1: METADATA & HANDSHAKE
|
||||||
|
// =========================================================
|
||||||
|
struct Header
|
||||||
|
{
|
||||||
|
UINT32 Magic; // 0x49414950 ("IAIP")
|
||||||
|
UINT32 Version; // 1
|
||||||
|
UINT64 TotalSize; // Total size of SHM block
|
||||||
|
} Meta;
|
||||||
|
|
||||||
|
// Pad to ensure MONI starts on a fresh cache line (64 bytes)
|
||||||
|
UINT8 _pad0[64 - sizeof(Header)];
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// SECTION 2: RING BUFFER CONTROL BLOCKS
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
// RingBufferView::ControlBlock is already 64-byte aligned internally.
|
||||||
|
RingBufferView::ControlBlock MONI_Control;
|
||||||
|
RingBufferView::ControlBlock MINO_Control;
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// SECTION 3: DATA BUFFER OFFSETS
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
UINT64 MONI_DataOffset;
|
||||||
|
UINT64 MONI_DataSize;
|
||||||
|
|
||||||
|
UINT64 MINO_DataOffset;
|
||||||
|
UINT64 MINO_DataSize;
|
||||||
|
|
||||||
|
// Pad to ensure the actual Data Buffer starts on a fresh cache line
|
||||||
|
UINT8 _pad1[64 - (sizeof(UINT64) * 4)];
|
||||||
|
|
||||||
|
static constexpr size_t GetHeaderSize()
|
||||||
|
{
|
||||||
|
return sizeof(IPC_SharedMemoryLayout);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Static assert to ensure manual padding logic is correct
|
||||||
|
static_assert(sizeof(IPC_SharedMemoryLayout) % 64 == 0, "IPC Layout is not cache-line aligned!");
|
||||||
|
|
||||||
|
class IPC_Node
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~IPC_Node();
|
||||||
|
|
||||||
|
// When Manager spawns a node, `connectionString` is passed
|
||||||
|
// as the first command line argument
|
||||||
|
Expected<VOID, String> Connect(IN PCCHAR connectionString);
|
||||||
|
|
||||||
|
VOID Update();
|
||||||
|
|
||||||
|
VOID SendSignal(IN UINT8 signal);
|
||||||
|
VOID SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PURE_VIRTUAL(VOID OnSignal(IN UINT8 signal));
|
||||||
|
PURE_VIRTUAL(VOID OnPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload));
|
||||||
|
|
||||||
|
private:
|
||||||
|
String m_shmName;
|
||||||
|
PUINT8 m_sharedMemory{};
|
||||||
|
Vector<UINT8> m_recieveBuffer;
|
||||||
|
SocketHandle m_socket{INVALID_SOCKET};
|
||||||
|
|
||||||
|
UniquePtr<RingBufferView> MONI; // Manager Out, Node In
|
||||||
|
UniquePtr<RingBufferView> MINO; // Manager In, Node Out
|
||||||
|
};
|
||||||
|
|
||||||
|
class IPC_Manager
|
||||||
|
{
|
||||||
|
struct NodeSession
|
||||||
|
{
|
||||||
|
SteadyTimePoint CreationTime{};
|
||||||
|
SharedPtr<ProcessHandle> ProcessHandle;
|
||||||
|
|
||||||
|
String SharedMemName;
|
||||||
|
PUINT8 MappedPtr{};
|
||||||
|
|
||||||
|
SocketHandle ListenerSocket{INVALID_SOCKET};
|
||||||
|
SocketHandle DataSocket{INVALID_SOCKET};
|
||||||
|
|
||||||
|
UniquePtr<RingBufferView> MONI; // Manager Out, Node In
|
||||||
|
UniquePtr<RingBufferView> MINO; // Manager In, Node Out
|
||||||
|
|
||||||
|
BOOL IsReady{FALSE};
|
||||||
|
|
||||||
|
VOID SendSignal(IN UINT8 signal);
|
||||||
|
VOID SendPacket(IN UINT16 packetID, IN Span<CONST UINT8> payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
STATIC CONSTEXPR UINT32 DEFAULT_NODE_SHARED_MEMORY_SIZE = SIZE_MB(4);
|
||||||
|
|
||||||
|
public:
|
||||||
|
IPC_Manager();
|
||||||
|
virtual ~IPC_Manager();
|
||||||
|
|
||||||
|
VOID Update();
|
||||||
|
|
||||||
|
Expected<NativeProcessID, String> SpawnNode(IN CONST FilePath &executablePath,
|
||||||
|
IN UINT32 sharedMemorySize = DEFAULT_NODE_SHARED_MEMORY_SIZE);
|
||||||
|
BOOL WaitTillNodeIsOnline(IN NativeProcessID node);
|
||||||
|
|
||||||
|
VOID ShutdownNode(IN NativeProcessID node);
|
||||||
|
|
||||||
|
VOID SendSignal(IN NativeProcessID node, IN UINT8 signal);
|
||||||
|
VOID SendPacket(IN NativeProcessID node, IN UINT16 packetID, IN Span<CONST UINT8> payload);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PURE_VIRTUAL(VOID OnSignal(IN NativeProcessID node, IN UINT8 signal));
|
||||||
|
PURE_VIRTUAL(VOID OnPacket(IN NativeProcessID node, IN UINT16 packetID, IN Span<CONST UINT8> payload));
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vector<UINT8> m_recieveBuffer;
|
||||||
|
Vector<UniquePtr<NodeSession>> m_activeSessions;
|
||||||
|
Vector<UniquePtr<NodeSession>> m_pendingSessions;
|
||||||
|
UnorderedMap<NativeProcessID, NodeSession *> m_activeSessionMap;
|
||||||
|
};
|
||||||
|
} // namespace IACore
|
||||||
58
Src/IACore/inc/IACore/JSON.hpp
Normal file
58
Src/IACore/inc/IACore/JSON.hpp
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
|
#include <simdjson.h>
|
||||||
|
#include <glaze/glaze.hpp>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
class JSON
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
STATIC CONSTEXPR AUTO GLAZE_JSON_OPTS = glz::opts{.error_on_unknown_keys = false};
|
||||||
|
|
||||||
|
public:
|
||||||
|
STATIC Expected<nlohmann::json, String> Parse(IN CONST String &json);
|
||||||
|
STATIC Expected<Pair<simdjson::dom::parser, simdjson::dom::object>, String> ParseReadOnly(IN CONST String &json);
|
||||||
|
template<typename _object_type> STATIC Expected<_object_type, String> ParseToStruct(IN CONST String &json);
|
||||||
|
|
||||||
|
STATIC String Encode(IN nlohmann::json data);
|
||||||
|
template<typename _object_type> STATIC Expected<String, String> EncodeStruct(IN CONST _object_type &data);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename _object_type> Expected<_object_type, String> JSON::ParseToStruct(IN CONST String &json)
|
||||||
|
{
|
||||||
|
_object_type result{};
|
||||||
|
const auto parseError = glz::read<GLAZE_JSON_OPTS>(result, json);
|
||||||
|
if (parseError)
|
||||||
|
return MakeUnexpected(std::format("JSON Error: {}", glz::format_error(parseError, json)));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename _object_type> Expected<String, String> JSON::EncodeStruct(IN CONST _object_type &data)
|
||||||
|
{
|
||||||
|
String result;
|
||||||
|
const auto encodeError = glz::write_json(data, result);
|
||||||
|
if (encodeError)
|
||||||
|
return MakeUnexpected(std::format("JSON Error: {}", glz::format_error(encodeError)));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
@ -20,8 +20,6 @@
|
|||||||
|
|
||||||
namespace IACore
|
namespace IACore
|
||||||
{
|
{
|
||||||
class File;
|
|
||||||
|
|
||||||
class Logger
|
class Logger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -34,9 +32,6 @@ namespace IACore
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
STATIC VOID Initialize();
|
|
||||||
STATIC VOID Terminate();
|
|
||||||
|
|
||||||
STATIC BOOL EnableLoggingToDisk(IN PCCHAR filePath);
|
STATIC BOOL EnableLoggingToDisk(IN PCCHAR filePath);
|
||||||
STATIC VOID SetLogLevel(IN ELogLevel logLevel);
|
STATIC VOID SetLogLevel(IN ELogLevel logLevel);
|
||||||
|
|
||||||
@ -60,8 +55,30 @@ namespace IACore
|
|||||||
LogError(std::vformat(fmt.get(), std::make_format_args(args...)));
|
LogError(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
STATIC VOID FlushLogs();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#if IA_ENABLE_LOGGING > 0
|
#if IA_DISABLE_LOGGING > 0
|
||||||
|
STATIC VOID LogStatus(IN String &&msg)
|
||||||
|
{
|
||||||
|
UNUSED(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC VOID LogInfo(IN String &&msg)
|
||||||
|
{
|
||||||
|
UNUSED(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC VOID LogWarn(IN String &&msg)
|
||||||
|
{
|
||||||
|
UNUSED(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC VOID LogError(IN String &&msg)
|
||||||
|
{
|
||||||
|
UNUSED(msg);
|
||||||
|
}
|
||||||
|
#else
|
||||||
STATIC VOID LogStatus(IN String &&msg)
|
STATIC VOID LogStatus(IN String &&msg)
|
||||||
{
|
{
|
||||||
if (s_logLevel <= ELogLevel::VERBOSE)
|
if (s_logLevel <= ELogLevel::VERBOSE)
|
||||||
@ -85,33 +102,18 @@ namespace IACore
|
|||||||
if (s_logLevel <= ELogLevel::ERROR)
|
if (s_logLevel <= ELogLevel::ERROR)
|
||||||
LogInternal(__CC_RED, "ERROR", IA_MOVE(msg));
|
LogInternal(__CC_RED, "ERROR", IA_MOVE(msg));
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
STATIC VOID LogStatus(IN String &&msg)
|
|
||||||
{
|
|
||||||
UNUSED(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
STATIC VOID LogInfo(IN String &&msg)
|
|
||||||
{
|
|
||||||
UNUSED(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
STATIC VOID LogWarn(IN String &&msg)
|
|
||||||
{
|
|
||||||
UNUSED(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
STATIC VOID LogError(IN String &&msg)
|
|
||||||
{
|
|
||||||
UNUSED(msg);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
STATIC VOID LogInternal(IN PCCHAR prefix, IN PCCHAR tag, IN String &&msg);
|
STATIC VOID LogInternal(IN PCCHAR prefix, IN PCCHAR tag, IN String &&msg);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
STATIC ELogLevel s_logLevel;
|
STATIC ELogLevel s_logLevel;
|
||||||
STATIC HRTimePoint s_startTime;
|
STATIC std::ofstream s_logFile;
|
||||||
STATIC IACore::File *s_logFile;
|
|
||||||
|
STATIC VOID Initialize();
|
||||||
|
STATIC VOID Terminate();
|
||||||
|
|
||||||
|
friend VOID Initialize();
|
||||||
|
friend VOID Terminate();
|
||||||
};
|
};
|
||||||
}
|
} // namespace IACore
|
||||||
@ -27,18 +27,25 @@
|
|||||||
# include <new>
|
# include <new>
|
||||||
# include <span>
|
# include <span>
|
||||||
# include <atomic>
|
# include <atomic>
|
||||||
|
# include <mutex>
|
||||||
# include <thread>
|
# include <thread>
|
||||||
# include <limits>
|
# include <limits>
|
||||||
# include <cstring>
|
# include <cstring>
|
||||||
# include <cstddef>
|
# include <cstddef>
|
||||||
# include <chrono>
|
# include <chrono>
|
||||||
|
# include <iomanip>
|
||||||
|
# include <fstream>
|
||||||
# include <iostream>
|
# include <iostream>
|
||||||
# include <concepts>
|
# include <concepts>
|
||||||
|
# include <filesystem>
|
||||||
# include <functional>
|
# include <functional>
|
||||||
# include <type_traits>
|
# include <type_traits>
|
||||||
# include <initializer_list>
|
# include <initializer_list>
|
||||||
|
# include <condition_variable>
|
||||||
|
|
||||||
|
# include <tuple>
|
||||||
# include <array>
|
# include <array>
|
||||||
|
# include <deque>
|
||||||
# include <string>
|
# include <string>
|
||||||
# include <vector>
|
# include <vector>
|
||||||
# include <format>
|
# include <format>
|
||||||
@ -66,6 +73,9 @@
|
|||||||
# define __DEBUG_MODE__
|
# define __DEBUG_MODE__
|
||||||
# define __BUILD_MODE_NAME "debug"
|
# define __BUILD_MODE_NAME "debug"
|
||||||
# define DEBUG_ONLY(v) v
|
# define DEBUG_ONLY(v) v
|
||||||
|
# ifndef _DEBUG
|
||||||
|
# define _DEBUG
|
||||||
|
# endif
|
||||||
#else
|
#else
|
||||||
# define __RELEASE_MODE__
|
# define __RELEASE_MODE__
|
||||||
# define __BUILD_MODE_NAME "release"
|
# define __BUILD_MODE_NAME "release"
|
||||||
@ -260,6 +270,15 @@
|
|||||||
|
|
||||||
#define IA_UNREACHABLE(msg) IA_RELEASE_ASSERT_MSG(FALSE, "Unreachable code: " msg)
|
#define IA_UNREACHABLE(msg) IA_RELEASE_ASSERT_MSG(FALSE, "Unreachable code: " msg)
|
||||||
|
|
||||||
|
#define IA_TRY_PURE(expr) \
|
||||||
|
{ \
|
||||||
|
auto _ia_res = (expr); \
|
||||||
|
if (!_ia_res) \
|
||||||
|
{ \
|
||||||
|
return tl::make_unexpected(std::move(_ia_res.error())); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
#define IA_TRY(expr) \
|
#define IA_TRY(expr) \
|
||||||
__extension__({ \
|
__extension__({ \
|
||||||
auto _ia_res = (expr); \
|
auto _ia_res = (expr); \
|
||||||
@ -274,6 +293,10 @@
|
|||||||
#define IA_CONCAT(x, y) IA_CONCAT_IMPL(x, y)
|
#define IA_CONCAT(x, y) IA_CONCAT_IMPL(x, y)
|
||||||
#define IA_UNIQUE_NAME(prefix) IA_CONCAT(prefix, __LINE__)
|
#define IA_UNIQUE_NAME(prefix) IA_CONCAT(prefix, __LINE__)
|
||||||
|
|
||||||
|
#define SIZE_KB(v) (v * 1024)
|
||||||
|
#define SIZE_MB(v) (v * 1024 * 1024)
|
||||||
|
#define SIZE_GB(v) (v * 1024 * 1024 * 1024)
|
||||||
|
|
||||||
#define ENSURE_BINARY_COMPATIBILITY(A, B) \
|
#define ENSURE_BINARY_COMPATIBILITY(A, B) \
|
||||||
static_assert(sizeof(A) == sizeof(B), \
|
static_assert(sizeof(A) == sizeof(B), \
|
||||||
#A ", " #B " size mismatch! Do not add virtual functions or new member variables.");
|
#A ", " #B " size mismatch! Do not add virtual functions or new member variables.");
|
||||||
@ -501,6 +524,7 @@ STATIC CONST FLOAT64 FLOAT64_EPSILON = DBL_EPSILON;
|
|||||||
# include <sys/stat.h>
|
# include <sys/stat.h>
|
||||||
# include <fcntl.h>
|
# include <fcntl.h>
|
||||||
# include <spawn.h>
|
# include <spawn.h>
|
||||||
|
# include <signal.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if IA_CHECK(IA_PLATFORM_WIN64) || IA_CHECK(IA_PLATFORM_UNIX)
|
#if IA_CHECK(IA_PLATFORM_WIN64) || IA_CHECK(IA_PLATFORM_UNIX)
|
||||||
@ -524,6 +548,13 @@ template<typename _key_type> using UnorderedSet = ankerl::unordered_dense::set<_
|
|||||||
template<typename _value_type> using Span = std::span<_value_type>;
|
template<typename _value_type> using Span = std::span<_value_type>;
|
||||||
template<typename _key_type, typename _value_type>
|
template<typename _key_type, typename _value_type>
|
||||||
using UnorderedMap = ankerl::unordered_dense::map<_key_type, _value_type>;
|
using UnorderedMap = ankerl::unordered_dense::map<_key_type, _value_type>;
|
||||||
|
template<typename _value_type> using Atomic = std::atomic<_value_type>;
|
||||||
|
template<typename _value_type> using SharedPtr = std::shared_ptr<_value_type>;
|
||||||
|
template<typename _value_type> using UniquePtr = std::unique_ptr<_value_type>;
|
||||||
|
template<typename _value_type> using Deque = std::deque<_value_type>;
|
||||||
|
template<typename _type_a, typename _type_b> using Pair = std::pair<_type_a, _type_b>;
|
||||||
|
template<typename... types> using Tuple = std::tuple<types...>;
|
||||||
|
template<typename _key_type, typename _value_type> using KeyValuePair = std::pair<_key_type, _value_type>;
|
||||||
|
|
||||||
template<typename _expected_type, typename _unexpected_type>
|
template<typename _expected_type, typename _unexpected_type>
|
||||||
using Expected = tl::expected<_expected_type, _unexpected_type>;
|
using Expected = tl::expected<_expected_type, _unexpected_type>;
|
||||||
@ -533,11 +564,20 @@ using String = std::string;
|
|||||||
using StringView = std::string_view;
|
using StringView = std::string_view;
|
||||||
using StringStream = std::stringstream;
|
using StringStream = std::stringstream;
|
||||||
|
|
||||||
using HRClock = std::chrono::high_resolution_clock;
|
using SteadyClock = std::chrono::steady_clock;
|
||||||
using HRTimePoint = std::chrono::time_point<HRClock>;
|
using SteadyTimePoint = std::chrono::time_point<SteadyClock>;
|
||||||
|
using HighResClock = std::chrono::high_resolution_clock;
|
||||||
|
using HighResTimePoint = std::chrono::time_point<HighResClock>;
|
||||||
|
|
||||||
using Mutex = std::mutex;
|
using Mutex = std::mutex;
|
||||||
using LockGuard = std::lock_guard<Mutex>;
|
using StopToken = std::stop_token;
|
||||||
|
using ScopedLock = std::scoped_lock<Mutex>;
|
||||||
|
using UniqueLock = std::unique_lock<Mutex>;
|
||||||
|
using JoiningThread = std::jthread;
|
||||||
|
using ConditionVariable = std::condition_variable;
|
||||||
|
|
||||||
|
namespace FileSystem = std::filesystem;
|
||||||
|
using FilePath = FileSystem::path;
|
||||||
|
|
||||||
template<typename... Args> using FormatterString = std::format_string<Args...>;
|
template<typename... Args> using FormatterString = std::format_string<Args...>;
|
||||||
|
|
||||||
|
|||||||
@ -1,292 +0,0 @@
|
|||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <IACore/PCH.hpp>
|
|
||||||
|
|
||||||
namespace IACore
|
|
||||||
{
|
|
||||||
|
|
||||||
class Process
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Static One-Shot Execution
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Returns Exit Code or Error String
|
|
||||||
// callback receives distinct lines of output (stdout + stderr merged)
|
|
||||||
STATIC tl::expected<INT32, String> Run(CONST String &cmd, CONST String &args,
|
|
||||||
Function<VOID(StringView)> onOutputLine)
|
|
||||||
{
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
|
||||||
return RunWindows(cmd, args, onOutputLine);
|
|
||||||
#else
|
|
||||||
return RunPosix(cmd, args, onOutputLine);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Async Execution
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Returns a jthread.
|
|
||||||
// - Store it if you want to wait for it later (join).
|
|
||||||
// - If you destroy the returned jthread immediately, it will BLOCK (join) by design.
|
|
||||||
// - If you want "fire and forget", call .detach() on the returned thread.
|
|
||||||
STATIC std::jthread RunAsync(String cmd, String args, Function<VOID(StringView)> onOutputLine,
|
|
||||||
Function<VOID(tl::expected<INT32, String>)> onComplete)
|
|
||||||
{
|
|
||||||
// We capture arguments by VALUE (=) to ensure the thread owns its own copies of the strings.
|
|
||||||
return std::jthread([=, cmd = std::move(cmd), args = std::move(args)]() mutable {
|
|
||||||
tl::expected<INT32, String> result = tl::make_unexpected("Not started");
|
|
||||||
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
|
||||||
result = RunWindows(cmd, args, onOutputLine);
|
|
||||||
#else
|
|
||||||
result = RunPosix(cmd, args, onOutputLine);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Report final result to the callback
|
|
||||||
if (onComplete)
|
|
||||||
{
|
|
||||||
onComplete(std::move(result));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Output Buffering Helper
|
|
||||||
// Splits raw chunks into lines, preserving partial lines across chunks
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
struct LineBuffer
|
|
||||||
{
|
|
||||||
String accumulator;
|
|
||||||
Function<VOID(StringView)> &callback;
|
|
||||||
|
|
||||||
VOID Append(const char *data, SIZE_T size)
|
|
||||||
{
|
|
||||||
SIZE_T start = 0;
|
|
||||||
for (SIZE_T i = 0; i < size; ++i)
|
|
||||||
{
|
|
||||||
if (data[i] == '\n' || data[i] == '\r')
|
|
||||||
{
|
|
||||||
// Flush accumulator + current chunk
|
|
||||||
if (!accumulator.empty())
|
|
||||||
{
|
|
||||||
accumulator.append(data + start, i - start);
|
|
||||||
if (!accumulator.empty())
|
|
||||||
callback(accumulator);
|
|
||||||
accumulator.clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Zero copy optimization for pure lines in one chunk
|
|
||||||
if (i > start)
|
|
||||||
callback(StringView(data + start, i - start));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip \r\n sequence if needed, or just start next
|
|
||||||
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n')
|
|
||||||
i++;
|
|
||||||
start = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Save remaining partial line
|
|
||||||
if (start < size)
|
|
||||||
{
|
|
||||||
accumulator.append(data + start, size - start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VOID Flush()
|
|
||||||
{
|
|
||||||
if (!accumulator.empty())
|
|
||||||
{
|
|
||||||
callback(accumulator);
|
|
||||||
accumulator.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Windows Implementation
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
#if IA_PLATFORM_WINDOWS
|
|
||||||
STATIC tl::expected<INT32, String> RunWindows(CONST String &cmd, CONST String &args,
|
|
||||||
Function<VOID(StringView)> cb)
|
|
||||||
{
|
|
||||||
SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; // Allow inheritance
|
|
||||||
HANDLE hRead = NULL, hWrite = NULL;
|
|
||||||
|
|
||||||
if (!CreatePipe(&hRead, &hWrite, &saAttr, 0))
|
|
||||||
return tl::make_unexpected("Failed to create pipe");
|
|
||||||
|
|
||||||
// Ensure the read handle to the pipe for STDOUT is NOT inherited
|
|
||||||
if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0))
|
|
||||||
return tl::make_unexpected("Failed to secure pipe handles");
|
|
||||||
|
|
||||||
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
|
|
||||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
|
||||||
si.hStdOutput = hWrite;
|
|
||||||
si.hStdError = hWrite; // Merge stderr
|
|
||||||
si.hStdInput = NULL; // No input
|
|
||||||
|
|
||||||
PROCESS_INFORMATION pi = {0};
|
|
||||||
|
|
||||||
// Windows command line needs to be mutable and concatenated
|
|
||||||
String commandLine = BuildString("\"", cmd, "\" ", args);
|
|
||||||
|
|
||||||
BOOL success = CreateProcessA(NULL, commandLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
|
|
||||||
|
|
||||||
// Important: Close write end in parent, otherwise ReadFile never returns EOF!
|
|
||||||
CloseHandle(hWrite);
|
|
||||||
|
|
||||||
if (!success)
|
|
||||||
{
|
|
||||||
CloseHandle(hRead);
|
|
||||||
return tl::make_unexpected(String("CreateProcess failed: ") + std::to_string(GetLastError()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read Loop
|
|
||||||
LineBuffer lineBuf{"", cb};
|
|
||||||
DWORD bytesRead;
|
|
||||||
CHAR buffer[4096];
|
|
||||||
|
|
||||||
while (ReadFile(hRead, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead != 0)
|
|
||||||
{
|
|
||||||
lineBuf.Append(buffer, bytesRead);
|
|
||||||
}
|
|
||||||
lineBuf.Flush();
|
|
||||||
|
|
||||||
// NOW we wait for exit code
|
|
||||||
DWORD exitCode = 0;
|
|
||||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
|
||||||
GetExitCodeProcess(pi.hProcess, &exitCode);
|
|
||||||
|
|
||||||
CloseHandle(pi.hProcess);
|
|
||||||
CloseHandle(pi.hThread);
|
|
||||||
CloseHandle(hRead);
|
|
||||||
|
|
||||||
return static_cast<INT32>(exitCode);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// POSIX (Linux/Mac) Implementation
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
#if IA_PLATFORM_UNIX
|
|
||||||
STATIC tl::expected<INT32, String> RunPosix(CONST String &cmd, CONST String &args,
|
|
||||||
Function<VOID(StringView)> cb)
|
|
||||||
{
|
|
||||||
int pipefd[2];
|
|
||||||
if (pipe(pipefd) == -1)
|
|
||||||
return tl::make_unexpected("Failed to create pipe");
|
|
||||||
|
|
||||||
pid_t pid = fork();
|
|
||||||
|
|
||||||
if (pid == -1)
|
|
||||||
{
|
|
||||||
return tl::make_unexpected("Failed to fork process");
|
|
||||||
}
|
|
||||||
else if (pid == 0)
|
|
||||||
{
|
|
||||||
// --- Child Process ---
|
|
||||||
close(pipefd[0]);
|
|
||||||
|
|
||||||
dup2(pipefd[1], STDOUT_FILENO);
|
|
||||||
dup2(pipefd[1], STDERR_FILENO);
|
|
||||||
close(pipefd[1]);
|
|
||||||
|
|
||||||
// --- ARGUMENT PARSING START ---
|
|
||||||
std::vector<std::string> argStorage; // To keep strings alive
|
|
||||||
std::vector<char *> argv;
|
|
||||||
|
|
||||||
std::string cmdStr = cmd;
|
|
||||||
argv.push_back(cmdStr.data());
|
|
||||||
|
|
||||||
// Manual Quote-Aware Splitter
|
|
||||||
std::string currentToken;
|
|
||||||
bool inQuotes = false;
|
|
||||||
|
|
||||||
for (char c : args)
|
|
||||||
{
|
|
||||||
if (c == '\"')
|
|
||||||
{
|
|
||||||
inQuotes = !inQuotes;
|
|
||||||
// Determine if you want to keep the quotes or strip them.
|
|
||||||
// Usually for execvp, you strip them so the shell receives the raw content.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == ' ' && !inQuotes)
|
|
||||||
{
|
|
||||||
if (!currentToken.empty())
|
|
||||||
{
|
|
||||||
argStorage.push_back(currentToken);
|
|
||||||
currentToken.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentToken += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!currentToken.empty())
|
|
||||||
{
|
|
||||||
argStorage.push_back(currentToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build char* array from the std::string storage
|
|
||||||
for (auto &s : argStorage)
|
|
||||||
{
|
|
||||||
argv.push_back(s.data());
|
|
||||||
}
|
|
||||||
argv.push_back(nullptr);
|
|
||||||
// --- ARGUMENT PARSING END ---
|
|
||||||
|
|
||||||
execvp(argv[0], argv.data());
|
|
||||||
_exit(127);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// --- Parent Process ---
|
|
||||||
close(pipefd[1]);
|
|
||||||
|
|
||||||
LineBuffer lineBuf{"", cb};
|
|
||||||
char buffer[4096];
|
|
||||||
ssize_t count;
|
|
||||||
|
|
||||||
while ((count = read(pipefd[0], buffer, sizeof(buffer))) > 0)
|
|
||||||
{
|
|
||||||
lineBuf.Append(buffer, count);
|
|
||||||
}
|
|
||||||
lineBuf.Flush();
|
|
||||||
close(pipefd[0]);
|
|
||||||
|
|
||||||
int status;
|
|
||||||
waitpid(pid, &status, 0);
|
|
||||||
|
|
||||||
if (WIFEXITED(status))
|
|
||||||
return WEXITSTATUS(status);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
} // namespace IACore
|
|
||||||
68
Src/IACore/inc/IACore/ProcessOps.hpp
Normal file
68
Src/IACore/inc/IACore/ProcessOps.hpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
using NativeProcessID = DWORD;
|
||||||
|
#elif IA_PLATFORM_UNIX
|
||||||
|
using NativeProcessID = pid_t;
|
||||||
|
#else
|
||||||
|
# error "This platform does not support IACore ProcessOps"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
struct ProcessHandle
|
||||||
|
{
|
||||||
|
Atomic<NativeProcessID> ID{0};
|
||||||
|
Atomic<BOOL> IsRunning{false};
|
||||||
|
|
||||||
|
BOOL IsActive() CONST
|
||||||
|
{
|
||||||
|
return IsRunning && ID != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
JoiningThread ThreadHandle;
|
||||||
|
|
||||||
|
friend class ProcessOps;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProcessOps
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
STATIC NativeProcessID GetCurrentProcessID();
|
||||||
|
|
||||||
|
STATIC Expected<INT32, String> SpawnProcessSync(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(IN StringView line)> onOutputLineCallback);
|
||||||
|
STATIC SharedPtr<ProcessHandle> SpawnProcessAsync(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(IN StringView line)> onOutputLineCallback,
|
||||||
|
IN Function<VOID(Expected<INT32, String>)> onFinishCallback);
|
||||||
|
|
||||||
|
STATIC VOID TerminateProcess(IN CONST SharedPtr<ProcessHandle> &handle);
|
||||||
|
|
||||||
|
private:
|
||||||
|
STATIC Expected<INT32, String> SpawnProcessWindows(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(StringView)> onOutputLineCallback,
|
||||||
|
OUT Atomic<NativeProcessID> &id);
|
||||||
|
STATIC Expected<INT32, String> SpawnProcessPosix(IN CONST String &command, IN CONST String &args,
|
||||||
|
IN Function<VOID(StringView)> onOutputLineCallback,
|
||||||
|
OUT Atomic<NativeProcessID> &id);
|
||||||
|
};
|
||||||
|
} // namespace IACore
|
||||||
@ -21,19 +21,24 @@
|
|||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
|
|
||||||
# include <winsock2.h>
|
# include <winsock2.h>
|
||||||
|
# include <ws2tcpip.h>
|
||||||
|
# include <afunix.h>
|
||||||
# pragma comment(lib, "ws2_32.lib")
|
# pragma comment(lib, "ws2_32.lib")
|
||||||
# define CLOSE_SOCKET(s) closesocket(s)
|
# define CLOSE_SOCKET(s) closesocket(s)
|
||||||
# define IS_VALID_SOCKET(s) (s != INVALID_SOCKET)
|
# define IS_VALID_SOCKET(s) (s != INVALID_SOCKET)
|
||||||
|
# define UNLINK_FILE(p) DeleteFileA(p)
|
||||||
using SocketHandle = SOCKET;
|
using SocketHandle = SOCKET;
|
||||||
|
|
||||||
#elif IA_PLATFORM_UNIX
|
#elif IA_PLATFORM_UNIX
|
||||||
|
|
||||||
|
# include <sys/un.h>
|
||||||
# include <sys/types.h>
|
# include <sys/types.h>
|
||||||
# include <sys/socket.h>
|
# include <sys/socket.h>
|
||||||
# include <netinet/in.h>
|
# include <netinet/in.h>
|
||||||
# define CLOSE_SOCKET(s) close(s)
|
# define CLOSE_SOCKET(s) close(s)
|
||||||
# define IS_VALID_SOCKET(s) (s >= 0)
|
# define IS_VALID_SOCKET(s) (s >= 0)
|
||||||
# define INVALID_SOCKET -1
|
# define INVALID_SOCKET -1
|
||||||
|
# define UNLINK_FILE(p) unlink(p)
|
||||||
using SocketHandle = int;
|
using SocketHandle = int;
|
||||||
|
|
||||||
#else
|
#else
|
||||||
@ -47,16 +52,26 @@ namespace IACore
|
|||||||
class SocketOps
|
class SocketOps
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
// SocketOps correctly handles multiple calls to Initialize and Terminate. Make sure
|
||||||
|
// every Initialize call is paired with a corresponding Terminate call
|
||||||
STATIC VOID Initialize()
|
STATIC VOID Initialize()
|
||||||
{
|
{
|
||||||
|
s_initCount++;
|
||||||
|
if (s_initCount > 1)
|
||||||
|
return;
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
WSADATA wsaData;
|
WSADATA wsaData;
|
||||||
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SocketOps correctly handles multiple calls to Initialize and Terminate. Make sure
|
||||||
|
// every Initialize call is paired with a corresponding Terminate call
|
||||||
STATIC VOID Terminate()
|
STATIC VOID Terminate()
|
||||||
{
|
{
|
||||||
|
s_initCount--;
|
||||||
|
if (s_initCount > 0)
|
||||||
|
return;
|
||||||
#if IA_PLATFORM_WINDOWS
|
#if IA_PLATFORM_WINDOWS
|
||||||
WSACleanup();
|
WSACleanup();
|
||||||
#endif
|
#endif
|
||||||
@ -72,25 +87,19 @@ namespace IACore
|
|||||||
return IsPortAvailable(port, SOCK_DGRAM);
|
return IsPortAvailable(port, SOCK_DGRAM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
STATIC VOID Close(IN SocketHandle sock);
|
||||||
|
|
||||||
|
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:
|
private:
|
||||||
STATIC BOOL IsPortAvailable(IN UINT16 port, IN INT32 type)
|
STATIC BOOL IsPortAvailable(IN UINT16 port, IN INT32 type);
|
||||||
{
|
|
||||||
SocketHandle sock = socket(AF_INET, type, IPPROTO_UDP);
|
|
||||||
if (!IS_VALID_SOCKET(sock))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
sockaddr_in addr{};
|
private:
|
||||||
addr.sin_family = AF_INET;
|
STATIC INT32 s_initCount;
|
||||||
addr.sin_port = htons(port);
|
|
||||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
||||||
|
|
||||||
bool isFree = false;
|
|
||||||
if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == 0)
|
|
||||||
isFree = true;
|
|
||||||
|
|
||||||
CLOSE_SOCKET(sock);
|
|
||||||
|
|
||||||
return isFree;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
} // namespace IACore
|
} // namespace IACore
|
||||||
104
Src/IACore/inc/IACore/StreamReader.hpp
Normal file
104
Src/IACore/inc/IACore/StreamReader.hpp
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
class StreamReader
|
||||||
|
{
|
||||||
|
enum class EStorageType
|
||||||
|
{
|
||||||
|
NON_OWNING,
|
||||||
|
OWNING_MMAP,
|
||||||
|
OWNING_VECTOR,
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
INLINE Expected<VOID, String> Read(IN PVOID buffer, IN SIZE_T size);
|
||||||
|
template<typename T> NO_DISCARD("Check for EOF") Expected<T, String> Read();
|
||||||
|
|
||||||
|
VOID Skip(SIZE_T amount)
|
||||||
|
{
|
||||||
|
m_cursor = std::min(m_cursor + amount, m_dataSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID Seek(SIZE_T pos)
|
||||||
|
{
|
||||||
|
m_cursor = (pos > m_dataSize) ? m_dataSize : pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
SIZE_T Cursor() CONST
|
||||||
|
{
|
||||||
|
return m_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
SIZE_T Size() CONST
|
||||||
|
{
|
||||||
|
return m_dataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
SIZE_T Remaining() CONST
|
||||||
|
{
|
||||||
|
return m_dataSize - m_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL IsEOF() CONST
|
||||||
|
{
|
||||||
|
return m_cursor >= m_dataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
StreamReader(IN CONST FilePath &path);
|
||||||
|
explicit StreamReader(IN Vector<UINT8> &&data);
|
||||||
|
explicit StreamReader(IN Span<CONST UINT8> data);
|
||||||
|
~StreamReader();
|
||||||
|
|
||||||
|
private:
|
||||||
|
PCUINT8 m_data{};
|
||||||
|
SIZE_T m_cursor{};
|
||||||
|
SIZE_T m_dataSize{};
|
||||||
|
Vector<UINT8> m_owningVector;
|
||||||
|
CONST EStorageType m_storageType;
|
||||||
|
};
|
||||||
|
|
||||||
|
Expected<VOID, String> StreamReader::Read(IN PVOID buffer, IN SIZE_T size)
|
||||||
|
{
|
||||||
|
if B_UNLIKELY ((m_cursor + size > m_dataSize))
|
||||||
|
return MakeUnexpected(String("Unexpected EOF while reading"));
|
||||||
|
|
||||||
|
std::memcpy(buffer, &m_data[m_cursor], size);
|
||||||
|
m_cursor += size;
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T> NO_DISCARD("Check for EOF") Expected<T, String> StreamReader::Read()
|
||||||
|
{
|
||||||
|
constexpr SIZE_T size = sizeof(T);
|
||||||
|
|
||||||
|
if B_UNLIKELY ((m_cursor + size > m_dataSize))
|
||||||
|
return MakeUnexpected(String("Unexpected EOF while reading"));
|
||||||
|
|
||||||
|
T value;
|
||||||
|
std::memcpy(&value, &m_data[m_cursor], size);
|
||||||
|
m_cursor += size;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
66
Src/IACore/inc/IACore/StreamWriter.hpp
Normal file
66
Src/IACore/inc/IACore/StreamWriter.hpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
class StreamWriter
|
||||||
|
{
|
||||||
|
enum class EStorageType
|
||||||
|
{
|
||||||
|
NON_OWNING,
|
||||||
|
OWNING_FILE,
|
||||||
|
OWNING_VECTOR,
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
BOOL Write(IN UINT8 byte, IN SIZE_T count);
|
||||||
|
BOOL Write(IN PCVOID buffer, IN SIZE_T size);
|
||||||
|
template<typename T> BOOL Write(IN CONST T &value);
|
||||||
|
|
||||||
|
PCUINT8 Data() CONST
|
||||||
|
{
|
||||||
|
return m_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
SIZE_T Cursor() CONST
|
||||||
|
{
|
||||||
|
return m_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
StreamWriter();
|
||||||
|
explicit StreamWriter(IN Span<UINT8> data);
|
||||||
|
explicit StreamWriter(IN CONST FilePath &path);
|
||||||
|
~StreamWriter();
|
||||||
|
|
||||||
|
private:
|
||||||
|
PUINT8 m_buffer{};
|
||||||
|
SIZE_T m_cursor{};
|
||||||
|
SIZE_T m_capacity{};
|
||||||
|
FilePath m_filePath{};
|
||||||
|
Vector<UINT8> m_owningVector;
|
||||||
|
CONST EStorageType m_storageType;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T> BOOL StreamWriter::Write(IN CONST T &value)
|
||||||
|
{
|
||||||
|
return Write(&value, sizeof(T));
|
||||||
|
}
|
||||||
|
} // namespace IACore
|
||||||
29
Src/IACore/inc/IACore/StringOps.hpp
Normal file
29
Src/IACore/inc/IACore/StringOps.hpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <IACore/PCH.hpp>
|
||||||
|
|
||||||
|
namespace IACore
|
||||||
|
{
|
||||||
|
class StringOps
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
STATIC String EncodeBase64(IN Span<CONST UINT8> data);
|
||||||
|
STATIC Vector<UINT8> DecodeBase64(IN CONST String& data);
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
|
add_subdirectory(Subjects/)
|
||||||
add_subdirectory(Unit/)
|
add_subdirectory(Unit/)
|
||||||
add_subdirectory(Regression/)
|
add_subdirectory(Regression/)
|
||||||
|
|||||||
1
Tests/Subjects/CMakeLists.txt
Normal file
1
Tests/Subjects/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
add_executable(LongProcess LongProcess/Main.cpp)
|
||||||
12
Tests/Subjects/LongProcess/Main.cpp
Normal file
12
Tests/Subjects/LongProcess/Main.cpp
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#include <thread>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main(int, char **)
|
||||||
|
{
|
||||||
|
std::cout << "Started!\n";
|
||||||
|
std::cout.flush();
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||||
|
std::cout << "Ended!\n";
|
||||||
|
std::cout.flush();
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#include <IACore/BinaryReader.hpp>
|
#include <IACore/BinaryStreamReader.hpp>
|
||||||
|
|
||||||
#include <IACore/IATest.hpp>
|
#include <IACore/IATest.hpp>
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ using namespace IACore;
|
|||||||
// Test Block Definition
|
// Test Block Definition
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
IAT_BEGIN_BLOCK(Core, BinaryReader)
|
IAT_BEGIN_BLOCK(Core, BinaryStreamReader)
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 1. Basic Primitive Reading (UINT8)
|
// 1. Basic Primitive Reading (UINT8)
|
||||||
@ -32,7 +32,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader)
|
|||||||
BOOL TestReadUint8()
|
BOOL TestReadUint8()
|
||||||
{
|
{
|
||||||
UINT8 data[] = { 0xAA, 0xBB, 0xCC };
|
UINT8 data[] = { 0xAA, 0xBB, 0xCC };
|
||||||
BinaryReader reader(data);
|
BinaryStreamReader reader(data);
|
||||||
|
|
||||||
// Read First Byte
|
// Read First Byte
|
||||||
auto val1 = reader.Read<UINT8>();
|
auto val1 = reader.Read<UINT8>();
|
||||||
@ -54,7 +54,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader)
|
|||||||
{
|
{
|
||||||
// 0x04030201 in Little Endian memory layout
|
// 0x04030201 in Little Endian memory layout
|
||||||
UINT8 data[] = { 0x01, 0x02, 0x03, 0x04 };
|
UINT8 data[] = { 0x01, 0x02, 0x03, 0x04 };
|
||||||
BinaryReader reader(data);
|
BinaryStreamReader reader(data);
|
||||||
|
|
||||||
auto val = reader.Read<UINT32>();
|
auto val = reader.Read<UINT32>();
|
||||||
IAT_CHECK(val.has_value());
|
IAT_CHECK(val.has_value());
|
||||||
@ -79,7 +79,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader)
|
|||||||
UINT8 data[4];
|
UINT8 data[4];
|
||||||
std::memcpy(data, &pi, 4);
|
std::memcpy(data, &pi, 4);
|
||||||
|
|
||||||
BinaryReader reader(data);
|
BinaryStreamReader reader(data);
|
||||||
auto val = reader.Read<FLOAT32>();
|
auto val = reader.Read<FLOAT32>();
|
||||||
|
|
||||||
IAT_CHECK(val.has_value());
|
IAT_CHECK(val.has_value());
|
||||||
@ -94,7 +94,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader)
|
|||||||
BOOL TestReadString()
|
BOOL TestReadString()
|
||||||
{
|
{
|
||||||
const char* raw = "HelloIA";
|
const char* raw = "HelloIA";
|
||||||
BinaryReader reader(std::span<const UINT8>((const UINT8*)raw, 7));
|
BinaryStreamReader reader(std::span<const UINT8>((const UINT8*)raw, 7));
|
||||||
|
|
||||||
// Read "Hello" (5 bytes)
|
// Read "Hello" (5 bytes)
|
||||||
auto str = reader.ReadString(5);
|
auto str = reader.ReadString(5);
|
||||||
@ -116,7 +116,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader)
|
|||||||
{
|
{
|
||||||
UINT8 src[] = { 1, 2, 3, 4, 5 };
|
UINT8 src[] = { 1, 2, 3, 4, 5 };
|
||||||
UINT8 dst[3] = { 0 };
|
UINT8 dst[3] = { 0 };
|
||||||
BinaryReader reader(src);
|
BinaryStreamReader reader(src);
|
||||||
|
|
||||||
// Read 3 bytes into dst
|
// Read 3 bytes into dst
|
||||||
auto res = reader.Read(dst, 3);
|
auto res = reader.Read(dst, 3);
|
||||||
@ -139,7 +139,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader)
|
|||||||
BOOL TestNavigation()
|
BOOL TestNavigation()
|
||||||
{
|
{
|
||||||
UINT8 data[10] = { 0 }; // Zero init
|
UINT8 data[10] = { 0 }; // Zero init
|
||||||
BinaryReader reader(data);
|
BinaryStreamReader reader(data);
|
||||||
|
|
||||||
IAT_CHECK_EQ(reader.Remaining(), (SIZE_T)10);
|
IAT_CHECK_EQ(reader.Remaining(), (SIZE_T)10);
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ IAT_BEGIN_BLOCK(Core, BinaryReader)
|
|||||||
BOOL TestBoundaryChecks()
|
BOOL TestBoundaryChecks()
|
||||||
{
|
{
|
||||||
UINT8 data[] = { 0x00, 0x00 };
|
UINT8 data[] = { 0x00, 0x00 };
|
||||||
BinaryReader reader(data);
|
BinaryStreamReader reader(data);
|
||||||
|
|
||||||
// Valid read
|
// Valid read
|
||||||
UNUSED(reader.Read<UINT16>());
|
UNUSED(reader.Read<UINT16>());
|
||||||
@ -213,7 +213,7 @@ int main(int argc, char* argv[])
|
|||||||
// Define runner (StopOnFail=false, Verbose=true)
|
// Define runner (StopOnFail=false, Verbose=true)
|
||||||
ia::iatest::runner<false, true> testRunner;
|
ia::iatest::runner<false, true> testRunner;
|
||||||
|
|
||||||
// Run the BinaryReader block
|
// Run the BinaryStreamReader block
|
||||||
testRunner.testBlock<Core_BinaryReader>();
|
testRunner.testBlock<Core_BinaryReader>();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#include <IACore/BinaryWriter.hpp>
|
#include <IACore/BinaryStreamWriter.hpp>
|
||||||
|
|
||||||
#include <IACore/IATest.hpp>
|
#include <IACore/IATest.hpp>
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ using namespace IACore;
|
|||||||
// Test Block Definition
|
// Test Block Definition
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
IAT_BEGIN_BLOCK(Core, BinaryWriter)
|
IAT_BEGIN_BLOCK(Core, BinaryStreamWriter)
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 1. Vector Mode (Dynamic Growth)
|
// 1. Vector Mode (Dynamic Growth)
|
||||||
@ -33,7 +33,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter)
|
|||||||
{
|
{
|
||||||
std::vector<UINT8> buffer;
|
std::vector<UINT8> buffer;
|
||||||
// Start empty
|
// Start empty
|
||||||
BinaryWriter writer(buffer);
|
BinaryStreamWriter writer(buffer);
|
||||||
|
|
||||||
// Write 1 Byte
|
// Write 1 Byte
|
||||||
writer.Write<UINT8>(0xAA);
|
writer.Write<UINT8>(0xAA);
|
||||||
@ -62,7 +62,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter)
|
|||||||
{
|
{
|
||||||
// Vector starts with existing data
|
// Vector starts with existing data
|
||||||
std::vector<UINT8> buffer = { 0x01, 0x02 };
|
std::vector<UINT8> buffer = { 0x01, 0x02 };
|
||||||
BinaryWriter writer(buffer);
|
BinaryStreamWriter writer(buffer);
|
||||||
|
|
||||||
// Should append to end, not overwrite 0x01
|
// Should append to end, not overwrite 0x01
|
||||||
writer.Write<UINT8>(0x03);
|
writer.Write<UINT8>(0x03);
|
||||||
@ -83,7 +83,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter)
|
|||||||
// Initialize with zeros
|
// Initialize with zeros
|
||||||
std::memset(rawData, 0, 10);
|
std::memset(rawData, 0, 10);
|
||||||
|
|
||||||
BinaryWriter writer(rawData); // Implicit conversion to span
|
BinaryStreamWriter writer(rawData); // Implicit conversion to span
|
||||||
|
|
||||||
// Write UINT8
|
// Write UINT8
|
||||||
writer.Write<UINT8>(0xFF);
|
writer.Write<UINT8>(0xFF);
|
||||||
@ -106,7 +106,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter)
|
|||||||
BOOL TestWriteBytes()
|
BOOL TestWriteBytes()
|
||||||
{
|
{
|
||||||
std::vector<UINT8> buffer;
|
std::vector<UINT8> buffer;
|
||||||
BinaryWriter writer(buffer);
|
BinaryStreamWriter writer(buffer);
|
||||||
|
|
||||||
const char* msg = "IA";
|
const char* msg = "IA";
|
||||||
writer.WriteBytes((PVOID)msg, 2);
|
writer.WriteBytes((PVOID)msg, 2);
|
||||||
@ -126,7 +126,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter)
|
|||||||
BOOL TestRandomAccess()
|
BOOL TestRandomAccess()
|
||||||
{
|
{
|
||||||
std::vector<UINT8> buffer;
|
std::vector<UINT8> buffer;
|
||||||
BinaryWriter writer(buffer);
|
BinaryStreamWriter writer(buffer);
|
||||||
|
|
||||||
// Fill with placeholders
|
// Fill with placeholders
|
||||||
writer.Write<UINT32>(0xFFFFFFFF);
|
writer.Write<UINT32>(0xFFFFFFFF);
|
||||||
@ -152,7 +152,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter)
|
|||||||
BOOL TestWriteFloat()
|
BOOL TestWriteFloat()
|
||||||
{
|
{
|
||||||
std::vector<UINT8> buffer;
|
std::vector<UINT8> buffer;
|
||||||
BinaryWriter writer(buffer);
|
BinaryStreamWriter writer(buffer);
|
||||||
|
|
||||||
FLOAT32 val = 1.234f;
|
FLOAT32 val = 1.234f;
|
||||||
writer.Write<FLOAT32>(val);
|
writer.Write<FLOAT32>(val);
|
||||||
@ -174,7 +174,7 @@ IAT_BEGIN_BLOCK(Core, BinaryWriter)
|
|||||||
BOOL TestGetPtr()
|
BOOL TestGetPtr()
|
||||||
{
|
{
|
||||||
std::vector<UINT8> buffer = { 0x10, 0x20, 0x30 };
|
std::vector<UINT8> buffer = { 0x10, 0x20, 0x30 };
|
||||||
BinaryWriter writer(buffer);
|
BinaryStreamWriter writer(buffer);
|
||||||
|
|
||||||
PUINT8 p1 = writer.GetPtrAt(1);
|
PUINT8 p1 = writer.GetPtrAt(1);
|
||||||
IAT_CHECK(p1 != nullptr);
|
IAT_CHECK(p1 != nullptr);
|
||||||
@ -216,7 +216,7 @@ int main(int argc, char* argv[])
|
|||||||
ia::iatest::runner<false, true> testRunner;
|
ia::iatest::runner<false, true> testRunner;
|
||||||
|
|
||||||
// Run the BinaryReader block
|
// Run the BinaryReader block
|
||||||
testRunner.testBlock<Core_BinaryWriter>();
|
testRunner.testBlock<Core_BinaryStreamWriter>();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -13,21 +13,21 @@ set_target_properties(${TEST_NAME_PREFIX}CCompile PROPERTIES
|
|||||||
|
|
||||||
target_link_libraries(${TEST_NAME_PREFIX}CCompile PRIVATE IACore)
|
target_link_libraries(${TEST_NAME_PREFIX}CCompile PRIVATE IACore)
|
||||||
|
|
||||||
# ------------------------------------------------
|
## ------------------------------------------------
|
||||||
# Unit: BinaryReader
|
## Unit: BinaryReader
|
||||||
# ------------------------------------------------
|
## ------------------------------------------------
|
||||||
add_executable(${TEST_NAME_PREFIX}BinaryReader "BinaryReader.cpp")
|
#add_executable(${TEST_NAME_PREFIX}BinaryReader "BinaryReader.cpp")
|
||||||
target_link_libraries(${TEST_NAME_PREFIX}BinaryReader PRIVATE IACore)
|
#target_link_libraries(${TEST_NAME_PREFIX}BinaryReader PRIVATE IACore)
|
||||||
target_compile_options(${TEST_NAME_PREFIX}BinaryReader PRIVATE -fexceptions)
|
#target_compile_options(${TEST_NAME_PREFIX}BinaryReader PRIVATE -fexceptions)
|
||||||
set_target_properties(${TEST_NAME_PREFIX}BinaryReader PROPERTIES USE_EXCEPTIONS ON)
|
#set_target_properties(${TEST_NAME_PREFIX}BinaryReader PROPERTIES USE_EXCEPTIONS ON)
|
||||||
|
#
|
||||||
# ------------------------------------------------
|
## ------------------------------------------------
|
||||||
# Unit: BinaryWriter
|
## Unit: BinaryWriter
|
||||||
# ------------------------------------------------
|
## ------------------------------------------------
|
||||||
add_executable(${TEST_NAME_PREFIX}BinaryWriter "BinaryWriter.cpp")
|
#add_executable(${TEST_NAME_PREFIX}BinaryWriter "BinaryWriter.cpp")
|
||||||
target_link_libraries(${TEST_NAME_PREFIX}BinaryWriter PRIVATE IACore)
|
#target_link_libraries(${TEST_NAME_PREFIX}BinaryWriter PRIVATE IACore)
|
||||||
target_compile_options(${TEST_NAME_PREFIX}BinaryWriter PRIVATE -fexceptions)
|
#target_compile_options(${TEST_NAME_PREFIX}BinaryWriter PRIVATE -fexceptions)
|
||||||
set_target_properties(${TEST_NAME_PREFIX}BinaryWriter PROPERTIES USE_EXCEPTIONS ON)
|
#set_target_properties(${TEST_NAME_PREFIX}BinaryWriter PROPERTIES USE_EXCEPTIONS ON)
|
||||||
|
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------
|
||||||
# Unit: Environment
|
# Unit: Environment
|
||||||
@ -38,20 +38,20 @@ target_compile_options(${TEST_NAME_PREFIX}Environment PRIVATE -fexceptions)
|
|||||||
set_target_properties(${TEST_NAME_PREFIX}Environment PROPERTIES USE_EXCEPTIONS ON)
|
set_target_properties(${TEST_NAME_PREFIX}Environment PROPERTIES USE_EXCEPTIONS ON)
|
||||||
|
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------
|
||||||
# Unit: File
|
# Unit: FileOps
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------
|
||||||
add_executable(${TEST_NAME_PREFIX}File "File.cpp")
|
#add_executable(${TEST_NAME_PREFIX}FileOps "FileOps.cpp")
|
||||||
target_link_libraries(${TEST_NAME_PREFIX}File PRIVATE IACore)
|
#target_link_libraries(${TEST_NAME_PREFIX}FileOps PRIVATE IACore)
|
||||||
target_compile_options(${TEST_NAME_PREFIX}File PRIVATE -fexceptions)
|
#target_compile_options(${TEST_NAME_PREFIX}FileOps PRIVATE -fexceptions)
|
||||||
set_target_properties(${TEST_NAME_PREFIX}File PROPERTIES USE_EXCEPTIONS ON)
|
#set_target_properties(${TEST_NAME_PREFIX}FileOps PROPERTIES USE_EXCEPTIONS ON)
|
||||||
|
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------
|
||||||
# Unit: Process
|
# Unit: ProcessOps
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------
|
||||||
add_executable(${TEST_NAME_PREFIX}Process "Process.cpp")
|
add_executable(${TEST_NAME_PREFIX}ProcessOps "ProcessOps.cpp")
|
||||||
target_link_libraries(${TEST_NAME_PREFIX}Process PRIVATE IACore)
|
target_link_libraries(${TEST_NAME_PREFIX}ProcessOps PRIVATE IACore)
|
||||||
target_compile_options(${TEST_NAME_PREFIX}Process PRIVATE -fexceptions)
|
target_compile_options(${TEST_NAME_PREFIX}ProcessOps PRIVATE -fexceptions)
|
||||||
set_target_properties(${TEST_NAME_PREFIX}Process PROPERTIES USE_EXCEPTIONS ON)
|
set_target_properties(${TEST_NAME_PREFIX}ProcessOps PROPERTIES USE_EXCEPTIONS ON)
|
||||||
|
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------
|
||||||
# Unit: Utils
|
# Unit: Utils
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#include <IACore/File.hpp>
|
#include <IACore/FileOps.hpp>
|
||||||
|
|
||||||
#include <IACore/IATest.hpp>
|
#include <IACore/IATest.hpp>
|
||||||
|
|
||||||
@ -129,7 +129,7 @@ IAT_BEGIN_BLOCK(Core, File)
|
|||||||
// 1. Write
|
// 1. Write
|
||||||
{
|
{
|
||||||
File f;
|
File f;
|
||||||
auto res = f.Open(path, flagsWrite);
|
auto res = f.Open(path, (File::EOpenFlags)flagsWrite);
|
||||||
IAT_CHECK(res.has_value());
|
IAT_CHECK(res.has_value());
|
||||||
IAT_CHECK(f.IsOpen());
|
IAT_CHECK(f.IsOpen());
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ IAT_BEGIN_BLOCK(Core, File)
|
|||||||
UINT32 flagsRead = (UINT32)File::EOpenFlags::Read |
|
UINT32 flagsRead = (UINT32)File::EOpenFlags::Read |
|
||||||
(UINT32)File::EOpenFlags::Binary;
|
(UINT32)File::EOpenFlags::Binary;
|
||||||
|
|
||||||
File f(path, flagsRead); // Test RAII constructor
|
File f(path, (File::EOpenFlags)flagsRead); // Test RAII constructor
|
||||||
IAT_CHECK(f.IsOpen());
|
IAT_CHECK(f.IsOpen());
|
||||||
|
|
||||||
UINT32 magicRead = 0;
|
UINT32 magicRead = 0;
|
||||||
@ -172,7 +172,7 @@ IAT_BEGIN_BLOCK(Core, File)
|
|||||||
|
|
||||||
// Open in append mode
|
// Open in append mode
|
||||||
File f;
|
File f;
|
||||||
const auto openResult = f.Open(path, flagsAppend);
|
const auto openResult = f.Open(path, (File::EOpenFlags)flagsAppend);
|
||||||
if(!openResult)
|
if(!openResult)
|
||||||
{
|
{
|
||||||
IA_PANIC(openResult.error().c_str())
|
IA_PANIC(openResult.error().c_str())
|
||||||
@ -209,7 +209,7 @@ IAT_BEGIN_BLOCK(Core, File)
|
|||||||
|
|
||||||
// Test Instance
|
// Test Instance
|
||||||
File f;
|
File f;
|
||||||
auto resInstance = f.Open(ghostPath, (UINT32)File::EOpenFlags::Read);
|
auto resInstance = f.Open(ghostPath, File::EOpenFlags::Read);
|
||||||
IAT_CHECK_NOT(resInstance.has_value());
|
IAT_CHECK_NOT(resInstance.has_value());
|
||||||
IAT_CHECK_NOT(f.IsOpen());
|
IAT_CHECK_NOT(f.IsOpen());
|
||||||
|
|
||||||
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#include <IACore/Process.hpp>
|
#include <IACore/ProcessOps.hpp>
|
||||||
|
|
||||||
#include <IACore/IATest.hpp>
|
#include <IACore/IATest.hpp>
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ IAT_BEGIN_BLOCK(Core, Process)
|
|||||||
// Simple "echo hello"
|
// Simple "echo hello"
|
||||||
String captured;
|
String captured;
|
||||||
|
|
||||||
auto result = Process::Run(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA",
|
auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA",
|
||||||
[&](StringView line) {
|
[&](StringView line) {
|
||||||
captured = line;
|
captured = line;
|
||||||
}
|
}
|
||||||
@ -73,7 +73,7 @@ IAT_BEGIN_BLOCK(Core, Process)
|
|||||||
String args = String(CMD_ARG_PREFIX) + " one two";
|
String args = String(CMD_ARG_PREFIX) + " one two";
|
||||||
if(args[0] == ' ') args.erase(0, 1); // cleanup space if prefix empty
|
if(args[0] == ' ') args.erase(0, 1); // cleanup space if prefix empty
|
||||||
|
|
||||||
auto result = Process::Run(CMD_ECHO_EXE, args,
|
auto result = ProcessOps::SpawnProcessSync(CMD_ECHO_EXE, args,
|
||||||
[&](StringView line) {
|
[&](StringView line) {
|
||||||
lines.push_back(String(line));
|
lines.push_back(String(line));
|
||||||
}
|
}
|
||||||
@ -107,7 +107,7 @@ IAT_BEGIN_BLOCK(Core, Process)
|
|||||||
arg = "-c \"exit 42\""; // quotes needed for sh -c
|
arg = "-c \"exit 42\""; // quotes needed for sh -c
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto result = Process::Run(cmd, arg, [](StringView){});
|
auto result = ProcessOps::SpawnProcessSync(cmd, arg, [](StringView){});
|
||||||
|
|
||||||
IAT_CHECK(result.has_value());
|
IAT_CHECK(result.has_value());
|
||||||
IAT_CHECK_EQ(*result, 42);
|
IAT_CHECK_EQ(*result, 42);
|
||||||
@ -121,7 +121,7 @@ IAT_BEGIN_BLOCK(Core, Process)
|
|||||||
BOOL TestMissingExe()
|
BOOL TestMissingExe()
|
||||||
{
|
{
|
||||||
// Try to run a random string
|
// Try to run a random string
|
||||||
auto result = Process::Run("sdflkjghsdflkjg", "", [](StringView){});
|
auto result = ProcessOps::SpawnProcessSync("sdflkjghsdflkjg", "", [](StringView){});
|
||||||
|
|
||||||
// Windows: CreateProcess usually fails -> returns unexpected
|
// Windows: CreateProcess usually fails -> returns unexpected
|
||||||
// Linux: execvp fails inside child, returns 127 via waitpid
|
// Linux: execvp fails inside child, returns 127 via waitpid
|
||||||
@ -166,7 +166,7 @@ IAT_BEGIN_BLOCK(Core, Process)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
String captured;
|
String captured;
|
||||||
auto result = Process::Run(cmd, arg,
|
auto result = ProcessOps::SpawnProcessSync(cmd, arg,
|
||||||
[&](StringView line) {
|
[&](StringView line) {
|
||||||
captured += line;
|
captured += line;
|
||||||
}
|
}
|
||||||
@ -203,7 +203,7 @@ IAT_BEGIN_BLOCK(Core, Process)
|
|||||||
bool foundA = false;
|
bool foundA = false;
|
||||||
bool foundB = false;
|
bool foundB = false;
|
||||||
|
|
||||||
UNUSED(Process::Run(cmd, arg, [&](StringView line) {
|
UNUSED(ProcessOps::SpawnProcessSync(cmd, arg, [&](StringView line) {
|
||||||
lineCount++;
|
lineCount++;
|
||||||
if (line.find("LineA") != String::npos) foundA = true;
|
if (line.find("LineA") != String::npos) foundA = true;
|
||||||
if (line.find("LineB") != String::npos) foundB = true;
|
if (line.find("LineB") != String::npos) foundB = true;
|
||||||
@ -20,7 +20,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
def main(args: list[str]):
|
def main(args: list[str]):
|
||||||
os.system("cmake -S. -B./Build/Linux -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug")
|
os.system("cmake -S. -B./Build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug")
|
||||||
os.system("cmake --build ./Build/Linux")
|
os.system("cmake --build ./Build")
|
||||||
|
|
||||||
main(sys.argv)
|
main(sys.argv)
|
||||||
|
|||||||
10
Vendor/CMakeLists.txt
vendored
10
Vendor/CMakeLists.txt
vendored
@ -1,10 +0,0 @@
|
|||||||
|
|
||||||
set(EXPECTED_BUILD_TESTS OFF)
|
|
||||||
add_subdirectory(expected/)
|
|
||||||
|
|
||||||
set(MI_BUILD_TESTS OFF)
|
|
||||||
set(MI_BUILD_STATIC ON)
|
|
||||||
set(MI_BUILD_SHARED ON)
|
|
||||||
add_subdirectory(mimalloc/)
|
|
||||||
|
|
||||||
add_subdirectory(unordered_dense/)
|
|
||||||
1
Vendor/expected
vendored
1
Vendor/expected
vendored
Submodule Vendor/expected deleted from 1770e3559f
1
Vendor/mimalloc
vendored
1
Vendor/mimalloc
vendored
Submodule Vendor/mimalloc deleted from 09a27098aa
1
Vendor/unordered_dense
vendored
1
Vendor/unordered_dense
vendored
Submodule Vendor/unordered_dense deleted from 3234af2c03
Reference in New Issue
Block a user