IACore v1.2
This commit is contained in:
32
.clang-format
Normal file
32
.clang-format
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
Language: Cpp
|
||||
Standard: c++20
|
||||
BasedOnStyle: Microsoft
|
||||
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
AccessModifierOffset: -4
|
||||
|
||||
IndentPPDirectives: AfterHash
|
||||
IndentRequiresClause: true
|
||||
BreakBeforeConceptDeclarations: true
|
||||
SpaceAfterTemplateKeyword: false
|
||||
|
||||
FixNamespaceComments: true
|
||||
NamespaceIndentation: All
|
||||
CompactNamespaces: true
|
||||
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments: true
|
||||
|
||||
SpaceAfterCStyleCast: true
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesInAngles: false
|
||||
|
||||
SeparateDefinitionBlocks: Always
|
||||
SortIncludes: false
|
||||
---
|
||||
78
.clang-tidy
Normal file
78
.clang-tidy
Normal file
@ -0,0 +1,78 @@
|
||||
Checks: >
|
||||
-*,
|
||||
readability-identifier-naming,
|
||||
readability-const-return-type,
|
||||
modernize-use-nodiscard,
|
||||
modernize-use-override,
|
||||
modernize-use-nullptr,
|
||||
bugprone-use-after-move
|
||||
|
||||
CheckOptions:
|
||||
# ----------------------------------------------------------------------------
|
||||
# Types (PascalCase) - Matches Rust structs/enums
|
||||
# ----------------------------------------------------------------------------
|
||||
- key: readability-identifier-naming.ClassCase
|
||||
value: PascalCase
|
||||
- key: readability-identifier-naming.StructCase
|
||||
value: PascalCase
|
||||
- key: readability-identifier-naming.TypedefCase
|
||||
value: PascalCase
|
||||
- key: readability-identifier-naming.EnumCase
|
||||
value: PascalCase
|
||||
- key: readability-identifier-naming.TemplateParameterCase
|
||||
value: PascalCase
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Functions & Methods (snake_case) - Matches Rust fn
|
||||
# ----------------------------------------------------------------------------
|
||||
- key: readability-identifier-naming.FunctionCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.MethodCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.ParameterCase
|
||||
value: lower_case
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Variables (snake_case) - Matches Rust let
|
||||
# ----------------------------------------------------------------------------
|
||||
- key: readability-identifier-naming.VariableCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.LocalVariableCase
|
||||
value: lower_case
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Members (m_snake_case for private, snake_case for public)
|
||||
# ----------------------------------------------------------------------------
|
||||
# Public struct members (like a Rust struct) -> x, y, width
|
||||
- key: readability-identifier-naming.PublicMemberCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.StructMemberIgnoredRegexp
|
||||
value: ^_[a-z0-9_]*$
|
||||
|
||||
# Private/Protected class members -> m_parser, m_count
|
||||
- key: readability-identifier-naming.PrivateMemberCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.PrivateMemberPrefix
|
||||
value: m_
|
||||
- key: readability-identifier-naming.ProtectedMemberCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.ProtectedMemberPrefix
|
||||
value: m_
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Constants (SCREAMING_SNAKE_CASE)
|
||||
# ----------------------------------------------------------------------------
|
||||
- key: readability-identifier-naming.GlobalConstantCase
|
||||
value: UPPER_CASE
|
||||
- key: readability-identifier-naming.ConstexprVariableCase
|
||||
value: UPPER_CASE
|
||||
- key: readability-identifier-naming.EnumConstantCase
|
||||
value: PascalCase
|
||||
# Note: Rust uses PascalCase for Enum Variants (Option::None).
|
||||
# Change to UPPER_CASE if you prefer C-style enums.
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Macros (SCREAMING_SNAKE_CASE)
|
||||
# ----------------------------------------------------------------------------
|
||||
- key: readability-identifier-naming.MacroDefinitionCase
|
||||
value: UPPER_CASE
|
||||
3
.clangd
Normal file
3
.clangd
Normal file
@ -0,0 +1,3 @@
|
||||
CompileFlags:
|
||||
Add: [-Wno-missing-field-initializers, -Wno-missing-designated-field-initializers]
|
||||
|
||||
31
.github/workflows/ci.yaml
vendored
Normal file
31
.github/workflows/ci.yaml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build-linux-and-wasm:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
container:
|
||||
image: ghcr.io/i-a-s/iabuild-env:latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [x64-linux]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install OpenSSL v3 (System)
|
||||
run: sudo dnf install -y openssl-devel
|
||||
|
||||
- name: Configure
|
||||
run: cmake --preset ${{ matrix.target }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build --preset ${{ matrix.target }} --config Release
|
||||
54
.gitignore
vendored
Normal file
54
.gitignore
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
# ---> C++
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# ---> VisualStudioCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
[Ss]andbox/
|
||||
|
||||
.cache/
|
||||
.local/
|
||||
out/
|
||||
[Bb]uild
|
||||
/compile_commands.json
|
||||
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug (Linux)",
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"program": "${command:cmake.launchTargetPath}",
|
||||
"args": [
|
||||
"dummy"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/",
|
||||
"preLaunchTask": "CMake Build"
|
||||
}
|
||||
]
|
||||
}
|
||||
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"C_Cpp.codeAnalysis.runAutomatically": "onSave",
|
||||
"C_Cpp.codeAnalysis.clangTidy.enabled": true,
|
||||
"C_Cpp.codeAnalysis.clangTidy.useBuildPath": false,
|
||||
"C_Cpp.codeAnalysis.clangTidy.config": "",
|
||||
"C_Cpp.clang_format_fallbackStyle": "Microsoft",
|
||||
"cmake.copyCompileCommands": "${workspaceFolder}/compile_commands.json",
|
||||
}
|
||||
19
.vscode/tasks.json
vendored
Normal file
19
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "CMake Build",
|
||||
"type": "cmake",
|
||||
"command": "build",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "silent",
|
||||
"panel": "shared"
|
||||
},
|
||||
"detail": "Builds the currently selected CMake Preset"
|
||||
}
|
||||
]
|
||||
}
|
||||
143
CMake/FindDeps.cmake
Normal file
143
CMake/FindDeps.cmake
Normal file
@ -0,0 +1,143 @@
|
||||
include(FetchContent)
|
||||
|
||||
find_package(OpenSSL 3.0.0 REQUIRED)
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "Force static libs")
|
||||
|
||||
set(HWY_ENABLE_TESTS OFF CACHE BOOL "Disable Highway tests" FORCE)
|
||||
set(HWY_ENABLE_EXAMPLES OFF CACHE BOOL "Disable Highway examples" FORCE)
|
||||
set(HWY_ENABLE_CONTRIB OFF CACHE BOOL "Disable Highway contrib" FORCE)
|
||||
set(HWY_ENABLE_INSTALL OFF CACHE BOOL "Disable Highway install rules" FORCE)
|
||||
|
||||
set(ZLIB_USE_STATIC_LIBS ON CACHE BOOL "" FORCE)
|
||||
set(ZLIB_COMPAT ON CACHE BOOL "" FORCE)
|
||||
set(ZLIB_ENABLE_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(WITH_GZFILEOP ON CACHE BOOL "" FORCE)
|
||||
|
||||
FetchContent_Declare(
|
||||
Oxide
|
||||
GIT_REPOSITORY https://github.com/I-A-S/Oxide
|
||||
GIT_TAG main
|
||||
OVERRIDE_FIND_PACKAGE
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
zlib
|
||||
GIT_REPOSITORY https://github.com/zlib-ng/zlib-ng.git
|
||||
GIT_TAG 2.1.6
|
||||
OVERRIDE_FIND_PACKAGE
|
||||
)
|
||||
|
||||
set(ZSTD_BUILD_PROGRAMS OFF CACHE BOOL "" FORCE)
|
||||
set(ZSTD_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(ZSTD_BUILD_SHARED OFF CACHE BOOL "" FORCE)
|
||||
set(ZSTD_BUILD_STATIC ON CACHE BOOL "" FORCE)
|
||||
|
||||
FetchContent_Declare(
|
||||
zstd
|
||||
GIT_REPOSITORY https://github.com/facebook/zstd.git
|
||||
GIT_TAG v1.5.5
|
||||
SOURCE_SUBDIR build/cmake
|
||||
OVERRIDE_FIND_PACKAGE
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
httplib
|
||||
GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git
|
||||
GIT_TAG v0.28.0
|
||||
SYSTEM
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
nlohmann_json
|
||||
GIT_REPOSITORY https://github.com/nlohmann/json.git
|
||||
GIT_TAG v3.12.0
|
||||
SYSTEM
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
glaze
|
||||
GIT_REPOSITORY https://github.com/stephenberry/glaze.git
|
||||
GIT_TAG v5.0.0
|
||||
SYSTEM
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
simdjson
|
||||
GIT_REPOSITORY https://github.com/simdjson/simdjson.git
|
||||
GIT_TAG v4.2.2
|
||||
SYSTEM
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
mimalloc
|
||||
GIT_REPOSITORY https://github.com/microsoft/mimalloc.git
|
||||
GIT_TAG v3.0.10
|
||||
SYSTEM
|
||||
EXCLUDE_FROM_ALL
|
||||
PATCH_COMMAND ${CMAKE_COMMAND}
|
||||
-DSOURCE_DIR=<SOURCE_DIR>
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/CMake/PatchMimalloc.cmake
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
unordered_dense
|
||||
GIT_REPOSITORY https://github.com/martinus/unordered_dense.git
|
||||
GIT_TAG v4.8.1
|
||||
SYSTEM
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
pugixml
|
||||
GIT_REPOSITORY https://github.com/zeux/pugixml.git
|
||||
GIT_TAG v1.15
|
||||
SYSTEM
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
highway
|
||||
GIT_REPOSITORY https://github.com/google/highway.git
|
||||
GIT_TAG 1.3.0
|
||||
SYSTEM
|
||||
)
|
||||
|
||||
set(MI_OVERRIDE ON CACHE BOOL "" FORCE)
|
||||
set(MI_BUILD_STATIC ON CACHE BOOL "" FORCE)
|
||||
set(MI_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(MI_BUILD_SHARED OFF CACHE BOOL "" FORCE)
|
||||
set(MI_DEBUG OFF CACHE BOOL "" FORCE)
|
||||
set(MI_SHOW_ERRORS OFF CACHE BOOL "" FORCE)
|
||||
|
||||
set(EXPECTED_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
|
||||
set(HTTPLIB_REQUIRE_OPENSSL ON CACHE BOOL "" FORCE)
|
||||
set(HTTPLIB_REQUIRE_ZLIB OFF CACHE BOOL "" FORCE)
|
||||
set(HTTPLIB_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
|
||||
set(HTTPLIB_COMPILE OFF CACHE BOOL "" FORCE)
|
||||
set(HTTPLIB_TEST OFF CACHE BOOL "" FORCE)
|
||||
set(HTTPLIB_EXAMPLE OFF CACHE BOOL "" FORCE)
|
||||
|
||||
FetchContent_MakeAvailable(zlib zstd)
|
||||
|
||||
target_include_directories(libzstd_static INTERFACE
|
||||
$<BUILD_INTERFACE:${zstd_SOURCE_DIR}/lib>
|
||||
)
|
||||
|
||||
if(NOT TARGET zstd::libzstd)
|
||||
add_library(zstd::libzstd ALIAS libzstd_static)
|
||||
endif()
|
||||
|
||||
FetchContent_MakeAvailable(Oxide httplib pugixml nlohmann_json glaze simdjson unordered_dense mimalloc highway)
|
||||
|
||||
if(NOT TARGET simdjson::simdjson)
|
||||
add_library(simdjson::simdjson ALIAS simdjson)
|
||||
endif()
|
||||
|
||||
target_compile_options(hwy PRIVATE -w)
|
||||
target_compile_options(libzstd_static PRIVATE -w)
|
||||
29
CMake/IAProjectConfig.cmake
Normal file
29
CMake/IAProjectConfig.cmake
Normal file
@ -0,0 +1,29 @@
|
||||
macro(iacore_setup_project)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
|
||||
|
||||
if(MSVC)
|
||||
add_compile_options(/W4)
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
add_compile_options(-Wno-c++98-compat -Wno-c++98-compat-pedantic)
|
||||
endif()
|
||||
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
|
||||
add_compile_options(-Wall -Wextra -Wpedantic -Wno-language-extension-token)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "amd64|x86_64|AMD64")
|
||||
set(IACORE_ARCH_X64 TRUE CACHE INTERNAL "")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64")
|
||||
set(IACORE_ARCH_ARM64 TRUE CACHE INTERNAL "")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "wasm32|emscripten")
|
||||
set(IACORE_ARCH_WASM TRUE CACHE INTERNAL "")
|
||||
endif()
|
||||
|
||||
endmacro()
|
||||
36
CMake/PatchMimalloc.cmake
Normal file
36
CMake/PatchMimalloc.cmake
Normal file
@ -0,0 +1,36 @@
|
||||
# Get the file path from the command line argument
|
||||
set(TARGET_FILE "${SOURCE_DIR}/src/prim/windows/prim.c")
|
||||
|
||||
# Read the file
|
||||
file(READ "${TARGET_FILE}" FILE_CONTENT)
|
||||
|
||||
# Check if the patch is already applied
|
||||
string(FIND "${FILE_CONTENT}" "struct _TEB* const teb" ALREADY_PATCHED)
|
||||
|
||||
if(ALREADY_PATCHED EQUAL -1)
|
||||
message(STATUS "Patching mimalloc source: ${TARGET_FILE}")
|
||||
|
||||
# Perform the replacement
|
||||
string(REPLACE
|
||||
"_TEB* const teb = NtCurrentTeb()"
|
||||
"struct _TEB* const teb = NtCurrentTeb()"
|
||||
FILE_CONTENT
|
||||
"${FILE_CONTENT}"
|
||||
)
|
||||
|
||||
# Write the file back only if changes were made
|
||||
file(WRITE "${TARGET_FILE}" "${FILE_CONTENT}")
|
||||
else()
|
||||
message(STATUS "mimalloc source is already patched. Skipping.")
|
||||
endif()
|
||||
|
||||
# Patch mimalloc complaing about false positive alignment issues in libc loader
|
||||
file(READ "${SOURCE_DIR}/CMakeLists.txt" MI_CMAKE_CONTENT)
|
||||
string(REPLACE
|
||||
"set(mi_debug_default ON)"
|
||||
"set(mi_debug_default OFF)"
|
||||
MI_CMAKE_CONTENT
|
||||
"${MI_CMAKE_CONTENT}"
|
||||
)
|
||||
file(WRITE "${SOURCE_DIR}/CMakeLists.txt" "${MI_CMAKE_CONTENT}")
|
||||
message(STATUS "Patched mimalloc: Forced MI_DEBUG default to OFF to silence alignment warnings.")
|
||||
21
CMake/Toolchains/arm64-linux-clang.cmake
Normal file
21
CMake/Toolchains/arm64-linux-clang.cmake
Normal file
@ -0,0 +1,21 @@
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
|
||||
set(triple aarch64-linux-gnu)
|
||||
set(CMAKE_C_COMPILER_TARGET ${triple})
|
||||
set(CMAKE_CXX_COMPILER_TARGET ${triple})
|
||||
|
||||
set(CMAKE_SYSROOT /usr/aarch64-linux-gnu/sys-root)
|
||||
set(CMAKE_LIBRARY_ARCHITECTURE aarch64-linux-gnu)
|
||||
|
||||
string(APPEND CMAKE_C_FLAGS " -march=armv8-a+simd")
|
||||
string(APPEND CMAKE_CXX_FLAGS " -march=armv8-a+simd")
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
|
||||
link_directories(${CMAKE_SYSROOT}/usr/lib64)
|
||||
link_directories(${CMAKE_SYSROOT}/lib64)
|
||||
|
||||
18
CMake/Toolchains/arm64-windows-clang.cmake
Normal file
18
CMake/Toolchains/arm64-windows-clang.cmake
Normal file
@ -0,0 +1,18 @@
|
||||
set(CMAKE_SYSTEM_NAME Windows)
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
set(CMAKE_C_COMPILER clang-cl)
|
||||
set(CMAKE_CXX_COMPILER clang-cl)
|
||||
set(CMAKE_RC_COMPILER llvm-rc)
|
||||
|
||||
set(CMAKE_LINKER lld-link)
|
||||
|
||||
set(triple arm64-pc-windows-msvc)
|
||||
set(CMAKE_C_COMPILER_TARGET ${triple})
|
||||
set(CMAKE_CXX_COMPILER_TARGET ${triple})
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
|
||||
string(APPEND CMAKE_C_FLAGS " /clang:--target=arm64-pc-windows-msvc")
|
||||
string(APPEND CMAKE_CXX_FLAGS " /clang:--target=arm64-pc-windows-msvc")
|
||||
7
CMake/Toolchains/wasm32-emscripten-clang.cmake
Normal file
7
CMake/Toolchains/wasm32-emscripten-clang.cmake
Normal file
@ -0,0 +1,7 @@
|
||||
set(CMAKE_SYSTEM_NAME Emscripten)
|
||||
set(CMAKE_SYSTEM_PROCESSOR wasm32)
|
||||
|
||||
include("$ENV{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake")
|
||||
|
||||
string(APPEND CMAKE_C_FLAGS " -msimd128 -pthread")
|
||||
string(APPEND CMAKE_CXX_FLAGS " -msimd128 -pthread")
|
||||
6
CMake/Toolchains/x64-linux-clang.cmake
Normal file
6
CMake/Toolchains/x64-linux-clang.cmake
Normal file
@ -0,0 +1,6 @@
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR AMD64)
|
||||
|
||||
string(APPEND CMAKE_C_FLAGS " -mavx2 -mfma -mxsave -msse4.2")
|
||||
string(APPEND CMAKE_CXX_FLAGS " -mavx2 -mfma -mxsave -msse4.2")
|
||||
|
||||
14
CMake/Toolchains/x64-windows.cmake
Normal file
14
CMake/Toolchains/x64-windows.cmake
Normal file
@ -0,0 +1,14 @@
|
||||
set(CMAKE_SYSTEM_NAME Windows)
|
||||
set(CMAKE_SYSTEM_PROCESSOR AMD64)
|
||||
set(CMAKE_C_COMPILER clang-cl)
|
||||
set(CMAKE_CXX_COMPILER clang-cl)
|
||||
set(CMAKE_RC_COMPILER llvm-rc)
|
||||
|
||||
set(triple x86_64-pc-windows-msvc)
|
||||
set(CMAKE_C_COMPILER_TARGET ${triple})
|
||||
set(CMAKE_CXX_COMPILER_TARGET ${triple})
|
||||
|
||||
set(CMAKE_LINKER lld-link)
|
||||
|
||||
string(APPEND CMAKE_C_FLAGS " /arch:AVX2 /clang:-mfma")
|
||||
string(APPEND CMAKE_CXX_FLAGS " /arch:AVX2 /clang:-mfma")
|
||||
41
CMakeLists.txt
Normal file
41
CMakeLists.txt
Normal file
@ -0,0 +1,41 @@
|
||||
cmake_minimum_required(VERSION 3.28 FATAL_ERROR)
|
||||
|
||||
project(IACore LANGUAGES C CXX)
|
||||
|
||||
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(IACORE_IS_TOP_LEVEL ON)
|
||||
else()
|
||||
set(IACORE_IS_TOP_LEVEL OFF)
|
||||
endif()
|
||||
|
||||
set(IACORE_ROOT "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "")
|
||||
set(IACORE_CMAKE_DIR "${IACORE_ROOT}/CMake" CACHE INTERNAL "")
|
||||
list(APPEND CMAKE_MODULE_PATH "${IACORE_CMAKE_DIR}")
|
||||
|
||||
include(IAProjectConfig)
|
||||
iacore_setup_project()
|
||||
|
||||
message(STATUS "[IACore] Detected Compiler: ${CMAKE_CXX_COMPILER_ID}")
|
||||
|
||||
if (MSVC AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
message(FATAL_ERROR
|
||||
"\n-------------------------------------------------------------\n"
|
||||
"CRITICAL ERROR: Unsupported Compiler (MSVC/cl.exe)\n"
|
||||
"IACore requires GCC or Clang. On Windows, use 'Clang-cl' or MinGW.\n"
|
||||
"-------------------------------------------------------------\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
include(FindDeps)
|
||||
|
||||
option(IACore_BUILD_TESTS "Build unit tests" ${IACORE_IS_TOP_LEVEL})
|
||||
|
||||
add_subdirectory(Src)
|
||||
|
||||
if(IACore_BUILD_TESTS)
|
||||
add_subdirectory(Tests)
|
||||
endif()
|
||||
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Sandbox")
|
||||
# add_subdirectory(Sandbox)
|
||||
endif()
|
||||
78
CMakePresets.json
Normal file
78
CMakePresets.json
Normal file
@ -0,0 +1,78 @@
|
||||
{
|
||||
"version": 3,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 28
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "iacore-base",
|
||||
"hidden": true,
|
||||
"generator": "Ninja Multi-Config",
|
||||
"binaryDir": "${sourceDir}/out/build/${presetName}",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "clang",
|
||||
"CMAKE_CXX_COMPILER": "clang++",
|
||||
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/CMake/Toolchains/${presetName}-clang.cmake"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x64-linux",
|
||||
"displayName": "IACore Linux x64 (Clang)",
|
||||
"inherits": "iacore-base",
|
||||
"cacheVariables": {}
|
||||
},
|
||||
{
|
||||
"name": "arm64-linux",
|
||||
"displayName": "IACore Linux ARM64 (Clang Cross)",
|
||||
"inherits": "iacore-base",
|
||||
"cacheVariables": {}
|
||||
},
|
||||
{
|
||||
"name": "x64-windows",
|
||||
"displayName": "IACore Windows x64 (Clang)",
|
||||
"inherits": "iacore-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "clang-cl",
|
||||
"CMAKE_CXX_COMPILER": "clang-cl"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "arm64-windows",
|
||||
"displayName": "IACore Windows ARM64 (Clang Cross)",
|
||||
"inherits": "iacore-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "clang-cl",
|
||||
"CMAKE_CXX_COMPILER": "clang-cl"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "wasm",
|
||||
"displayName": "IACore WebAssembly (Clang)",
|
||||
"inherits": "iacore-base",
|
||||
"cacheVariables": {}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "x64-linux",
|
||||
"configurePreset": "x64-linux"
|
||||
},
|
||||
{
|
||||
"name": "arm64-linux",
|
||||
"configurePreset": "arm64-linux"
|
||||
},
|
||||
{
|
||||
"name": "x64-windows",
|
||||
"configurePreset": "x64-windows"
|
||||
},
|
||||
{
|
||||
"name": "arm64-windows",
|
||||
"configurePreset": "arm64-windows"
|
||||
},
|
||||
{
|
||||
"name": "wasm",
|
||||
"configurePreset": "wasm"
|
||||
}
|
||||
]
|
||||
}
|
||||
37
Docs/BUILDING.md
Normal file
37
Docs/BUILDING.md
Normal file
@ -0,0 +1,37 @@
|
||||
## 🛠️ Building IACore
|
||||
|
||||
IACore uses **CMake Presets** to manage toolchains and cross-compilation. This ensures that the correct compilers (Clang) and flags (AVX2/SIMD) are used automatically.
|
||||
|
||||
### Prerequisites
|
||||
* CMake 3.28+
|
||||
* Ninja Build System
|
||||
* Vcpkg (Environment variable `VCPKG_ROOT` must be set)
|
||||
* Clang / Clang-CL
|
||||
|
||||
### Build Instructions
|
||||
|
||||
**1. Configure**
|
||||
Select the preset for your target platform.
|
||||
```bash
|
||||
# List available presets
|
||||
cmake --list-presets
|
||||
|
||||
# Configure for your platform (e.g., windows-x64, linux-arm64, wasm)
|
||||
cmake --preset windows-x64
|
||||
```
|
||||
|
||||
**2. Build**
|
||||
|
||||
```bash
|
||||
cmake --build --preset windows-x64
|
||||
```
|
||||
|
||||
### Available Presets
|
||||
|
||||
|Preset |Description |Toolchain |
|
||||
|-------------|------------------------------|------------------------------------|
|
||||
|windows-x64 |Windows (Clang-CL) |CMake/Toolchains/windows-x64.cmake |
|
||||
|linux-x64 |Linux (Clang) |CMake/Toolchains/linux-x64.cmake |
|
||||
|wasm |WebAssembly (Emscripten) |CMake/Toolchains/wasm.cmake |
|
||||
|windows-arm64|Windows on ARM (Cross-compile)|CMake/Toolchains/windows-arm64.cmake|
|
||||
|linux-arm64 |Linux on ARM (Cross-compile) |CMake/Toolchains/linux-arm64.cmake |
|
||||
27
Docs/USING.md
Normal file
27
Docs/USING.md
Normal file
@ -0,0 +1,27 @@
|
||||
## 🚀 Using IACore in a New Project
|
||||
|
||||
IACore provides a CMake macro `iacore_setup_project()`, which standardizes your build environment. This macro automatically:
|
||||
|
||||
* Enforces C++20 standard.
|
||||
* Sets warning levels (-Wall -Wextra -Wpedantic for Clang/GCC, /W4 for MSVC/Clang-CL).
|
||||
* Detects the target architecture (x64, ARM64, WASM) and sets internal cache variables.
|
||||
* Suppresses C++98 compatibility warnings when using Clang on Windows.
|
||||
|
||||
Example CMakeLists.txt
|
||||
```cmake
|
||||
cmake_minimum_required(VERSION 3.28)
|
||||
project(MyGame)
|
||||
|
||||
# Or you can use FetchContent
|
||||
add_subdirectory(external/IACore)
|
||||
|
||||
## Apply IACore's standard project configuration
|
||||
# This applies C++20 and strict warning flags globally to your targets.
|
||||
iacore_setup_project()
|
||||
|
||||
# Define your target(s)
|
||||
add_executable(MyGame src/main.cpp)
|
||||
|
||||
# Link IACore
|
||||
target_link_libraries(MyGame PRIVATE IACore)
|
||||
```
|
||||
177
LICENSE
Normal file
177
LICENSE
Normal file
@ -0,0 +1,177 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
182
README.md
Normal file
182
README.md
Normal file
@ -0,0 +1,182 @@
|
||||
# IACore (Independent Architecture Core)
|
||||
|
||||
<div align="center">
|
||||
<img src="logo.svg" alt="IACore Logo" width="400"/>
|
||||
<br/>
|
||||
|
||||
<img src="https://img.shields.io/badge/license-apache_v2-blue.svg" alt="License"/>
|
||||
<img src="https://img.shields.io/badge/standard-C%2B%2B20-yellow.svg" alt="C++ Standard"/>
|
||||
<img src="https://img.shields.io/badge/platform-Windows%20%7C%20Linux-lightgrey.svg" alt="Platform"/>
|
||||
|
||||
<p>
|
||||
<b>A High-Performance Foundation for Modern C++ 20 Applications.</b>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
> [!NOTE]
|
||||
> This branch presents the latest version of IACore (v1.2), which is a major rewrite of the previous API. If you're looking for the previous API instead, please find it in the `_legay_api` branch!
|
||||
|
||||
## 📖 Description
|
||||
|
||||
IACore is a high-performance C++20 foundation library bundling essential systems (IPC, Logging, Networking, Compression, and Async Scheduling) into a single, coherent API.
|
||||
|
||||
IACore strictly follows the coding style and philosophy of [Oxide](https://github.com/I-A-S/Oxide).
|
||||
|
||||
Originally developed as the internal core for IASoft (PVT) LTD., it is now open-source to provide a standardized bedrock for C++ applications where performance matters.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
* **🚀 High-Performance IPC:** Shared-Memory Ring Buffers with wait-free SPSC synchronization.
|
||||
* **🌐 Networking:** Integrated HTTP/HTTPS client (wrapper around `cpp-httplib` with automatic Zlib/Gzip handling).
|
||||
* **🧵 Async Scheduler:** A job system with high/normal priority queues.
|
||||
* **💾 File I/O:** Memory-mapped file operations and optimized binary stream readers/writers.
|
||||
* **📦 Compression:** Unified API for Zlib, Gzip, and Zstd.
|
||||
* **📜 Logging:** Thread-safe, colored console and disk logging.
|
||||
* **⚡ Modern C++:** Heavily utilizes modern C++20 concepts, `std::span`, and `Oxide` for error handling.
|
||||
|
||||
## 💡 Usage Examples
|
||||
### 1. IPC (Manager & Node)
|
||||
IACore provides a manager/node architecture using shared memory.
|
||||
|
||||
#### Manager:
|
||||
```C++
|
||||
#include <IACore/IPC.hpp>
|
||||
|
||||
// Spawns a child process and connects via Shared Memory
|
||||
auto nodeID = manager.SpawnNode("MyChildNodeExe");
|
||||
manager.WaitTillNodeIsOnline(*nodeID);
|
||||
|
||||
// Send data with zero-copy overhead
|
||||
String msg = "Hello Node";
|
||||
manager.SendPacket(*nodeID, 100, {(PCUINT8)msg.data(), msg.size()});
|
||||
```
|
||||
|
||||
#### Node:
|
||||
```C++
|
||||
#include <IACore/IPC.hpp>
|
||||
|
||||
class Node : public IACore::IPC_Node {
|
||||
public:
|
||||
void OnSignal(uint8_t signal) override {
|
||||
// Handle signals
|
||||
}
|
||||
|
||||
void OnPacket(uint16_t packetID, std::span<const uint8_t> payload) override {
|
||||
// Handle packets
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
// The connection string is passed as the first argument by the Manager
|
||||
if (argc < 2) return -1;
|
||||
|
||||
Node node;
|
||||
// Connect back to the manager via Shared Memory
|
||||
if (!node.Connect(argv[1])) return -1;
|
||||
|
||||
while(true) {
|
||||
node.Update();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Async Jobs
|
||||
```C++
|
||||
|
||||
#include <IACore/AsyncOps.hpp>
|
||||
|
||||
// Initialize worker threads (hardware_concurrency - 2)
|
||||
IACore::AsyncOps::InitializeScheduler();
|
||||
|
||||
// Schedule a task
|
||||
IACore::AsyncOps::Schedule *mySchedule = new IACore::AsyncOps::Schedule();
|
||||
|
||||
IACore::AsyncOps::ScheduleTask([](auto workerID) {
|
||||
printf("Running on worker %d\n", workerID);
|
||||
}, 0, mySchedule);
|
||||
|
||||
// Wait for completion
|
||||
IACore::AsyncOps::WaitForScheduleCompletion(mySchedule);
|
||||
```
|
||||
|
||||
### 3. HTTP Request
|
||||
```C++
|
||||
#include <IACore/HttpClient.hpp>
|
||||
|
||||
IACore::HttpClient client("https://api.example.com");
|
||||
auto res = client.JsonGet<MyResponseStruct>("/data", {});
|
||||
|
||||
if (res) {
|
||||
std::cout << "Data: " << res->value << "\n";
|
||||
} else {
|
||||
std::cerr << "Error: " << res.error() << "\n";
|
||||
}
|
||||
```
|
||||
|
||||
## 🛠️ Integration
|
||||
|
||||
IACore is built with CMake. You can include it in your project via `FetchContent` or by adding it as a subdirectory.
|
||||
|
||||
**Note:** On Windows, you must have **VCPKG** installed and the `VCPKG_ROOT` environment variable set, for OpenSSL support.
|
||||
|
||||
### CMake Example
|
||||
```cmake
|
||||
cmake_minimum_required(VERSION 3.28)
|
||||
project(MyGame)
|
||||
|
||||
# Or you can use FetchContent
|
||||
add_subdirectory(external/IACore)
|
||||
|
||||
# Apply IACore's standard project configuration
|
||||
# This applies C++20 and strict warning flags globally to your targets.
|
||||
iacore_setup_project()
|
||||
|
||||
# Define your targets
|
||||
add_executable(MyGame src/main.cpp)
|
||||
|
||||
# Link IACore
|
||||
target_link_libraries(MyGame PRIVATE IACore)
|
||||
```
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome contributions from the community!
|
||||
|
||||
### What we accept immediately:
|
||||
* **📚 Documentation:** Improvements to comments, the README, or external docs.
|
||||
* **🧪 Tests:** New unit tests (in `Tests/`) to improve coverage or reproduce bugs.
|
||||
* **💡 Examples:** New usage examples or sample projects.
|
||||
* **🐛 Bug Reports:** detailed issues describing reproduction steps are highly valued.
|
||||
|
||||
### Core Library Policy (`Src/` Directory)
|
||||
Currently, **we are not accepting Pull Requests that modify the core source code (`Src/`)**.
|
||||
|
||||
If you find a critical bug in `Src/`, please open an **Issue** rather than a PR, and the core team will implement a fix ASAP.
|
||||
|
||||
## 📦 Dependencies
|
||||
IACore manages its own dependencies via CMake's FetchContent. You do not need to install these manually:
|
||||
|
||||
* **JSON:** `glaze`
|
||||
* **SIMD:** `google-highway`
|
||||
* **Networking:** `cpp-httplib`
|
||||
* **Compression:** `zlib-ng` & `zstd`
|
||||
* **Utilities:** `tl-expected` & `unordered_dense`
|
||||
|
||||
**Note:** Following dependencies are not directly used by IACore, but bundles them (+ helper wrappers) for user convenience: `nlohmann_json`, `simdjson`, `pugixml`
|
||||
|
||||
## ⚖️ License
|
||||
|
||||
This project is licensed under the Apache License Version 2.0.
|
||||
|
||||
## 🤖 Use of Generative AI at IASoft
|
||||
|
||||
While we never let Generative AI to make architecural/design decisions, and we hand-write almost all of the implementations, we do use Generative AI (Google Gemini) for the following and *(ONLY following)* tasks:
|
||||
|
||||
1) **Controlled Repititive Boilerplate Generation:** Each and **every single line of AI generated code** is **manually reviewed** one-by-one for hallucinations and logic errors. Trust, but verify.
|
||||
|
||||
2) **Concept Research and Development:** Design pattern comparisions, cost-benefit analysis and as a second pair of eyes to evalute and critique our design decisions.
|
||||
|
||||
3) **Documentation:** Repititive method doc strings (parameter, return value descriptions) are done mostly using Generative AI.
|
||||
|
||||
4) **Code Review:** Automated logic checking and static analysis on top of deterministic tools.
|
||||
2
Src/CMakeLists.txt
Normal file
2
Src/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
add_subdirectory(IACore/)
|
||||
97
Src/IACore/CMakeLists.txt
Normal file
97
Src/IACore/CMakeLists.txt
Normal file
@ -0,0 +1,97 @@
|
||||
set(SRC_FILES
|
||||
"imp/cpp/CLI.cpp"
|
||||
"imp/cpp/IPC.cpp"
|
||||
"imp/cpp/XML.cpp"
|
||||
"imp/cpp/SIMD.cpp"
|
||||
"imp/cpp/JSON.cpp"
|
||||
"imp/cpp/IACore.cpp"
|
||||
"imp/cpp/Logger.cpp"
|
||||
"imp/cpp/Utils.cpp"
|
||||
"imp/cpp/FileOps.cpp"
|
||||
"imp/cpp/DataOps.cpp"
|
||||
"imp/cpp/AsyncOps.cpp"
|
||||
"imp/cpp/Platform.cpp"
|
||||
"imp/cpp/SocketOps.cpp"
|
||||
"imp/cpp/StringOps.cpp"
|
||||
"imp/cpp/ProcessOps.cpp"
|
||||
"imp/cpp/StreamReader.cpp"
|
||||
"imp/cpp/StreamWriter.cpp"
|
||||
|
||||
"imp/cpp/Http/Common.cpp"
|
||||
"imp/cpp/Http/Client.cpp"
|
||||
"imp/cpp/Http/Server.cpp"
|
||||
)
|
||||
|
||||
add_library(IACore STATIC ${SRC_FILES})
|
||||
|
||||
target_include_directories(IACore PUBLIC inc/)
|
||||
target_include_directories(IACore PRIVATE imp/hpp/)
|
||||
|
||||
target_link_libraries(IACore PUBLIC
|
||||
hwy
|
||||
zlib
|
||||
Oxide
|
||||
zstd::libzstd
|
||||
glaze::glaze
|
||||
httplib::httplib
|
||||
pugixml::pugixml
|
||||
simdjson::simdjson
|
||||
nlohmann_json::nlohmann_json
|
||||
unordered_dense::unordered_dense
|
||||
)
|
||||
|
||||
target_link_libraries(IACore PRIVATE
|
||||
OpenSSL::SSL
|
||||
OpenSSL::Crypto
|
||||
)
|
||||
|
||||
target_link_libraries(IACore PUBLIC mimalloc-static)
|
||||
|
||||
if(WIN32)
|
||||
if(MSVC)
|
||||
target_link_options(IACore PUBLIC "/INCLUDE:mi_version")
|
||||
else()
|
||||
target_link_options(IACore PUBLIC "")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_precompile_headers(IACore PUBLIC inc/IACore/PCH.hpp)
|
||||
|
||||
set(NO_EXCEPT_FLAG "$<IF:$<CXX_COMPILER_ID:MSVC>,/EHs-c-,-fno-exceptions>")
|
||||
target_compile_options(IACore PRIVATE ${NO_EXCEPT_FLAG})
|
||||
target_compile_options(IACore INTERFACE
|
||||
$<$<NOT:$<BOOL:$<TARGET_PROPERTY:USE_EXCEPTIONS>>>:${NO_EXCEPT_FLAG}>
|
||||
)
|
||||
|
||||
define_property(TARGET PROPERTY USE_EXCEPTIONS
|
||||
BRIEF_DOCS "If ON, this target is allowed to use C++ exceptions."
|
||||
FULL_DOCS "Prevents IACore from propagating -fno-exceptions to this target."
|
||||
)
|
||||
|
||||
target_compile_definitions(IACore PRIVATE
|
||||
CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
CPPHTTPLIB_ZLIB_SUPPORT
|
||||
NOMINMAX
|
||||
)
|
||||
|
||||
target_compile_definitions(IACore PUBLIC
|
||||
$<$<CONFIG:Debug>:__IA_DEBUG=1>
|
||||
$<$<CONFIG:Release>:__IA_DEBUG=0>
|
||||
)
|
||||
|
||||
if(IACORE_ARCH_X64)
|
||||
if(MSVC)
|
||||
target_compile_options(IACore INTERFACE /arch:AVX2)
|
||||
else()
|
||||
target_compile_options(IACore INTERFACE -mavx2 -mfma -mpclmul -maes -mbmi -mbmi2 -mf16c)
|
||||
endif()
|
||||
target_compile_definitions(IACore INTERFACE HWY_BASELINE_TARGETS=HWY_AVX2)
|
||||
elseif(IACORE_ARCH_ARM64)
|
||||
if(NOT MSVC)
|
||||
target_compile_options(IACore INTERFACE -march=armv8-a+simd)
|
||||
endif()
|
||||
target_compile_definitions(IACore INTERFACE HWY_BASELINE_TARGETS=HWY_NEON)
|
||||
elseif(IACORE_ARCH_WASM)
|
||||
target_compile_options(IACore INTERFACE -msimd128)
|
||||
target_compile_definitions(IACore INTERFACE HWY_BASELINE_TARGETS=HWY_WASM)
|
||||
endif()
|
||||
200
Src/IACore/imp/cpp/AsyncOps.cpp
Normal file
200
Src/IACore/imp/cpp/AsyncOps.cpp
Normal file
@ -0,0 +1,200 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/AsyncOps.hpp>
|
||||
|
||||
namespace IACore {
|
||||
Mut<std::mutex> AsyncOps::s_queue_mutex;
|
||||
Mut<std::condition_variable> AsyncOps::s_wake_condition;
|
||||
Mut<Vec<std::jthread>> AsyncOps::s_schedule_workers;
|
||||
Mut<std::deque<AsyncOps::ScheduledTask>> AsyncOps::s_high_priority_queue;
|
||||
Mut<std::deque<AsyncOps::ScheduledTask>> AsyncOps::s_normal_priority_queue;
|
||||
|
||||
auto AsyncOps::run_task(Mut<std::function<void()>> task) -> void {
|
||||
std::jthread(std::move(task)).detach();
|
||||
}
|
||||
|
||||
auto AsyncOps::initialize_scheduler(Mut<u8> worker_count) -> Result<void> {
|
||||
if (worker_count == 0) {
|
||||
Const<u32> hw_concurrency = std::thread::hardware_concurrency();
|
||||
Mut<u32> threads = 2;
|
||||
if (hw_concurrency > 2) {
|
||||
threads = hw_concurrency - 2;
|
||||
}
|
||||
|
||||
if (threads > 255) {
|
||||
threads = 255;
|
||||
}
|
||||
worker_count = static_cast<u8>(threads);
|
||||
}
|
||||
|
||||
for (Mut<u32> i = 0; i < worker_count; ++i) {
|
||||
s_schedule_workers.emplace_back(schedule_worker_loop,
|
||||
static_cast<WorkerId>(i + 1));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto AsyncOps::terminate_scheduler() -> void {
|
||||
for (MutRef<std::jthread> worker : s_schedule_workers) {
|
||||
worker.request_stop();
|
||||
}
|
||||
|
||||
s_wake_condition.notify_all();
|
||||
|
||||
for (MutRef<std::jthread> worker : s_schedule_workers) {
|
||||
if (worker.joinable()) {
|
||||
worker.join();
|
||||
}
|
||||
}
|
||||
|
||||
s_schedule_workers.clear();
|
||||
}
|
||||
|
||||
auto AsyncOps::schedule_task(Mut<std::function<void(WorkerId worker_id)>> task,
|
||||
Const<TaskTag> tag, Const<Schedule *> schedule,
|
||||
Const<Priority> priority) -> void {
|
||||
ensure(!s_schedule_workers.empty(),
|
||||
"Scheduler must be initialized before calling schedule_task");
|
||||
|
||||
schedule->counter.fetch_add(1);
|
||||
{
|
||||
Const<std::lock_guard<std::mutex>> lock(s_queue_mutex);
|
||||
if (priority == Priority::High) {
|
||||
s_high_priority_queue.emplace_back(
|
||||
ScheduledTask{tag, schedule, std::move(task)});
|
||||
} else {
|
||||
s_normal_priority_queue.emplace_back(
|
||||
ScheduledTask{tag, schedule, std::move(task)});
|
||||
}
|
||||
}
|
||||
s_wake_condition.notify_one();
|
||||
}
|
||||
|
||||
auto AsyncOps::cancel_tasks_of_tag(Const<TaskTag> tag) -> void {
|
||||
Const<std::lock_guard<std::mutex>> lock(s_queue_mutex);
|
||||
|
||||
{
|
||||
MutRef<std::deque<ScheduledTask>> queue = s_high_priority_queue;
|
||||
for (Mut<std::deque<ScheduledTask>::iterator> it = queue.begin();
|
||||
it != queue.end();
|
||||
/* no incr */) {
|
||||
if (it->tag == tag) {
|
||||
if (it->schedule_handle->counter.fetch_sub(1) == 1) {
|
||||
it->schedule_handle->counter.notify_all();
|
||||
}
|
||||
it = queue.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
MutRef<std::deque<ScheduledTask>> queue = s_normal_priority_queue;
|
||||
for (Mut<std::deque<ScheduledTask>::iterator> it = queue.begin();
|
||||
it != queue.end();
|
||||
/* no incr */) {
|
||||
if (it->tag == tag) {
|
||||
if (it->schedule_handle->counter.fetch_sub(1) == 1) {
|
||||
it->schedule_handle->counter.notify_all();
|
||||
}
|
||||
it = queue.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto AsyncOps::wait_for_schedule_completion(Const<Schedule *> schedule)
|
||||
-> void {
|
||||
ensure(!s_schedule_workers.empty(), "Scheduler must be initialized before "
|
||||
"calling wait_for_schedule_completion");
|
||||
|
||||
while (schedule->counter.load() > 0) {
|
||||
Mut<ScheduledTask> task;
|
||||
Mut<bool> found_task = false;
|
||||
{
|
||||
Mut<std::unique_lock<std::mutex>> lock(s_queue_mutex);
|
||||
if (!s_high_priority_queue.empty()) {
|
||||
task = std::move(s_high_priority_queue.front());
|
||||
s_high_priority_queue.pop_front();
|
||||
found_task = true;
|
||||
} else if (!s_normal_priority_queue.empty()) {
|
||||
task = std::move(s_normal_priority_queue.front());
|
||||
s_normal_priority_queue.pop_front();
|
||||
found_task = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_task) {
|
||||
task.task(MAIN_THREAD_WORKER_ID);
|
||||
if (task.schedule_handle->counter.fetch_sub(1) == 1) {
|
||||
task.schedule_handle->counter.notify_all();
|
||||
}
|
||||
} else {
|
||||
Const<u32> current_val = schedule->counter.load();
|
||||
if (current_val > 0) {
|
||||
schedule->counter.wait(current_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto AsyncOps::get_worker_count() -> WorkerId {
|
||||
return static_cast<WorkerId>(s_schedule_workers.size());
|
||||
}
|
||||
|
||||
auto AsyncOps::schedule_worker_loop(Const<std::stop_token> stop_token,
|
||||
Const<WorkerId> worker_id) -> void {
|
||||
while (!stop_token.stop_requested()) {
|
||||
Mut<ScheduledTask> task;
|
||||
Mut<bool> found_task = false;
|
||||
{
|
||||
Mut<std::unique_lock<std::mutex>> lock(s_queue_mutex);
|
||||
|
||||
s_wake_condition.wait(lock, [&stop_token] {
|
||||
return !s_high_priority_queue.empty() ||
|
||||
!s_normal_priority_queue.empty() || stop_token.stop_requested();
|
||||
});
|
||||
|
||||
if (stop_token.stop_requested() && s_high_priority_queue.empty() &&
|
||||
s_normal_priority_queue.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s_high_priority_queue.empty()) {
|
||||
task = std::move(s_high_priority_queue.front());
|
||||
s_high_priority_queue.pop_front();
|
||||
found_task = true;
|
||||
} else if (!s_normal_priority_queue.empty()) {
|
||||
task = std::move(s_normal_priority_queue.front());
|
||||
s_normal_priority_queue.pop_front();
|
||||
found_task = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_task) {
|
||||
task.task(worker_id);
|
||||
if (task.schedule_handle->counter.fetch_sub(1) == 1) {
|
||||
task.schedule_handle->counter.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
27
Src/IACore/imp/cpp/CLI.cpp
Normal file
27
Src/IACore/imp/cpp/CLI.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/CLI.hpp>
|
||||
|
||||
namespace IACore {
|
||||
CLIParser::CLIParser(Const<Span<Const<String>>> args) : m_arg_list(args) {
|
||||
m_current_arg = m_arg_list.begin();
|
||||
|
||||
// Skip executable path
|
||||
if (m_current_arg != m_arg_list.end()) {
|
||||
m_current_arg++;
|
||||
}
|
||||
}
|
||||
} // namespace IACore
|
||||
452
Src/IACore/imp/cpp/DataOps.cpp
Normal file
452
Src/IACore/imp/cpp/DataOps.cpp
Normal file
@ -0,0 +1,452 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/DataOps.hpp>
|
||||
#include <IACore/Platform.hpp>
|
||||
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
#if IA_ARCH_X64
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
#if IA_ARCH_ARM64
|
||||
#include <arm_acle.h>
|
||||
#endif
|
||||
|
||||
namespace IACore {
|
||||
template <typename T>
|
||||
[[nodiscard]] inline auto read_unaligned(Const<Const<u8> *> ptr) -> T {
|
||||
Mut<T> v;
|
||||
std::memcpy(&v, ptr, sizeof(T));
|
||||
return v;
|
||||
}
|
||||
|
||||
struct Crc32Tables {
|
||||
Mut<u32> table[8][256] = {};
|
||||
|
||||
consteval Crc32Tables() {
|
||||
constexpr Const<u32> T = 0x82F63B78;
|
||||
|
||||
for (Mut<u32> i = 0; i < 256; i++) {
|
||||
Mut<u32> crc = i;
|
||||
for (Mut<i32> j = 0; j < 8; j++) {
|
||||
crc = (crc >> 1) ^ ((crc & 1) ? T : 0);
|
||||
}
|
||||
table[0][i] = crc;
|
||||
}
|
||||
|
||||
for (Mut<i32> i = 0; i < 256; i++) {
|
||||
for (Mut<i32> slice = 1; slice < 8; slice++) {
|
||||
Const<u32> prev = table[slice - 1][i];
|
||||
table[slice][i] = (prev >> 8) ^ table[0][prev & 0xFF];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr Const<Crc32Tables> CRC32_TABLES{};
|
||||
|
||||
#if IA_ARCH_X64
|
||||
inline auto crc32_x64_hw(Ref<Span<Const<u8>>> data) -> u32 {
|
||||
Mut<const u8 *> p = data.data();
|
||||
|
||||
Mut<u32> crc = 0xFFFFFFFF;
|
||||
Mut<usize> len = data.size();
|
||||
|
||||
while (len >= 8) {
|
||||
Const<u64> chunk = read_unaligned<u64>(p);
|
||||
crc = static_cast<u32>(_mm_crc32_u64(static_cast<u64>(crc), chunk));
|
||||
p += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
while (len--) {
|
||||
crc = _mm_crc32_u8(crc, *p++);
|
||||
}
|
||||
|
||||
return ~crc;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if IA_ARCH_ARM64
|
||||
__attribute__((target("+crc"))) inline auto
|
||||
crc32_arm64_hw(Ref<Span<Const<u8>>> data) -> u32 {
|
||||
Mut<const u8 *> p = data.data();
|
||||
|
||||
Mut<u32> crc = 0xFFFFFFFF;
|
||||
Mut<usize> len = data.size();
|
||||
|
||||
while (len >= 8) {
|
||||
Const<u64> chunk = read_unaligned<u64>(p);
|
||||
crc = __crc32cd(crc, chunk);
|
||||
p += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
while (len--) {
|
||||
crc = __crc32cb(crc, *p++);
|
||||
}
|
||||
|
||||
return ~crc;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline auto crc32_software_slice8(Ref<Span<Const<u8>>> data) -> u32 {
|
||||
Mut<const u8 *> p = data.data();
|
||||
Mut<u32> crc = 0xFFFFFFFF;
|
||||
Mut<usize> len = data.size();
|
||||
|
||||
while (len >= 8) {
|
||||
Const<u32> term1 = crc ^ read_unaligned<u32>(p);
|
||||
Const<u32> term2 = read_unaligned<u32>(p + 4);
|
||||
|
||||
crc = CRC32_TABLES.table[7][term1 & 0xFF] ^
|
||||
CRC32_TABLES.table[6][(term1 >> 8) & 0xFF] ^
|
||||
CRC32_TABLES.table[5][(term1 >> 16) & 0xFF] ^
|
||||
CRC32_TABLES.table[4][(term1 >> 24)] ^
|
||||
CRC32_TABLES.table[3][term2 & 0xFF] ^
|
||||
CRC32_TABLES.table[2][(term2 >> 8) & 0xFF] ^
|
||||
CRC32_TABLES.table[1][(term2 >> 16) & 0xFF] ^
|
||||
CRC32_TABLES.table[0][(term2 >> 24)];
|
||||
|
||||
p += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
while (len--) {
|
||||
crc = (crc >> 8) ^ CRC32_TABLES.table[0][(crc ^ *p++) & 0xFF];
|
||||
}
|
||||
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
auto DataOps::crc32(Ref<Span<Const<u8>>> data) -> u32 {
|
||||
#if IA_ARCH_X64
|
||||
// IACore mandates AVX2 so no need to check
|
||||
return crc32_x64_hw(data);
|
||||
#elif IA_ARCH_ARM64
|
||||
if (Platform::GetCapabilities().HardwareCRC32) {
|
||||
return crc32_arm64_hw(data);
|
||||
}
|
||||
#endif
|
||||
return crc32_software_slice8(data);
|
||||
}
|
||||
|
||||
constexpr Const<u32> XXH_PRIME32_1 = 0x9E3779B1U;
|
||||
constexpr Const<u32> XXH_PRIME32_2 = 0x85EBCA77U;
|
||||
constexpr Const<u32> XXH_PRIME32_3 = 0xC2B2AE3DU;
|
||||
constexpr Const<u32> XXH_PRIME32_4 = 0x27D4EB2FU;
|
||||
constexpr Const<u32> XXH_PRIME32_5 = 0x165667B1U;
|
||||
|
||||
inline auto xxh32_round(Mut<u32> seed, Const<u32> input) -> u32 {
|
||||
seed += input * XXH_PRIME32_2;
|
||||
seed = std::rotl(seed, 13);
|
||||
seed *= XXH_PRIME32_1;
|
||||
return seed;
|
||||
}
|
||||
|
||||
auto DataOps::hash_xxhash(Ref<String> string, Const<u32> seed) -> u32 {
|
||||
return hash_xxhash(
|
||||
Span<Const<u8>>(reinterpret_cast<const u8 *>(string.data()),
|
||||
string.length()),
|
||||
seed);
|
||||
}
|
||||
|
||||
auto DataOps::hash_xxhash(Ref<Span<Const<u8>>> data, Const<u32> seed) -> u32 {
|
||||
Mut<const u8 *> p = data.data();
|
||||
Const<const u8 *> b_end = p + data.size();
|
||||
Mut<u32> h32{};
|
||||
|
||||
if (data.size() >= 16) {
|
||||
Const<const u8 *> limit = b_end - 16;
|
||||
|
||||
Mut<u32> v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
|
||||
Mut<u32> v2 = seed + XXH_PRIME32_2;
|
||||
Mut<u32> v3 = seed + 0;
|
||||
Mut<u32> v4 = seed - XXH_PRIME32_1;
|
||||
|
||||
do {
|
||||
v1 = xxh32_round(v1, read_unaligned<u32>(p));
|
||||
p += 4;
|
||||
v2 = xxh32_round(v2, read_unaligned<u32>(p));
|
||||
p += 4;
|
||||
v3 = xxh32_round(v3, read_unaligned<u32>(p));
|
||||
p += 4;
|
||||
v4 = xxh32_round(v4, read_unaligned<u32>(p));
|
||||
p += 4;
|
||||
} while (p <= limit);
|
||||
|
||||
h32 = std::rotl(v1, 1) + std::rotl(v2, 7) + std::rotl(v3, 12) +
|
||||
std::rotl(v4, 18);
|
||||
} else {
|
||||
h32 = seed + XXH_PRIME32_5;
|
||||
}
|
||||
|
||||
h32 += static_cast<u32>(data.size());
|
||||
|
||||
while (p + 4 <= b_end) {
|
||||
Const<u32> t = read_unaligned<u32>(p) * XXH_PRIME32_3;
|
||||
h32 += t;
|
||||
h32 = std::rotl(h32, 17) * XXH_PRIME32_4;
|
||||
p += 4;
|
||||
}
|
||||
|
||||
while (p < b_end) {
|
||||
h32 += (*p++) * XXH_PRIME32_5;
|
||||
h32 = std::rotl(h32, 11) * XXH_PRIME32_1;
|
||||
}
|
||||
|
||||
h32 ^= h32 >> 15;
|
||||
h32 *= XXH_PRIME32_2;
|
||||
h32 ^= h32 >> 13;
|
||||
h32 *= XXH_PRIME32_3;
|
||||
h32 ^= h32 >> 16;
|
||||
|
||||
return h32;
|
||||
}
|
||||
|
||||
constexpr Const<u32> FNV1A_32_PRIME = 0x01000193;
|
||||
constexpr Const<u32> FNV1A_32_OFFSET = 0x811c9dc5;
|
||||
|
||||
auto DataOps::hash_fnv1a(Ref<String> string) -> u32 {
|
||||
Mut<u32> hash = FNV1A_32_OFFSET;
|
||||
for (Const<char> c : string) {
|
||||
hash ^= static_cast<u8>(c);
|
||||
hash *= FNV1A_32_PRIME;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
auto DataOps::hash_fnv1a(Ref<Span<Const<u8>>> data) -> u32 {
|
||||
Mut<u32> hash = FNV1A_32_OFFSET;
|
||||
Const<const u8 *> ptr = data.data();
|
||||
|
||||
for (Mut<usize> i = 0; i < data.size(); ++i) {
|
||||
hash ^= ptr[i];
|
||||
hash *= FNV1A_32_PRIME;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
auto DataOps::detect_compression(Const<Span<Const<u8>>> data)
|
||||
-> CompressionType {
|
||||
if (data.size() < 2) {
|
||||
return CompressionType::None;
|
||||
}
|
||||
|
||||
if (data[0] == 0x1F && data[1] == 0x8B) {
|
||||
return CompressionType::Gzip;
|
||||
}
|
||||
|
||||
if (data[0] == 0x78 &&
|
||||
(data[1] == 0x01 || data[1] == 0x9C || data[1] == 0xDA)) {
|
||||
return CompressionType::Zlib;
|
||||
}
|
||||
|
||||
return CompressionType::None;
|
||||
}
|
||||
|
||||
auto DataOps::zlib_inflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
||||
Mut<z_stream> zs{};
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
|
||||
if (inflateInit2(&zs, 15 + 32) != Z_OK) {
|
||||
return fail("Failed to initialize zlib inflate");
|
||||
}
|
||||
|
||||
zs.next_in = const_cast<Bytef *>(data.data());
|
||||
zs.avail_in = static_cast<uInt>(data.size());
|
||||
|
||||
Mut<Vec<u8>> out_buffer;
|
||||
Const<usize> guess_size =
|
||||
data.size() < 1024 ? data.size() * 4 : data.size() * 2;
|
||||
out_buffer.resize(guess_size);
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
||||
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
||||
|
||||
Mut<int> ret;
|
||||
do {
|
||||
if (zs.avail_out == 0) {
|
||||
Const<usize> current_pos = zs.total_out;
|
||||
Const<usize> new_size = out_buffer.size() * 2;
|
||||
out_buffer.resize(new_size);
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data() + current_pos);
|
||||
zs.avail_out = static_cast<uInt>(new_size - current_pos);
|
||||
}
|
||||
|
||||
ret = inflate(&zs, Z_NO_FLUSH);
|
||||
|
||||
} while (ret == Z_OK);
|
||||
|
||||
inflateEnd(&zs);
|
||||
|
||||
if (ret != Z_STREAM_END) {
|
||||
return fail("Failed to inflate: corrupt data or stream error");
|
||||
}
|
||||
|
||||
out_buffer.resize(zs.total_out);
|
||||
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
auto DataOps::zlib_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
||||
Mut<z_stream> zs{};
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
|
||||
if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK) {
|
||||
return fail("Failed to initialize zlib deflate");
|
||||
}
|
||||
|
||||
zs.next_in = const_cast<Bytef *>(data.data());
|
||||
zs.avail_in = static_cast<uInt>(data.size());
|
||||
|
||||
Mut<Vec<u8>> out_buffer;
|
||||
out_buffer.resize(deflateBound(&zs, static_cast<uLong>(data.size())));
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
||||
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
||||
|
||||
Const<int> ret = deflate(&zs, Z_FINISH);
|
||||
|
||||
if (ret != Z_STREAM_END) {
|
||||
deflateEnd(&zs);
|
||||
return fail("Failed to deflate, ran out of buffer memory");
|
||||
}
|
||||
|
||||
out_buffer.resize(zs.total_out);
|
||||
|
||||
deflateEnd(&zs);
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
auto DataOps::zstd_inflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
||||
Const<unsigned long long> content_size =
|
||||
ZSTD_getFrameContentSize(data.data(), data.size());
|
||||
|
||||
if (content_size == ZSTD_CONTENTSIZE_ERROR) {
|
||||
return fail("Failed to inflate: Not valid ZSTD compressed data");
|
||||
}
|
||||
|
||||
if (content_size != ZSTD_CONTENTSIZE_UNKNOWN) {
|
||||
Mut<Vec<u8>> out_buffer;
|
||||
out_buffer.resize(static_cast<usize>(content_size));
|
||||
|
||||
Const<usize> d_size = ZSTD_decompress(out_buffer.data(), out_buffer.size(),
|
||||
data.data(), data.size());
|
||||
|
||||
if (ZSTD_isError(d_size)) {
|
||||
return fail("Failed to inflate: {}", ZSTD_getErrorName(d_size));
|
||||
}
|
||||
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
Mut<ZSTD_DCtx *> dctx = ZSTD_createDCtx();
|
||||
Mut<Vec<u8>> out_buffer;
|
||||
out_buffer.resize(data.size() * 2);
|
||||
|
||||
Mut<ZSTD_inBuffer> input = {data.data(), data.size(), 0};
|
||||
Mut<ZSTD_outBuffer> output = {out_buffer.data(), out_buffer.size(), 0};
|
||||
|
||||
Mut<usize> ret;
|
||||
do {
|
||||
ret = ZSTD_decompressStream(dctx, &output, &input);
|
||||
|
||||
if (ZSTD_isError(ret)) {
|
||||
ZSTD_freeDCtx(dctx);
|
||||
return fail("Failed to inflate: {}", ZSTD_getErrorName(ret));
|
||||
}
|
||||
|
||||
if (output.pos == output.size) {
|
||||
Const<usize> new_size = out_buffer.size() * 2;
|
||||
out_buffer.resize(new_size);
|
||||
output.dst = out_buffer.data();
|
||||
output.size = new_size;
|
||||
}
|
||||
|
||||
} while (ret != 0);
|
||||
|
||||
out_buffer.resize(output.pos);
|
||||
ZSTD_freeDCtx(dctx);
|
||||
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
auto DataOps::zstd_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
||||
Const<usize> max_dst_size = ZSTD_compressBound(data.size());
|
||||
|
||||
Mut<Vec<u8>> out_buffer;
|
||||
out_buffer.resize(max_dst_size);
|
||||
|
||||
Const<usize> compressed_size = ZSTD_compress(out_buffer.data(), max_dst_size,
|
||||
data.data(), data.size(), 3);
|
||||
|
||||
if (ZSTD_isError(compressed_size)) {
|
||||
return fail("Failed to deflate: {}", ZSTD_getErrorName(compressed_size));
|
||||
}
|
||||
|
||||
out_buffer.resize(compressed_size);
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
auto DataOps::gzip_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
||||
Mut<z_stream> zs{};
|
||||
zs.zalloc = Z_NULL;
|
||||
zs.zfree = Z_NULL;
|
||||
zs.opaque = Z_NULL;
|
||||
|
||||
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8,
|
||||
Z_DEFAULT_STRATEGY) != Z_OK) {
|
||||
return fail("Failed to initialize gzip deflate");
|
||||
}
|
||||
|
||||
zs.next_in = const_cast<Bytef *>(data.data());
|
||||
zs.avail_in = static_cast<uInt>(data.size());
|
||||
|
||||
Mut<Vec<u8>> out_buffer;
|
||||
|
||||
out_buffer.resize(deflateBound(&zs, static_cast<uLong>(data.size())) + 1024);
|
||||
|
||||
zs.next_out = reinterpret_cast<Bytef *>(out_buffer.data());
|
||||
zs.avail_out = static_cast<uInt>(out_buffer.size());
|
||||
|
||||
Const<int> ret = deflate(&zs, Z_FINISH);
|
||||
|
||||
if (ret != Z_STREAM_END) {
|
||||
deflateEnd(&zs);
|
||||
return fail("Failed to deflate");
|
||||
}
|
||||
|
||||
out_buffer.resize(zs.total_out);
|
||||
|
||||
deflateEnd(&zs);
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
auto DataOps::gzip_inflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>> {
|
||||
return zlib_inflate(data);
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
539
Src/IACore/imp/cpp/FileOps.cpp
Normal file
539
Src/IACore/imp/cpp/FileOps.cpp
Normal file
@ -0,0 +1,539 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/FileOps.hpp>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
|
||||
#if IA_PLATFORM_UNIX
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace IACore {
|
||||
|
||||
Mut<HashMap<const u8 *, std::tuple<void *, void *, void *>>>
|
||||
FileOps::s_mapped_files;
|
||||
|
||||
auto FileOps::unmap_file(Const<Const<u8> *> mapped_ptr) -> void {
|
||||
if (!s_mapped_files.contains(mapped_ptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mut<decltype(s_mapped_files)::iterator> it = s_mapped_files.find(mapped_ptr);
|
||||
Const<std::tuple<void *, void *, void *>> handles = it->second;
|
||||
s_mapped_files.erase(it);
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
::UnmapViewOfFile(std::get<1>(handles));
|
||||
::CloseHandle(static_cast<HANDLE>(std::get<2>(handles)));
|
||||
|
||||
Const<HANDLE> handle = static_cast<HANDLE>(std::get<0>(handles));
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
::CloseHandle(handle);
|
||||
}
|
||||
#elif IA_PLATFORM_UNIX
|
||||
::munmap(std::get<1>(handles), (usize)std::get<2>(handles));
|
||||
Const<i32> fd = (i32)((u64)std::get<0>(handles));
|
||||
if (fd != -1) {
|
||||
::close(fd);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
auto FileOps::map_shared_memory(Ref<String> name, Const<usize> size,
|
||||
Const<bool> is_owner) -> Result<u8 *> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Const<int> wchars_num =
|
||||
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, NULL, 0);
|
||||
Mut<std::wstring> w_name(wchars_num, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, &w_name[0], wchars_num);
|
||||
|
||||
Mut<HANDLE> h_map = NULL;
|
||||
if (is_owner) {
|
||||
h_map = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
|
||||
(DWORD)(size >> 32), (DWORD)(size & 0xFFFFFFFF),
|
||||
w_name.c_str());
|
||||
} else {
|
||||
h_map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, false, w_name.c_str());
|
||||
}
|
||||
|
||||
if (h_map == NULL) {
|
||||
return fail("Failed to {} shared memory '{}'",
|
||||
is_owner ? "owner" : "consumer", name);
|
||||
}
|
||||
|
||||
Mut<u8 *> result =
|
||||
static_cast<u8 *>(MapViewOfFile(h_map, FILE_MAP_ALL_ACCESS, 0, 0, size));
|
||||
if (result == NULL) {
|
||||
CloseHandle(h_map);
|
||||
return fail("Failed to map view of shared memory '{}'", name);
|
||||
}
|
||||
|
||||
s_mapped_files[result] = std::make_tuple((void *)INVALID_HANDLE_VALUE,
|
||||
(void *)result, (void *)h_map);
|
||||
return result;
|
||||
|
||||
#elif IA_PLATFORM_UNIX
|
||||
Mut<int> fd = -1;
|
||||
if (is_owner) {
|
||||
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 fail("Failed to truncate shared memory '{}'", name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fd = shm_open(name.c_str(), O_RDWR, 0666);
|
||||
}
|
||||
|
||||
if (fd == -1) {
|
||||
return fail("Failed to {} shared memory '{}'",
|
||||
is_owner ? "owner" : "consumer", name);
|
||||
}
|
||||
|
||||
Mut<void *> addr =
|
||||
mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (addr == MAP_FAILED) {
|
||||
close(fd);
|
||||
return fail("Failed to mmap shared memory '{}'", name);
|
||||
}
|
||||
|
||||
Mut<u8 *> result = static_cast<u8 *>(addr);
|
||||
|
||||
s_mapped_files[result] =
|
||||
std::make_tuple((void *)((u64)fd), (void *)addr, (void *)size);
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
auto FileOps::unlink_shared_memory(Ref<String> name) -> void {
|
||||
if (name.empty()) {
|
||||
return;
|
||||
}
|
||||
#if IA_PLATFORM_UNIX
|
||||
shm_unlink(name.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
auto FileOps::map_file(Ref<Path> path, MutRef<usize> size)
|
||||
-> Result<const u8 *> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Const<HANDLE> handle = CreateFileA(
|
||||
path.string().c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
|
||||
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
return fail("Failed to open {} for memory mapping", path.string());
|
||||
}
|
||||
|
||||
Mut<LARGE_INTEGER> file_size;
|
||||
if (!GetFileSizeEx(handle, &file_size)) {
|
||||
CloseHandle(handle);
|
||||
return fail("Failed to get size of {} for memory mapping", path.string());
|
||||
}
|
||||
size = static_cast<usize>(file_size.QuadPart);
|
||||
if (size == 0) {
|
||||
CloseHandle(handle);
|
||||
return fail("Failed to get size of {} for memory mapping", path.string());
|
||||
}
|
||||
|
||||
Mut<HANDLE> h_map =
|
||||
CreateFileMappingW(handle, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||
if (h_map == NULL) {
|
||||
CloseHandle(handle);
|
||||
return fail("Failed to memory map {}", path.string());
|
||||
}
|
||||
|
||||
Const<u8> *result =
|
||||
static_cast<const u8 *>(MapViewOfFile(h_map, FILE_MAP_READ, 0, 0, 0));
|
||||
if (result == NULL) {
|
||||
CloseHandle(handle);
|
||||
CloseHandle(h_map);
|
||||
return fail("Failed to memory map {}", path.string());
|
||||
}
|
||||
s_mapped_files[result] = std::make_tuple(
|
||||
(void *)handle, (void *)const_cast<u8 *>(result), (void *)h_map);
|
||||
return result;
|
||||
|
||||
#elif IA_PLATFORM_UNIX
|
||||
Const<int> handle = open(path.string().c_str(), O_RDONLY);
|
||||
if (handle == -1) {
|
||||
return fail("Failed to open {} for memory mapping", path.string());
|
||||
}
|
||||
Mut<struct stat> sb;
|
||||
if (fstat(handle, &sb) == -1) {
|
||||
close(handle);
|
||||
return fail("Failed to get stats of {} for memory mapping", path.string());
|
||||
}
|
||||
size = static_cast<usize>(sb.st_size);
|
||||
if (size == 0) {
|
||||
close(handle);
|
||||
return fail("Failed to get size of {} for memory mapping", path.string());
|
||||
}
|
||||
Mut<void *> addr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, handle, 0);
|
||||
if (addr == MAP_FAILED) {
|
||||
close(handle);
|
||||
return fail("Failed to memory map {}", path.string());
|
||||
}
|
||||
Const<Const<u8> *> result = static_cast<const u8 *>(addr);
|
||||
madvise(addr, size, MADV_SEQUENTIAL);
|
||||
s_mapped_files[result] =
|
||||
std::make_tuple((void *)((u64)handle), (void *)addr, (void *)size);
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
auto FileOps::stream_to_file(Ref<Path> path, Const<bool> overwrite)
|
||||
-> Result<StreamWriter> {
|
||||
if (!overwrite && std::filesystem::exists(path)) {
|
||||
return fail("File already exists: {}", path.string());
|
||||
}
|
||||
return StreamWriter::create_from_file(path);
|
||||
}
|
||||
|
||||
auto FileOps::stream_from_file(Ref<Path> path) -> Result<StreamReader> {
|
||||
if (!std::filesystem::exists(path)) {
|
||||
return fail("File does not exist: {}", path.string());
|
||||
}
|
||||
return StreamReader::create_from_file(path);
|
||||
}
|
||||
|
||||
auto FileOps::read_text_file(Ref<Path> path) -> Result<String> {
|
||||
Mut<FILE *> f = fopen(path.string().c_str(), "r");
|
||||
if (!f) {
|
||||
return fail("Failed to open file: {}", path.string());
|
||||
}
|
||||
Mut<String> result;
|
||||
fseek(f, 0, SEEK_END);
|
||||
Const<long> len = ftell(f);
|
||||
if (len > 0) {
|
||||
result.resize(static_cast<usize>(len));
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fread(result.data(), 1, result.size(), f);
|
||||
}
|
||||
fclose(f);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto FileOps::read_binary_file(Ref<Path> path) -> Result<Vec<u8>> {
|
||||
Mut<FILE *> f = fopen(path.string().c_str(), "rb");
|
||||
if (!f) {
|
||||
return fail("Failed to open file: {}", path.string());
|
||||
}
|
||||
Mut<Vec<u8>> result;
|
||||
fseek(f, 0, SEEK_END);
|
||||
Const<long> len = ftell(f);
|
||||
if (len > 0) {
|
||||
result.resize(static_cast<usize>(len));
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fread(result.data(), 1, result.size(), f);
|
||||
}
|
||||
fclose(f);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto FileOps::write_text_file(Ref<Path> path, Ref<String> contents,
|
||||
Const<bool> overwrite) -> Result<usize> {
|
||||
Const<Const<char> *> mode = overwrite ? "w" : "wx";
|
||||
Mut<FILE *> f = fopen(path.string().c_str(), mode);
|
||||
if (!f) {
|
||||
if (!overwrite && errno == EEXIST) {
|
||||
return fail("File already exists: {}", path.string());
|
||||
}
|
||||
return fail("Failed to write to file: {}", path.string());
|
||||
}
|
||||
Const<usize> result = fwrite(contents.data(), 1, contents.size(), f);
|
||||
fclose(f);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto FileOps::write_binary_file(Ref<Path> path, Const<Span<Const<u8>>> contents,
|
||||
Const<bool> overwrite) -> Result<usize> {
|
||||
Const<Const<char> *> mode = overwrite ? "w" : "wx";
|
||||
Mut<FILE *> f = fopen(path.string().c_str(), mode);
|
||||
if (!f) {
|
||||
if (!overwrite && errno == EEXIST) {
|
||||
return fail("File already exists: {}", path.string());
|
||||
}
|
||||
return fail("Failed to write to file: {}", path.string());
|
||||
}
|
||||
Const<usize> result = fwrite(contents.data(), 1, contents.size(), f);
|
||||
fclose(f);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto FileOps::normalize_executable_path(Ref<Path> path) -> Path {
|
||||
Mut<Path> 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()) {
|
||||
Mut<String> path_str = result.string();
|
||||
if (!path_str.starts_with("./") && !path_str.starts_with("../")) {
|
||||
result = "./" + path_str;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
auto FileOps::native_open_file(Ref<Path> path, Const<FileAccess> access,
|
||||
Const<FileMode> mode, Const<u32> permissions)
|
||||
-> Result<NativeFileHandle> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Mut<DWORD> dw_access = 0;
|
||||
Mut<DWORD> dw_share = FILE_SHARE_READ;
|
||||
Mut<DWORD> dw_disposition = 0;
|
||||
Mut<DWORD> dw_flags_and_attributes = FILE_ATTRIBUTE_NORMAL;
|
||||
|
||||
switch (access) {
|
||||
case FileAccess::Read:
|
||||
dw_access = GENERIC_READ;
|
||||
break;
|
||||
case FileAccess::Write:
|
||||
dw_access = GENERIC_WRITE;
|
||||
break;
|
||||
case FileAccess::ReadWrite:
|
||||
dw_access = GENERIC_READ | GENERIC_WRITE;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case FileMode::OpenExisting:
|
||||
dw_disposition = OPEN_EXISTING;
|
||||
break;
|
||||
case FileMode::OpenAlways:
|
||||
dw_disposition = OPEN_ALWAYS;
|
||||
break;
|
||||
case FileMode::CreateNew:
|
||||
dw_disposition = CREATE_NEW;
|
||||
break;
|
||||
case FileMode::CreateAlways:
|
||||
dw_disposition = CREATE_ALWAYS;
|
||||
break;
|
||||
case FileMode::TruncateExisting:
|
||||
dw_disposition = TRUNCATE_EXISTING;
|
||||
break;
|
||||
}
|
||||
|
||||
Mut<HANDLE> h_file =
|
||||
CreateFileA(path.string().c_str(), dw_access, dw_share, NULL,
|
||||
dw_disposition, dw_flags_and_attributes, NULL);
|
||||
|
||||
if (h_file == INVALID_HANDLE_VALUE) {
|
||||
return fail("Failed to open file '{}': {}", path.string(), GetLastError());
|
||||
}
|
||||
|
||||
return h_file;
|
||||
|
||||
#elif IA_PLATFORM_UNIX
|
||||
Mut<int> flags = 0;
|
||||
|
||||
switch (access) {
|
||||
case FileAccess::Read:
|
||||
flags = O_RDONLY;
|
||||
break;
|
||||
case FileAccess::Write:
|
||||
flags = O_WRONLY;
|
||||
break;
|
||||
case FileAccess::ReadWrite:
|
||||
flags = O_RDWR;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case FileMode::OpenExisting:
|
||||
break;
|
||||
case FileMode::OpenAlways:
|
||||
flags |= O_CREAT;
|
||||
break;
|
||||
case FileMode::CreateNew:
|
||||
flags |= O_CREAT | O_EXCL;
|
||||
break;
|
||||
case FileMode::CreateAlways:
|
||||
flags |= O_CREAT | O_TRUNC;
|
||||
break;
|
||||
case FileMode::TruncateExisting:
|
||||
flags |= O_TRUNC;
|
||||
break;
|
||||
}
|
||||
|
||||
Mut<int> fd = open(path.string().c_str(), flags, permissions);
|
||||
|
||||
if (fd == -1) {
|
||||
return fail("Failed to open file '{}': {}", path.string(), errno);
|
||||
}
|
||||
|
||||
return fd;
|
||||
#endif
|
||||
}
|
||||
|
||||
auto FileOps::native_close_file(Const<NativeFileHandle> handle) -> void {
|
||||
if (handle == INVALID_FILE_HANDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
CloseHandle(handle);
|
||||
#elif IA_PLATFORM_UNIX
|
||||
close(handle);
|
||||
#endif
|
||||
}
|
||||
|
||||
FileOps::MemoryMappedRegion::~MemoryMappedRegion() { unmap(); }
|
||||
|
||||
FileOps::MemoryMappedRegion::MemoryMappedRegion(
|
||||
ForwardRef<MemoryMappedRegion> other) noexcept {
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
auto FileOps::MemoryMappedRegion::operator=(
|
||||
ForwardRef<MemoryMappedRegion> other) noexcept
|
||||
-> MutRef<MemoryMappedRegion> {
|
||||
if (this != &other) {
|
||||
unmap();
|
||||
m_ptr = other.m_ptr;
|
||||
m_size = other.m_size;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
m_map_handle = other.m_map_handle;
|
||||
other.m_map_handle = NULL;
|
||||
#endif
|
||||
other.m_ptr = nullptr;
|
||||
other.m_size = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto FileOps::MemoryMappedRegion::map(Const<NativeFileHandle> handle,
|
||||
Const<u64> offset, Const<usize> size)
|
||||
-> Result<void> {
|
||||
unmap();
|
||||
|
||||
if (handle == INVALID_FILE_HANDLE) {
|
||||
return fail("Invalid file handle provided to Map");
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
return fail("Cannot map region of size 0");
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Mut<LARGE_INTEGER> file_size;
|
||||
if (!GetFileSizeEx(handle, &file_size)) {
|
||||
return fail("Failed to get file size");
|
||||
}
|
||||
|
||||
Const<u64> end_offset = offset + size;
|
||||
if (static_cast<u64>(file_size.QuadPart) < end_offset) {
|
||||
Mut<LARGE_INTEGER> new_size;
|
||||
new_size.QuadPart = static_cast<LONGLONG>(end_offset);
|
||||
if (!SetFilePointerEx(handle, new_size, NULL, FILE_BEGIN)) {
|
||||
return fail("Failed to seek to new end of file");
|
||||
}
|
||||
|
||||
if (!SetEndOfFile(handle)) {
|
||||
return fail("Failed to extend file for mapping");
|
||||
}
|
||||
}
|
||||
|
||||
m_map_handle = CreateFileMappingW(handle, NULL, PAGE_READWRITE, 0, 0, NULL);
|
||||
if (m_map_handle == NULL) {
|
||||
return fail("CreateFileMapping failed: {}", GetLastError());
|
||||
}
|
||||
|
||||
Const<DWORD> offset_high = static_cast<DWORD>(offset >> 32);
|
||||
Const<DWORD> offset_low = static_cast<DWORD>(offset & 0xFFFFFFFF);
|
||||
|
||||
m_ptr = static_cast<u8 *>(MapViewOfFile(m_map_handle, FILE_MAP_WRITE,
|
||||
offset_high, offset_low, size));
|
||||
if (m_ptr == NULL) {
|
||||
CloseHandle(m_map_handle);
|
||||
m_map_handle = NULL;
|
||||
return fail("MapViewOfFile failed (Offset: {}, Size: {}): {}", offset, size,
|
||||
GetLastError());
|
||||
}
|
||||
m_size = size;
|
||||
|
||||
#elif IA_PLATFORM_UNIX
|
||||
Mut<struct stat> sb;
|
||||
if (fstat(handle, &sb) == -1) {
|
||||
return fail("Failed to fstat file");
|
||||
}
|
||||
|
||||
Const<u64> end_offset = offset + size;
|
||||
if (static_cast<u64>(sb.st_size) < end_offset) {
|
||||
if (ftruncate(handle, static_cast<off_t>(end_offset)) == -1) {
|
||||
return fail("Failed to ftruncate (extend) file");
|
||||
}
|
||||
}
|
||||
|
||||
Mut<void *> ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
handle, static_cast<off_t>(offset));
|
||||
if (ptr == MAP_FAILED) {
|
||||
return fail("mmap failed: {}", errno);
|
||||
}
|
||||
|
||||
m_ptr = static_cast<u8 *>(ptr);
|
||||
m_size = size;
|
||||
|
||||
madvise(m_ptr, m_size, MADV_SEQUENTIAL);
|
||||
#endif
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto FileOps::MemoryMappedRegion::unmap() -> void {
|
||||
if (!m_ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
UnmapViewOfFile(m_ptr);
|
||||
if (m_map_handle) {
|
||||
CloseHandle(m_map_handle);
|
||||
m_map_handle = NULL;
|
||||
}
|
||||
#elif IA_PLATFORM_UNIX
|
||||
munmap(m_ptr, m_size);
|
||||
#endif
|
||||
m_ptr = nullptr;
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
auto FileOps::MemoryMappedRegion::flush() -> void {
|
||||
if (!m_ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
FlushViewOfFile(m_ptr, m_size);
|
||||
#elif IA_PLATFORM_UNIX
|
||||
msync(m_ptr, m_size, MS_SYNC);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
150
Src/IACore/imp/cpp/Http/Client.cpp
Normal file
150
Src/IACore/imp/cpp/Http/Client.cpp
Normal file
@ -0,0 +1,150 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/DataOps.hpp>
|
||||
#include <IACore/Http/Client.hpp>
|
||||
|
||||
namespace IACore {
|
||||
auto HttpClient::create(Ref<String> host) -> Result<Box<HttpClient>> {
|
||||
return make_box_protected<HttpClient>(httplib::Client(host));
|
||||
}
|
||||
|
||||
static auto build_headers(Span<Const<HttpClient::Header>> headers,
|
||||
Const<Const<char> *> default_content_type)
|
||||
-> httplib::Headers {
|
||||
Mut<httplib::Headers> out;
|
||||
Mut<bool> has_content_type = false;
|
||||
|
||||
for (Ref<HttpClient::Header> h : headers) {
|
||||
out.emplace(h.first, h.second);
|
||||
|
||||
if (h.first == HttpClient::header_type_to_string(
|
||||
HttpClient::EHeaderType::CONTENT_TYPE)) {
|
||||
has_content_type = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_content_type && default_content_type) {
|
||||
out.emplace("Content-Type", default_content_type);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
HttpClient::HttpClient(ForwardRef<httplib::Client> client)
|
||||
: m_client(std::move(client)),
|
||||
m_last_response_code(EResponseCode::INTERNAL_SERVER_ERROR) {
|
||||
m_client.enable_server_certificate_verification(true);
|
||||
}
|
||||
|
||||
HttpClient::~HttpClient() = default;
|
||||
|
||||
auto HttpClient::enable_certificate_verification() -> void {
|
||||
m_client.enable_server_certificate_verification(true);
|
||||
}
|
||||
|
||||
auto HttpClient::disable_certificate_verification() -> void {
|
||||
m_client.enable_server_certificate_verification(false);
|
||||
}
|
||||
|
||||
auto HttpClient::preprocess_response(Ref<String> response) -> String {
|
||||
Const<Span<Const<u8>>> response_bytes = {
|
||||
reinterpret_cast<Const<u8> *>(response.data()), response.size()};
|
||||
Const<DataOps::CompressionType> compression =
|
||||
DataOps::detect_compression(response_bytes);
|
||||
|
||||
switch (compression) {
|
||||
case DataOps::CompressionType::Gzip: {
|
||||
Const<Result<Vec<u8>>> data = DataOps::gzip_inflate(response_bytes);
|
||||
if (!data) {
|
||||
return response;
|
||||
}
|
||||
return String(reinterpret_cast<Const<char> *>(data->data()), data->size());
|
||||
}
|
||||
|
||||
case DataOps::CompressionType::Zlib: {
|
||||
Const<Result<Vec<u8>>> data = DataOps::zlib_inflate(response_bytes);
|
||||
if (!data) {
|
||||
return response;
|
||||
}
|
||||
return String(reinterpret_cast<Const<char> *>(data->data()), data->size());
|
||||
}
|
||||
|
||||
case DataOps::CompressionType::None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
auto HttpClient::raw_get(Ref<String> path, Span<Const<Header>> headers,
|
||||
Const<Const<char> *> default_content_type)
|
||||
-> Result<String> {
|
||||
Const<httplib::Headers> http_headers =
|
||||
build_headers(headers, default_content_type);
|
||||
|
||||
Mut<String> adjusted_path = path;
|
||||
if (!path.empty() && path[0] != '/') {
|
||||
adjusted_path = "/" + path;
|
||||
}
|
||||
|
||||
Const<httplib::Result> res =
|
||||
m_client.Get(adjusted_path.c_str(), http_headers);
|
||||
|
||||
if (res) {
|
||||
m_last_response_code = static_cast<EResponseCode>(res->status);
|
||||
if (res->status >= 200 && res->status < 300) {
|
||||
return preprocess_response(res->body);
|
||||
}
|
||||
return fail("HTTP Error {} : {}", res->status, res->body);
|
||||
}
|
||||
|
||||
return fail("Network Error: {}", httplib::to_string(res.error()));
|
||||
}
|
||||
|
||||
auto HttpClient::raw_post(Ref<String> path, Span<Const<Header>> headers,
|
||||
Ref<String> body,
|
||||
Const<Const<char> *> default_content_type)
|
||||
-> Result<String> {
|
||||
Mut<httplib::Headers> http_headers =
|
||||
build_headers(headers, default_content_type);
|
||||
|
||||
Mut<String> content_type = default_content_type;
|
||||
if (http_headers.count("Content-Type")) {
|
||||
Const<httplib::Headers::iterator> t = http_headers.find("Content-Type");
|
||||
content_type = t->second;
|
||||
http_headers.erase(t);
|
||||
}
|
||||
|
||||
m_client.set_keep_alive(true);
|
||||
|
||||
Mut<String> adjusted_path = path;
|
||||
if (!path.empty() && path[0] != '/') {
|
||||
adjusted_path = "/" + path;
|
||||
}
|
||||
|
||||
Const<httplib::Result> res = m_client.Post(
|
||||
adjusted_path.c_str(), http_headers, body, content_type.c_str());
|
||||
|
||||
if (res) {
|
||||
m_last_response_code = static_cast<EResponseCode>(res->status);
|
||||
if (res->status >= 200 && res->status < 300) {
|
||||
return preprocess_response(res->body);
|
||||
}
|
||||
return fail("HTTP Error {} : {}", res->status, res->body);
|
||||
}
|
||||
|
||||
return fail("Network Error: {}", httplib::to_string(res.error()));
|
||||
}
|
||||
} // namespace IACore
|
||||
117
Src/IACore/imp/cpp/Http/Common.cpp
Normal file
117
Src/IACore/imp/cpp/Http/Common.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/Http/Common.hpp>
|
||||
|
||||
namespace IACore {
|
||||
auto HttpCommon::url_encode(Ref<String> value) -> String {
|
||||
Mut<std::stringstream> escaped;
|
||||
escaped.fill('0');
|
||||
escaped << std::hex << std::uppercase;
|
||||
|
||||
for (Const<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();
|
||||
}
|
||||
|
||||
auto HttpCommon::url_decode(Ref<String> value) -> String {
|
||||
Mut<String> result;
|
||||
result.reserve(value.length());
|
||||
|
||||
for (Mut<size_t> i = 0; i < value.length(); ++i) {
|
||||
if (value[i] == '%' && i + 2 < value.length()) {
|
||||
Const<std::string> hex_str = value.substr(i + 1, 2);
|
||||
Const<char> decoded_char =
|
||||
static_cast<char>(std::strtol(hex_str.c_str(), nullptr, 16));
|
||||
result += decoded_char;
|
||||
i += 2;
|
||||
} else if (value[i] == '+')
|
||||
result += ' ';
|
||||
else
|
||||
result += value[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto HttpCommon::header_type_to_string(Const<EHeaderType> type) -> String {
|
||||
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";
|
||||
}
|
||||
return "<Unknown>";
|
||||
}
|
||||
|
||||
auto HttpCommon::is_success_response_code(Const<EResponseCode> code) -> bool {
|
||||
return (i32)code >= 200 && (i32)code < 300;
|
||||
}
|
||||
} // namespace IACore
|
||||
161
Src/IACore/imp/cpp/Http/Server.cpp
Normal file
161
Src/IACore/imp/cpp/Http/Server.cpp
Normal file
@ -0,0 +1,161 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/Http/Server.hpp>
|
||||
|
||||
namespace IACore {
|
||||
|
||||
auto HttpServer::Request::get_header(Ref<String> key) const -> String {
|
||||
if (Const<HashMap<String, String>::const_iterator> it = headers.find(key);
|
||||
it != headers.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
auto HttpServer::Request::get_param(Ref<String> key) const -> String {
|
||||
if (Const<HashMap<String, String>::const_iterator> it = params.find(key);
|
||||
it != params.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
auto HttpServer::Request::get_path_param(Ref<String> key) const -> String {
|
||||
if (Const<HashMap<String, String>::const_iterator> it = path_params.find(key);
|
||||
it != path_params.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
auto HttpServer::Request::has_header(Ref<String> key) const -> bool {
|
||||
return headers.contains(key);
|
||||
}
|
||||
|
||||
auto HttpServer::Request::has_param(Ref<String> key) const -> bool {
|
||||
return params.contains(key);
|
||||
}
|
||||
|
||||
auto HttpServer::Request::has_path_param(Ref<String> key) const -> bool {
|
||||
return path_params.contains(key);
|
||||
}
|
||||
|
||||
void HttpServer::Response::set_content(Ref<String> content, Ref<String> type) {
|
||||
body = content;
|
||||
content_type = type;
|
||||
}
|
||||
|
||||
void HttpServer::Response::set_status(Const<EResponseCode> status_code) {
|
||||
code = status_code;
|
||||
}
|
||||
|
||||
void HttpServer::Response::add_header(Ref<String> key, Ref<String> value) {
|
||||
headers[key] = value;
|
||||
}
|
||||
|
||||
struct PublicHttpServer : public HttpServer {
|
||||
PublicHttpServer() = default;
|
||||
};
|
||||
|
||||
HttpServer::HttpServer() = default;
|
||||
|
||||
HttpServer::~HttpServer() { stop(); }
|
||||
|
||||
auto HttpServer::create() -> Result<Box<HttpServer>> {
|
||||
return make_box<PublicHttpServer>();
|
||||
}
|
||||
|
||||
auto HttpServer::listen(Ref<String> host, Const<u32> port) -> Result<void> {
|
||||
if (!m_server.listen(host.c_str(), static_cast<int>(port))) {
|
||||
return fail("Failed to start HTTP server on {}:{}", host, port);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void HttpServer::stop() {
|
||||
if (m_server.is_running()) {
|
||||
m_server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
auto HttpServer::is_running() const -> bool { return m_server.is_running(); }
|
||||
|
||||
void HttpServer::register_handler(Ref<String> method, Ref<String> pattern,
|
||||
Const<Handler> handler) {
|
||||
Const<httplib::Server::Handler> wrapper =
|
||||
[handler](Ref<httplib::Request> req, MutRef<httplib::Response> res) {
|
||||
Mut<Request> ia_req;
|
||||
ia_req.path = req.path;
|
||||
ia_req.method = req.method;
|
||||
ia_req.body = req.body;
|
||||
|
||||
for (Ref<Pair<const String, String>> item : req.headers) {
|
||||
ia_req.headers[item.first] = item.second;
|
||||
}
|
||||
|
||||
for (Ref<Pair<const String, String>> item : req.params) {
|
||||
ia_req.params[item.first] = item.second;
|
||||
}
|
||||
|
||||
for (Ref<Pair<const String, String>> item : req.path_params) {
|
||||
ia_req.path_params[item.first] = item.second;
|
||||
}
|
||||
|
||||
Mut<Response> ia_res;
|
||||
handler(ia_req, ia_res);
|
||||
|
||||
res.status = static_cast<int>(ia_res.code);
|
||||
res.set_content(ia_res.body, ia_res.content_type.c_str());
|
||||
|
||||
for (Ref<Pair<String, String>> item : ia_res.headers) {
|
||||
res.set_header(item.first.c_str(), item.second.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
if (method == "GET") {
|
||||
m_server.Get(pattern.c_str(), wrapper);
|
||||
} else if (method == "POST") {
|
||||
m_server.Post(pattern.c_str(), wrapper);
|
||||
} else if (method == "PUT") {
|
||||
m_server.Put(pattern.c_str(), wrapper);
|
||||
} else if (method == "DELETE") {
|
||||
m_server.Delete(pattern.c_str(), wrapper);
|
||||
} else if (method == "OPTIONS") {
|
||||
m_server.Options(pattern.c_str(), wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpServer::get(Ref<String> pattern, Const<Handler> handler) {
|
||||
register_handler("GET", pattern, handler);
|
||||
}
|
||||
|
||||
void HttpServer::post(Ref<String> pattern, Const<Handler> handler) {
|
||||
register_handler("POST", pattern, handler);
|
||||
}
|
||||
|
||||
void HttpServer::put(Ref<String> pattern, Const<Handler> handler) {
|
||||
register_handler("PUT", pattern, handler);
|
||||
}
|
||||
|
||||
void HttpServer::del(Ref<String> pattern, Const<Handler> handler) {
|
||||
register_handler("DELETE", pattern, handler);
|
||||
}
|
||||
|
||||
void HttpServer::options(Ref<String> pattern, Const<Handler> handler) {
|
||||
register_handler("OPTIONS", pattern, handler);
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
56
Src/IACore/imp/cpp/IACore.cpp
Normal file
56
Src/IACore/imp/cpp/IACore.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/IACore.hpp>
|
||||
#include <IACore/Logger.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <mimalloc.h>
|
||||
|
||||
namespace IACore {
|
||||
Mut<std::chrono::high_resolution_clock::time_point> g_start_time = {};
|
||||
|
||||
static Mut<std::thread::id> g_main_thread_id = {};
|
||||
static Mut<i32> g_core_init_count = 0;
|
||||
|
||||
auto initialize() -> void {
|
||||
g_core_init_count++;
|
||||
if (g_core_init_count > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_main_thread_id = std::this_thread::get_id();
|
||||
g_start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
Logger::initialize();
|
||||
|
||||
mi_option_set(mi_option_verbose, 0);
|
||||
}
|
||||
|
||||
auto terminate() -> void {
|
||||
g_core_init_count--;
|
||||
if (g_core_init_count > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::terminate();
|
||||
}
|
||||
|
||||
auto is_initialized() -> bool { return g_core_init_count > 0; }
|
||||
|
||||
auto is_main_thread() -> bool {
|
||||
return std::this_thread::get_id() == g_main_thread_id;
|
||||
}
|
||||
} // namespace IACore
|
||||
451
Src/IACore/imp/cpp/IPC.cpp
Normal file
451
Src/IACore/imp/cpp/IPC.cpp
Normal file
@ -0,0 +1,451 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/IPC.hpp>
|
||||
|
||||
#include <IACore/FileOps.hpp>
|
||||
#include <IACore/StringOps.hpp>
|
||||
#include <charconv>
|
||||
#include <fcntl.h>
|
||||
|
||||
namespace IACore {
|
||||
struct IpcConnectionDescriptor {
|
||||
Mut<String> socket_path;
|
||||
Mut<String> shared_mem_path;
|
||||
Mut<u32> shared_mem_size;
|
||||
|
||||
[[nodiscard]] auto serialize() const -> String {
|
||||
return std::format("{}|{}|{}|", socket_path, shared_mem_path,
|
||||
shared_mem_size);
|
||||
}
|
||||
|
||||
static auto deserialize(Const<StringView> data)
|
||||
-> Option<IpcConnectionDescriptor> {
|
||||
enum class ParseState { SocketPath, SharedMemPath, SharedMemSize };
|
||||
|
||||
Mut<IpcConnectionDescriptor> result{};
|
||||
Mut<usize> t = 0;
|
||||
Mut<ParseState> state = ParseState::SocketPath;
|
||||
|
||||
for (Mut<usize> i = 0; i < data.size(); ++i) {
|
||||
if (data[i] != '|') {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case ParseState::SocketPath:
|
||||
result.socket_path = String(data.substr(t, i - t));
|
||||
state = ParseState::SharedMemPath;
|
||||
break;
|
||||
|
||||
case ParseState::SharedMemPath:
|
||||
result.shared_mem_path = String(data.substr(t, i - t));
|
||||
state = ParseState::SharedMemSize;
|
||||
break;
|
||||
|
||||
case ParseState::SharedMemSize: {
|
||||
Const<const char *> start = data.data() + t;
|
||||
Const<const char *> end = data.data() + i;
|
||||
if (std::from_chars(start, end, result.shared_mem_size).ec !=
|
||||
std::errc{}) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
t = i + 1;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
IpcNode::~IpcNode() {
|
||||
if (m_socket != INVALID_SOCKET) {
|
||||
SocketOps::close(m_socket);
|
||||
}
|
||||
}
|
||||
|
||||
auto IpcNode::connect(Const<const char *> connection_string) -> Result<void> {
|
||||
Const<Option<IpcConnectionDescriptor>> desc_opt =
|
||||
IpcConnectionDescriptor::deserialize(connection_string);
|
||||
if (!desc_opt) {
|
||||
return fail("Failed to parse connection string");
|
||||
}
|
||||
Ref<IpcConnectionDescriptor> desc = *desc_opt;
|
||||
m_shm_name = desc.shared_mem_path;
|
||||
|
||||
m_socket = OX_TRY(SocketOps::create_unix_socket());
|
||||
OX_TRY_PURE(
|
||||
SocketOps::connect_unix_socket(m_socket, desc.socket_path.c_str()));
|
||||
|
||||
Mut<u8 *> mapped_ptr = OX_TRY(FileOps::map_shared_memory(
|
||||
desc.shared_mem_path, desc.shared_mem_size, false));
|
||||
m_shared_memory = mapped_ptr;
|
||||
|
||||
Mut<IpcSharedMemoryLayout *> layout =
|
||||
reinterpret_cast<IpcSharedMemoryLayout *>(m_shared_memory);
|
||||
|
||||
if (layout->meta.magic != 0x49414950) {
|
||||
return fail("Invalid shared memory header signature");
|
||||
}
|
||||
|
||||
if (layout->meta.version != 1) {
|
||||
return fail("IPC version mismatch");
|
||||
}
|
||||
|
||||
Mut<u8 *> moni_ptr = m_shared_memory + layout->moni_data_offset;
|
||||
Mut<u8 *> mino_ptr = m_shared_memory + layout->mino_data_offset;
|
||||
|
||||
m_moni = OX_TRY(RingBufferView::create(
|
||||
&layout->moni_control,
|
||||
Span<u8>(moni_ptr, static_cast<usize>(layout->moni_data_size)), false));
|
||||
|
||||
m_mino = OX_TRY(RingBufferView::create(
|
||||
&layout->mino_control,
|
||||
Span<u8>(mino_ptr, static_cast<usize>(layout->mino_data_size)), false));
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Mut<u_long> mode = 1;
|
||||
ioctlsocket(m_socket, FIONBIO, &mode);
|
||||
#else
|
||||
fcntl(m_socket, F_SETFL, O_NONBLOCK);
|
||||
#endif
|
||||
|
||||
m_receive_buffer.resize(UINT16_MAX + 1);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void IpcNode::update() {
|
||||
if (!m_moni.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mut<IpcPacketHeader> header;
|
||||
|
||||
while (m_moni.pop(
|
||||
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
|
||||
on_packet(header.id, {m_receive_buffer.data(), header.payload_size});
|
||||
}
|
||||
|
||||
Mut<u8> signal = 0;
|
||||
Const<isize> res = recv(m_socket, reinterpret_cast<char *>(&signal), 1, 0);
|
||||
if (res == 1) {
|
||||
on_signal(signal);
|
||||
} else if (res == 0 || (res < 0 && !SocketOps::is_would_block())) {
|
||||
SocketOps::close(m_socket);
|
||||
FileOps::unlink_shared_memory(m_shm_name);
|
||||
|
||||
std::exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void IpcNode::send_signal(Const<u8> signal) {
|
||||
if (m_socket != INVALID_SOCKET) {
|
||||
send(m_socket, reinterpret_cast<const char *>(&signal), sizeof(signal), 0);
|
||||
}
|
||||
}
|
||||
|
||||
auto IpcNode::send_packet(Const<u16> packet_id, Const<Span<Const<u8>>> payload)
|
||||
-> Result<void> {
|
||||
if (!m_mino.is_valid())
|
||||
return fail("invalid MINO");
|
||||
return m_mino.push(packet_id, payload);
|
||||
}
|
||||
|
||||
void IpcManager::NodeSession::send_signal(Const<u8> signal) {
|
||||
if (data_socket != INVALID_SOCKET) {
|
||||
send(data_socket, reinterpret_cast<const char *>(&signal), sizeof(signal),
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
auto IpcManager::NodeSession::send_packet(Const<u16> packet_id,
|
||||
Const<Span<Const<u8>>> payload)
|
||||
-> Result<void> {
|
||||
Const<std::scoped_lock<std::mutex>> lock(send_mutex);
|
||||
if (!moni.is_valid())
|
||||
return fail("invalid MONI");
|
||||
return moni.push(packet_id, payload);
|
||||
}
|
||||
|
||||
IpcManager::IpcManager() {
|
||||
ensure(SocketOps::is_initialized(),
|
||||
"SocketOps must be initialized before using IpcManager");
|
||||
|
||||
m_receive_buffer.resize(UINT16_MAX + 1);
|
||||
}
|
||||
|
||||
IpcManager::~IpcManager() {
|
||||
for (MutRef<Box<NodeSession>> session : m_active_sessions) {
|
||||
ProcessOps::terminate_process(session->node_process);
|
||||
FileOps::unmap_file(session->mapped_ptr);
|
||||
FileOps::unlink_shared_memory(session->shared_mem_name);
|
||||
SocketOps::close(session->data_socket);
|
||||
}
|
||||
m_active_sessions.clear();
|
||||
|
||||
for (MutRef<Box<NodeSession>> session : m_pending_sessions) {
|
||||
ProcessOps::terminate_process(session->node_process);
|
||||
FileOps::unmap_file(session->mapped_ptr);
|
||||
FileOps::unlink_shared_memory(session->shared_mem_name);
|
||||
SocketOps::close(session->listener_socket);
|
||||
}
|
||||
m_pending_sessions.clear();
|
||||
}
|
||||
|
||||
void IpcManager::update() {
|
||||
Const<std::chrono::system_clock::time_point> now =
|
||||
std::chrono::system_clock::now();
|
||||
|
||||
for (Mut<isize> i = static_cast<isize>(m_pending_sessions.size()) - 1; i >= 0;
|
||||
--i) {
|
||||
MutRef<Box<NodeSession>> session =
|
||||
m_pending_sessions[static_cast<usize>(i)];
|
||||
|
||||
if (now - session->creation_time > std::chrono::seconds(5)) {
|
||||
ProcessOps::terminate_process(session->node_process);
|
||||
|
||||
FileOps::unmap_file(session->mapped_ptr);
|
||||
FileOps::unlink_shared_memory(session->shared_mem_name);
|
||||
SocketOps::close(session->listener_socket);
|
||||
|
||||
m_pending_sessions.erase(m_pending_sessions.begin() + i);
|
||||
continue;
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Mut<u64> new_sock = accept(session->listener_socket, nullptr, nullptr);
|
||||
#else
|
||||
Mut<i32> new_sock = accept(session->listener_socket, nullptr, nullptr);
|
||||
#endif
|
||||
|
||||
if (new_sock != INVALID_SOCKET) {
|
||||
session->data_socket = new_sock;
|
||||
session->is_ready = true;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Mut<u_long> mode = 1;
|
||||
ioctlsocket(session->data_socket, FIONBIO, &mode);
|
||||
#else
|
||||
fcntl(session->data_socket, F_SETFL, O_NONBLOCK);
|
||||
#endif
|
||||
|
||||
SocketOps::close(session->listener_socket);
|
||||
session->listener_socket = INVALID_SOCKET;
|
||||
|
||||
Const<NativeProcessID> session_id = session->node_process->id.load();
|
||||
Mut<NodeSession *> session_ptr = session.get();
|
||||
m_active_sessions.push_back(std::move(session));
|
||||
m_pending_sessions.erase(m_pending_sessions.begin() + i);
|
||||
m_active_session_map[session_id] = session_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
for (Mut<isize> i = static_cast<isize>(m_active_sessions.size()) - 1; i >= 0;
|
||||
--i) {
|
||||
MutRef<Box<NodeSession>> node = m_active_sessions[static_cast<usize>(i)];
|
||||
|
||||
Mut<NativeProcessID> node_id = node->node_process->id.load();
|
||||
|
||||
Mut<IpcPacketHeader> header;
|
||||
|
||||
while (node->mino.pop(
|
||||
header, Span<u8>(m_receive_buffer.data(), m_receive_buffer.size()))) {
|
||||
on_packet(node_id, header.id,
|
||||
{m_receive_buffer.data(), header.payload_size});
|
||||
}
|
||||
|
||||
Mut<u8> signal = 0;
|
||||
Const<isize> res =
|
||||
recv(node->data_socket, reinterpret_cast<char *>(&signal), 1, 0);
|
||||
|
||||
if (res == 1) {
|
||||
on_signal(node_id, signal);
|
||||
} else if (res == 0 || (res < 0 && !SocketOps::is_would_block())) {
|
||||
ProcessOps::terminate_process(node->node_process);
|
||||
|
||||
FileOps::unmap_file(node->mapped_ptr);
|
||||
FileOps::unlink_shared_memory(node->shared_mem_name);
|
||||
SocketOps::close(node->data_socket);
|
||||
|
||||
m_active_sessions.erase(m_active_sessions.begin() + i);
|
||||
m_active_session_map.erase(node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto IpcManager::spawn_node(Ref<Path> executable_path,
|
||||
Const<u32> shared_memory_size)
|
||||
-> Result<NativeProcessID> {
|
||||
Mut<Box<NodeSession>> session = make_box<NodeSession>();
|
||||
|
||||
static Mut<std::atomic<u32>> s_id_gen{0};
|
||||
Const<u32> sid = ++s_id_gen;
|
||||
|
||||
Mut<String> sock_path;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Mut<char[MAX_PATH]> temp_path;
|
||||
GetTempPathA(MAX_PATH, temp_path);
|
||||
sock_path = std::format("{}\\ia_sess_{}.sock", temp_path, sid);
|
||||
#else
|
||||
sock_path = std::format("/tmp/ia_sess_{}.sock", sid);
|
||||
#endif
|
||||
|
||||
session->listener_socket = OX_TRY(SocketOps::create_unix_socket());
|
||||
OX_TRY_PURE(
|
||||
SocketOps::bind_unix_socket(session->listener_socket, sock_path.c_str()));
|
||||
OX_TRY_PURE(SocketOps::listen(session->listener_socket, 1));
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Mut<u_long> mode = 1;
|
||||
ioctlsocket(session->listener_socket, FIONBIO, &mode);
|
||||
#else
|
||||
fcntl(session->listener_socket, F_SETFL, O_NONBLOCK);
|
||||
#endif
|
||||
|
||||
Const<String> shm_name = std::format("ia_shm_{}", sid);
|
||||
session->mapped_ptr =
|
||||
OX_TRY(FileOps::map_shared_memory(shm_name, shared_memory_size, true));
|
||||
|
||||
Mut<IpcSharedMemoryLayout *> layout =
|
||||
reinterpret_cast<IpcSharedMemoryLayout *>(session->mapped_ptr);
|
||||
|
||||
layout->meta.magic = 0x49414950;
|
||||
layout->meta.version = 1;
|
||||
layout->meta.total_size = shared_memory_size;
|
||||
|
||||
Const<u64> header_size = IpcSharedMemoryLayout::get_header_size();
|
||||
Const<u64> usable_bytes = shared_memory_size - header_size;
|
||||
|
||||
Mut<u64> half_size = (usable_bytes / 2);
|
||||
half_size -= (half_size % 64);
|
||||
|
||||
layout->moni_data_offset = header_size;
|
||||
layout->moni_data_size = half_size;
|
||||
|
||||
layout->mino_data_offset = header_size + half_size;
|
||||
layout->mino_data_size = half_size;
|
||||
|
||||
session->moni = OX_TRY(RingBufferView::create(
|
||||
&layout->moni_control,
|
||||
Span<u8>(session->mapped_ptr + layout->moni_data_offset,
|
||||
static_cast<usize>(layout->moni_data_size)),
|
||||
true));
|
||||
|
||||
session->mino = OX_TRY(RingBufferView::create(
|
||||
&layout->mino_control,
|
||||
Span<u8>(session->mapped_ptr + layout->mino_data_offset,
|
||||
static_cast<usize>(layout->mino_data_size)),
|
||||
true));
|
||||
|
||||
Mut<IpcConnectionDescriptor> desc;
|
||||
desc.socket_path = sock_path;
|
||||
desc.shared_mem_path = shm_name;
|
||||
desc.shared_mem_size = shared_memory_size;
|
||||
|
||||
Const<String> args = std::format("\"{}\"", desc.serialize());
|
||||
|
||||
session->node_process = OX_TRY(ProcessOps::spawn_process_async(
|
||||
FileOps::normalize_executable_path(executable_path).string(), args,
|
||||
[sid](Const<StringView> line) {
|
||||
if (Env::IS_DEBUG) {
|
||||
std::cout << std::format("{}[Node:{}:STDOUT|STDERR]: {}{}\n",
|
||||
console::MAGENTA, sid, line, console::RESET);
|
||||
}
|
||||
},
|
||||
[sid](Const<Result<i32>> result) {
|
||||
if (Env::IS_DEBUG) {
|
||||
if (!result) {
|
||||
std::cout << std::format(
|
||||
"{}[Node: {}]: Failed to spawn with error '{}'{}\n",
|
||||
console::RED, sid, result.error(), console::RESET);
|
||||
} else {
|
||||
std::cout << std::format("{}[Node: {}]: Exited with code {}{}\n",
|
||||
console::RED, sid, *result,
|
||||
console::RESET);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
if (!session->node_process->is_active()) {
|
||||
return fail("Failed to spawn the child process \"{}\"",
|
||||
executable_path.string());
|
||||
}
|
||||
|
||||
Const<NativeProcessID> process_id = session->node_process->id.load();
|
||||
|
||||
session->shared_mem_name = shm_name;
|
||||
session->creation_time = std::chrono::system_clock::now();
|
||||
m_pending_sessions.push_back(std::move(session));
|
||||
|
||||
return process_id;
|
||||
}
|
||||
|
||||
auto IpcManager::wait_till_node_is_online(Const<NativeProcessID> node_id)
|
||||
-> bool {
|
||||
Mut<bool> is_pending = true;
|
||||
while (is_pending) {
|
||||
is_pending = false;
|
||||
for (Const<Box<NodeSession>> &session : m_pending_sessions) {
|
||||
if (session->node_process->id.load() == node_id) {
|
||||
is_pending = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
update();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
return m_active_session_map.contains(node_id);
|
||||
}
|
||||
|
||||
void IpcManager::shutdown_node(Const<NativeProcessID> node_id) {
|
||||
Const<HashMap<NativeProcessID, NodeSession *>::iterator> it_node =
|
||||
m_active_session_map.find(node_id);
|
||||
if (it_node == m_active_session_map.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mut<NodeSession *> node = it_node->second;
|
||||
|
||||
ProcessOps::terminate_process(node->node_process);
|
||||
FileOps::unmap_file(node->mapped_ptr);
|
||||
FileOps::unlink_shared_memory(node->shared_mem_name);
|
||||
SocketOps::close(node->data_socket);
|
||||
|
||||
std::erase_if(m_active_sessions,
|
||||
[&](Ref<Box<NodeSession>> s) { return s.get() == node; });
|
||||
m_active_session_map.erase(it_node);
|
||||
}
|
||||
|
||||
void IpcManager::send_signal(Const<NativeProcessID> node, Const<u8> signal) {
|
||||
Const<HashMap<NativeProcessID, NodeSession *>::iterator> it_node =
|
||||
m_active_session_map.find(node);
|
||||
if (it_node == m_active_session_map.end()) {
|
||||
return;
|
||||
}
|
||||
it_node->second->send_signal(signal);
|
||||
}
|
||||
|
||||
auto IpcManager::send_packet(Const<NativeProcessID> node, Const<u16> packet_id,
|
||||
Const<Span<Const<u8>>> payload) -> Result<void> {
|
||||
Const<HashMap<NativeProcessID, NodeSession *>::iterator> it_node =
|
||||
m_active_session_map.find(node);
|
||||
if (it_node == m_active_session_map.end())
|
||||
return fail("no such node");
|
||||
return it_node->second->send_packet(packet_id, payload);
|
||||
}
|
||||
} // namespace IACore
|
||||
18
Src/IACore/imp/cpp/JSON.cpp
Normal file
18
Src/IACore/imp/cpp/JSON.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/JSON.hpp>
|
||||
|
||||
namespace IACore {} // namespace IACore
|
||||
86
Src/IACore/imp/cpp/Logger.cpp
Normal file
86
Src/IACore/imp/cpp/Logger.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/Logger.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace IACore {
|
||||
Mut<Logger::LogLevel> Logger::m_log_level = Logger::LogLevel::Info;
|
||||
Mut<std::ofstream> Logger::m_log_file;
|
||||
|
||||
static auto get_seconds_count() -> f64 {
|
||||
static Const<std::chrono::time_point<std::chrono::steady_clock>> start_time =
|
||||
std::chrono::steady_clock::now();
|
||||
Const<std::chrono::time_point<std::chrono::steady_clock>> now =
|
||||
std::chrono::steady_clock::now();
|
||||
Const<std::chrono::duration<f64>> duration = now - start_time;
|
||||
return duration.count();
|
||||
}
|
||||
|
||||
auto Logger::initialize() -> void {}
|
||||
|
||||
auto Logger::terminate() -> void {
|
||||
if (m_log_file.is_open()) {
|
||||
m_log_file.flush();
|
||||
m_log_file.close();
|
||||
}
|
||||
}
|
||||
|
||||
auto Logger::enable_logging_to_disk(Const<Const<char> *> file_path)
|
||||
-> Result<void> {
|
||||
if (m_log_file.is_open()) {
|
||||
m_log_file.flush();
|
||||
m_log_file.close();
|
||||
}
|
||||
|
||||
m_log_file.open(file_path);
|
||||
|
||||
if (!m_log_file.is_open()) {
|
||||
return fail("Failed to open log file: {}", file_path);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Logger::set_log_level(Const<LogLevel> log_level) -> void {
|
||||
m_log_level = log_level;
|
||||
}
|
||||
|
||||
auto Logger::flush_logs() -> void {
|
||||
std::cout.flush();
|
||||
if (m_log_file.is_open()) {
|
||||
m_log_file.flush();
|
||||
}
|
||||
}
|
||||
|
||||
auto Logger::log_internal(Const<Const<char> *> prefix, Const<Const<char> *> tag,
|
||||
ForwardRef<String> msg) -> void {
|
||||
Const<f64> seconds = get_seconds_count();
|
||||
Const<String> out_line =
|
||||
std::format("[{:>8.3f}]: [{}]: {}", seconds, tag, msg);
|
||||
|
||||
std::cout << prefix << out_line << console::RESET << '\n';
|
||||
|
||||
if (m_log_file.is_open()) {
|
||||
m_log_file.write(out_line.data(),
|
||||
static_cast<std::streamsize>(out_line.size()));
|
||||
m_log_file.put('\n');
|
||||
m_log_file.flush();
|
||||
}
|
||||
}
|
||||
} // namespace IACore
|
||||
134
Src/IACore/imp/cpp/Platform.cpp
Normal file
134
Src/IACore/imp/cpp/Platform.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/Platform.hpp>
|
||||
|
||||
#if defined(IA_ARCH_X64)
|
||||
#ifndef _MSC_VER
|
||||
#include <cpuid.h>
|
||||
#endif
|
||||
#elif defined(IA_ARCH_ARM64)
|
||||
#if defined(__linux__) || defined(__ANDROID__)
|
||||
#include <asm/hwcap.h>
|
||||
#include <sys/auxv.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace IACore {
|
||||
Mut<Platform::Capabilities> Platform::s_capabilities{};
|
||||
|
||||
#if defined(IA_ARCH_X64)
|
||||
auto Platform::cpuid(Const<i32> function, Const<i32> sub_function,
|
||||
Mut<i32> out[4]) -> void {
|
||||
#ifdef _MSC_VER
|
||||
__cpuidex(reinterpret_cast<i32 *>(out), static_cast<i32>(function),
|
||||
static_cast<i32>(sub_function));
|
||||
#else
|
||||
Mut<u32> a = 0;
|
||||
Mut<u32> b = 0;
|
||||
Mut<u32> c = 0;
|
||||
Mut<u32> d = 0;
|
||||
__cpuid_count(function, sub_function, a, b, c, d);
|
||||
out[0] = static_cast<i32>(a);
|
||||
out[1] = static_cast<i32>(b);
|
||||
out[2] = static_cast<i32>(c);
|
||||
out[3] = static_cast<i32>(d);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
auto Platform::check_cpu() -> bool {
|
||||
#if defined(IA_ARCH_X64)
|
||||
Mut<i32> cpu_info[4];
|
||||
|
||||
cpuid(0, 0, cpu_info);
|
||||
if (cpu_info[0] < 7) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cpuid(1, 0, cpu_info);
|
||||
Const<bool> osxsave = (cpu_info[2] & (1 << 27)) != 0;
|
||||
Const<bool> avx = (cpu_info[2] & (1 << 28)) != 0;
|
||||
Const<bool> fma = (cpu_info[2] & (1 << 12)) != 0;
|
||||
|
||||
if (!osxsave || !avx || !fma) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Const<u64> xcr_feature_mask = _xgetbv(0);
|
||||
if ((xcr_feature_mask & 0x6) != 0x6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cpuid(7, 0, cpu_info);
|
||||
Const<bool> avx2 = (cpu_info[1] & (1 << 5)) != 0;
|
||||
if (!avx2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s_capabilities.hardware_crc32 = true;
|
||||
|
||||
#elif defined(IA_ARCH_ARM64)
|
||||
#if defined(__linux__) || defined(__ANDROID__)
|
||||
Const<usize> hw_caps = getauxval(AT_HWCAP);
|
||||
|
||||
#ifndef HWCAP_CRC32
|
||||
#define HWCAP_CRC32 (1 << 7)
|
||||
#endif
|
||||
|
||||
s_capabilities.hardware_crc32 = (hw_caps & HWCAP_CRC32) != 0;
|
||||
#elif defined(IA_PLATFORM_APPLE)
|
||||
s_capabilities.hardware_crc32 = true;
|
||||
#else
|
||||
s_capabilities.hardware_crc32 = false;
|
||||
#endif
|
||||
#else
|
||||
s_capabilities.hardware_crc32 = false;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Platform::get_architecture_name() -> const char * {
|
||||
#if defined(IA_ARCH_X64)
|
||||
return "x86_64";
|
||||
#elif defined(IA_ARCH_ARM64)
|
||||
return "aarch64";
|
||||
#elif defined(IA_ARCH_WASM)
|
||||
return "wasm";
|
||||
#else
|
||||
return "unknown";
|
||||
#endif
|
||||
}
|
||||
|
||||
auto Platform::get_operating_system_name() -> const char * {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return "Windows";
|
||||
#elif defined(IA_PLATFORM_APPLE)
|
||||
#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
|
||||
return "iOS";
|
||||
#else
|
||||
return "macOS";
|
||||
#endif
|
||||
#elif defined(__ANDROID__)
|
||||
return "Android";
|
||||
#elif IA_PLATFORM_LINUX
|
||||
return "Linux";
|
||||
#elif IA_PLATFORM_WASM
|
||||
return "WebAssembly";
|
||||
#else
|
||||
return "Unknown";
|
||||
#endif
|
||||
}
|
||||
} // namespace IACore
|
||||
309
Src/IACore/imp/cpp/ProcessOps.cpp
Normal file
309
Src/IACore/imp/cpp/ProcessOps.cpp
Normal file
@ -0,0 +1,309 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/ProcessOps.hpp>
|
||||
|
||||
namespace IACore {
|
||||
struct LineBuffer {
|
||||
Mut<String> m_accumulator;
|
||||
Const<std::function<void(Const<StringView>)>> m_callback;
|
||||
|
||||
auto append(Const<char *> data, Const<usize> size) -> void;
|
||||
auto flush() -> void;
|
||||
};
|
||||
|
||||
auto LineBuffer::append(Const<char *> data, Const<usize> size) -> void {
|
||||
Mut<usize> start = 0;
|
||||
for (Mut<usize> i = 0; i < size; ++i) {
|
||||
if (data[i] == '\n' || data[i] == '\r') {
|
||||
if (!m_accumulator.empty()) {
|
||||
m_accumulator.append(data + start, i - start);
|
||||
if (!m_accumulator.empty()) {
|
||||
m_callback(m_accumulator);
|
||||
}
|
||||
m_accumulator.clear();
|
||||
} else {
|
||||
if (i > start) {
|
||||
m_callback(StringView(data + start, i - start));
|
||||
}
|
||||
}
|
||||
|
||||
if (data[i] == '\r' && i + 1 < size && data[i + 1] == '\n') {
|
||||
i++;
|
||||
}
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
if (start < size) {
|
||||
m_accumulator.append(data + start, size - start);
|
||||
}
|
||||
}
|
||||
|
||||
auto LineBuffer::flush() -> void {
|
||||
if (!m_accumulator.empty()) {
|
||||
m_callback(m_accumulator);
|
||||
m_accumulator.clear();
|
||||
}
|
||||
}
|
||||
|
||||
auto ProcessOps::get_current_process_id() -> NativeProcessID {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return ::GetCurrentProcessId();
|
||||
#else
|
||||
return getpid();
|
||||
#endif
|
||||
}
|
||||
|
||||
auto ProcessOps::spawn_process_sync(
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback)
|
||||
-> Result<i32> {
|
||||
Mut<std::atomic<NativeProcessID>> id = 0;
|
||||
if constexpr (Env::IS_WINDOWS) {
|
||||
return spawn_process_windows(command, args, on_output_line_callback, id);
|
||||
} else {
|
||||
return spawn_process_posix(command, args, on_output_line_callback, id);
|
||||
}
|
||||
}
|
||||
|
||||
auto ProcessOps::spawn_process_async(
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback,
|
||||
Const<std::function<void(Const<Result<i32>>)>> on_finish_callback)
|
||||
-> Result<Box<ProcessHandle>> {
|
||||
Mut<Box<ProcessHandle>> handle = make_box<ProcessHandle>();
|
||||
handle->is_running = true;
|
||||
|
||||
Mut<ProcessHandle *> h_ptr = handle.get();
|
||||
|
||||
handle->m_thread_handle = std::jthread([h_ptr, cmd = command, arg = args,
|
||||
cb = on_output_line_callback,
|
||||
fin = on_finish_callback]() mutable {
|
||||
Mut<Result<i32>> result = fail("Platform not supported");
|
||||
|
||||
if constexpr (Env::IS_WINDOWS) {
|
||||
result = spawn_process_windows(cmd, arg, cb, h_ptr->id);
|
||||
} else {
|
||||
result = spawn_process_posix(cmd, arg, cb, h_ptr->id);
|
||||
}
|
||||
|
||||
h_ptr->is_running = false;
|
||||
|
||||
if (fin) {
|
||||
if (!result) {
|
||||
fin(fail(std::move(result.error())));
|
||||
} else {
|
||||
fin(*result);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
auto ProcessOps::terminate_process(Ref<Box<ProcessHandle>> handle) -> void {
|
||||
if (!handle || !handle->is_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Const<NativeProcessID> pid = handle->id.load();
|
||||
if (pid == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Mut<HANDLE> h_process = OpenProcess(PROCESS_TERMINATE, false, pid);
|
||||
if (h_process != NULL) {
|
||||
::TerminateProcess(h_process, 9);
|
||||
CloseHandle(h_process);
|
||||
}
|
||||
#endif
|
||||
#if IA_PLATFORM_UNIX
|
||||
kill(pid, SIGKILL);
|
||||
#endif
|
||||
}
|
||||
|
||||
auto ProcessOps::spawn_process_windows(
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback,
|
||||
MutRef<std::atomic<NativeProcessID>> id) -> Result<i32> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Mut<SECURITY_ATTRIBUTES> sa_attr = {sizeof(SECURITY_ATTRIBUTES), NULL, true};
|
||||
Mut<HANDLE> h_read = NULL;
|
||||
Mut<HANDLE> h_write = NULL;
|
||||
|
||||
if (!CreatePipe(&h_read, &h_write, &sa_attr, 0)) {
|
||||
return fail("Failed to create pipe");
|
||||
}
|
||||
|
||||
if (!SetHandleInformation(h_read, HANDLE_FLAG_INHERIT, 0)) {
|
||||
return fail("Failed to secure pipe handles");
|
||||
}
|
||||
|
||||
Mut<STARTUPINFOA> si = {sizeof(STARTUPINFOA)};
|
||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||
si.hStdOutput = h_write;
|
||||
si.hStdError = h_write;
|
||||
si.hStdInput = NULL;
|
||||
|
||||
Mut<PROCESS_INFORMATION> pi = {0};
|
||||
|
||||
Mut<String> command_line = std::format("\"{}\" {}", command, args);
|
||||
|
||||
Const<BOOL> success = CreateProcessA(NULL, command_line.data(), NULL, NULL,
|
||||
true, 0, NULL, NULL, &si, &pi);
|
||||
|
||||
CloseHandle(h_write);
|
||||
|
||||
if (!success) {
|
||||
CloseHandle(h_read);
|
||||
return fail("CreateProcess failed: {}", GetLastError());
|
||||
}
|
||||
|
||||
id.store(pi.dwProcessId);
|
||||
|
||||
Mut<LineBuffer> line_buf{"", on_output_line_callback};
|
||||
Mut<DWORD> bytes_read = 0;
|
||||
Mut<Array<char, 4096>> buffer;
|
||||
|
||||
while (ReadFile(h_read, buffer.data(), static_cast<DWORD>(buffer.size()),
|
||||
&bytes_read, NULL) &&
|
||||
bytes_read != 0) {
|
||||
line_buf.append(buffer.data(), bytes_read);
|
||||
}
|
||||
line_buf.flush();
|
||||
|
||||
Mut<DWORD> exit_code = 0;
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
GetExitCodeProcess(pi.hProcess, &exit_code);
|
||||
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(h_read);
|
||||
id.store(0);
|
||||
|
||||
return static_cast<i32>(exit_code);
|
||||
#else
|
||||
OX_UNUSED(command);
|
||||
OX_UNUSED(args);
|
||||
OX_UNUSED(on_output_line_callback);
|
||||
OX_UNUSED(id);
|
||||
return fail("Windows implementation not available.");
|
||||
#endif
|
||||
}
|
||||
|
||||
auto ProcessOps::spawn_process_posix(
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback,
|
||||
MutRef<std::atomic<NativeProcessID>> id) -> Result<i32> {
|
||||
#if IA_PLATFORM_UNIX
|
||||
Mut<Array<i32, 2>> pipefd;
|
||||
if (pipe(pipefd.data()) == -1) {
|
||||
return fail("Failed to create pipe");
|
||||
}
|
||||
|
||||
Const<pid_t> pid = fork();
|
||||
|
||||
if (pid == -1) {
|
||||
return fail("Failed to fork process");
|
||||
} else if (pid == 0) {
|
||||
close(pipefd[0]);
|
||||
|
||||
dup2(pipefd[1], STDOUT_FILENO);
|
||||
dup2(pipefd[1], STDERR_FILENO);
|
||||
close(pipefd[1]);
|
||||
|
||||
Mut<Vec<String>> arg_storage;
|
||||
Mut<Vec<char *>> argv;
|
||||
|
||||
Mut<String> cmd_str = command;
|
||||
argv.push_back(cmd_str.data());
|
||||
|
||||
Mut<String> current_token;
|
||||
Mut<bool> in_quotes = false;
|
||||
Mut<bool> is_escaped = false;
|
||||
|
||||
for (Const<char> c : args) {
|
||||
if (is_escaped) {
|
||||
current_token += c;
|
||||
is_escaped = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\\') {
|
||||
is_escaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\"') {
|
||||
in_quotes = !in_quotes;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == ' ' && !in_quotes) {
|
||||
if (!current_token.empty()) {
|
||||
arg_storage.push_back(current_token);
|
||||
current_token.clear();
|
||||
}
|
||||
} else {
|
||||
current_token += c;
|
||||
}
|
||||
}
|
||||
|
||||
if (!current_token.empty()) {
|
||||
arg_storage.push_back(current_token);
|
||||
}
|
||||
|
||||
for (MutRef<String> s : arg_storage) {
|
||||
argv.push_back(s.data());
|
||||
}
|
||||
argv.push_back(nullptr);
|
||||
|
||||
execvp(argv[0], argv.data());
|
||||
_exit(127);
|
||||
} else {
|
||||
id.store(pid);
|
||||
|
||||
close(pipefd[1]);
|
||||
|
||||
Mut<LineBuffer> line_buf{"", on_output_line_callback};
|
||||
Mut<Array<char, 4096>> buffer;
|
||||
Mut<isize> count;
|
||||
|
||||
while ((count = read(pipefd[0], buffer.data(), buffer.size())) > 0) {
|
||||
line_buf.append(buffer.data(), static_cast<usize>(count));
|
||||
}
|
||||
line_buf.flush();
|
||||
close(pipefd[0]);
|
||||
|
||||
Mut<i32> status;
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
id.store(0);
|
||||
if (WIFEXITED(status)) {
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
OX_UNUSED(command);
|
||||
OX_UNUSED(args);
|
||||
OX_UNUSED(on_output_line_callback);
|
||||
OX_UNUSED(id);
|
||||
return fail("Posix implementation not available.");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
18
Src/IACore/imp/cpp/SIMD.cpp
Normal file
18
Src/IACore/imp/cpp/SIMD.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/SIMD.hpp>
|
||||
|
||||
namespace IACore {} // namespace IACore
|
||||
146
Src/IACore/imp/cpp/SocketOps.cpp
Normal file
146
Src/IACore/imp/cpp/SocketOps.cpp
Normal file
@ -0,0 +1,146 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/SocketOps.hpp>
|
||||
#include <cstring>
|
||||
|
||||
namespace IACore {
|
||||
Mut<i32> SocketOps::s_init_count = 0;
|
||||
|
||||
auto SocketOps::close(Const<SocketHandle> sock) -> void {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return;
|
||||
}
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
closesocket(sock);
|
||||
#else
|
||||
::close(sock);
|
||||
#endif
|
||||
}
|
||||
|
||||
auto SocketOps::listen(Const<SocketHandle> sock, Const<i32> queue_size)
|
||||
-> Result<void> {
|
||||
if (::listen(sock, queue_size) == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return fail("listen failed: {}", WSAGetLastError());
|
||||
#else
|
||||
return fail("listen failed: {}", errno);
|
||||
#endif
|
||||
}
|
||||
|
||||
auto SocketOps::create_unix_socket() -> Result<SocketHandle> {
|
||||
Const<SocketHandle> sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return fail("socket(AF_UNIX) failed: {}", WSAGetLastError());
|
||||
#else
|
||||
return fail("socket(AF_UNIX) failed: {}", errno);
|
||||
#endif
|
||||
}
|
||||
return sock;
|
||||
}
|
||||
|
||||
auto SocketOps::bind_unix_socket(Const<SocketHandle> sock,
|
||||
Const<const char *> path) -> Result<void> {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return fail("Invalid socket handle");
|
||||
}
|
||||
|
||||
unlink_file(path);
|
||||
|
||||
Mut<sockaddr_un> addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
Const<usize> max_len = sizeof(addr.sun_path) - 1;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
strncpy_s(addr.sun_path, sizeof(addr.sun_path), path, max_len);
|
||||
#else
|
||||
std::strncpy(addr.sun_path, path, max_len);
|
||||
#endif
|
||||
|
||||
if (::bind(sock, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) ==
|
||||
-1) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return fail("bind failed: {}", WSAGetLastError());
|
||||
#else
|
||||
return fail("bind failed: {}", errno);
|
||||
#endif
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto SocketOps::connect_unix_socket(Const<SocketHandle> sock,
|
||||
Const<const char *> path) -> Result<void> {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return fail("Invalid socket handle");
|
||||
}
|
||||
|
||||
Mut<sockaddr_un> addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
Const<usize> max_len = sizeof(addr.sun_path) - 1;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
strncpy_s(addr.sun_path, sizeof(addr.sun_path), path, max_len);
|
||||
#else
|
||||
std::strncpy(addr.sun_path, path, max_len);
|
||||
#endif
|
||||
|
||||
if (::connect(sock, reinterpret_cast<struct sockaddr *>(&addr),
|
||||
sizeof(addr)) == -1) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return fail("connect failed: {}", WSAGetLastError());
|
||||
#else
|
||||
return fail("connect failed: {}", errno);
|
||||
#endif
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto SocketOps::is_port_available(Const<u16> port, Const<i32> type) -> bool {
|
||||
Const<SocketHandle> sock = socket(AF_INET, type, 0);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Mut<sockaddr_in> addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
Mut<bool> is_free = false;
|
||||
if (::bind(sock, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) ==
|
||||
0) {
|
||||
is_free = true;
|
||||
}
|
||||
|
||||
close(sock);
|
||||
|
||||
return is_free;
|
||||
}
|
||||
|
||||
auto SocketOps::is_would_block() -> bool {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
return WSAGetLastError() == WSAEWOULDBLOCK;
|
||||
#else
|
||||
return errno == EWOULDBLOCK || errno == EAGAIN;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
85
Src/IACore/imp/cpp/StreamReader.cpp
Normal file
85
Src/IACore/imp/cpp/StreamReader.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/FileOps.hpp>
|
||||
#include <IACore/StreamReader.hpp>
|
||||
|
||||
namespace IACore {
|
||||
auto StreamReader::create_from_file(Ref<Path> path) -> Result<StreamReader> {
|
||||
Mut<usize> size = 0;
|
||||
|
||||
Const<const u8 *> ptr = OX_TRY(FileOps::map_file(path, size));
|
||||
|
||||
Mut<StreamReader> reader(Span<const u8>(ptr, size));
|
||||
reader.m_storage_type = StorageType::OwningMmap;
|
||||
|
||||
return reader;
|
||||
}
|
||||
|
||||
StreamReader::StreamReader(ForwardRef<Vec<u8>> data)
|
||||
: m_owning_vector(std::move(data)),
|
||||
m_storage_type(StorageType::OwningVector) {
|
||||
m_data = m_owning_vector.data();
|
||||
m_data_size = m_owning_vector.size();
|
||||
}
|
||||
|
||||
StreamReader::StreamReader(Const<Span<const u8>> data)
|
||||
: m_data(data.data()), m_data_size(data.size()),
|
||||
m_storage_type(StorageType::NonOwning) {}
|
||||
|
||||
StreamReader::StreamReader(ForwardRef<StreamReader> other)
|
||||
: m_data(other.m_data), m_cursor(other.m_cursor),
|
||||
m_data_size(other.m_data_size),
|
||||
m_owning_vector(std::move(other.m_owning_vector)),
|
||||
m_storage_type(other.m_storage_type) {
|
||||
other.m_storage_type = StorageType::NonOwning;
|
||||
other.m_data = {};
|
||||
other.m_data_size = 0;
|
||||
|
||||
if (m_storage_type == StorageType::OwningVector) {
|
||||
m_data = m_owning_vector.data();
|
||||
}
|
||||
}
|
||||
|
||||
auto StreamReader::operator=(ForwardRef<StreamReader> other)
|
||||
-> MutRef<StreamReader> {
|
||||
if (this != &other) {
|
||||
if (m_storage_type == StorageType::OwningMmap) {
|
||||
FileOps::unmap_file(m_data);
|
||||
}
|
||||
|
||||
m_data = other.m_data;
|
||||
m_cursor = other.m_cursor;
|
||||
m_data_size = other.m_data_size;
|
||||
m_owning_vector = std::move(other.m_owning_vector);
|
||||
m_storage_type = other.m_storage_type;
|
||||
|
||||
if (m_storage_type == StorageType::OwningVector) {
|
||||
m_data = m_owning_vector.data();
|
||||
}
|
||||
|
||||
other.m_storage_type = StorageType::NonOwning;
|
||||
other.m_data = {};
|
||||
other.m_data_size = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamReader::~StreamReader() {
|
||||
if (m_storage_type == StorageType::OwningMmap) {
|
||||
FileOps::unmap_file(m_data);
|
||||
}
|
||||
}
|
||||
} // namespace IACore
|
||||
162
Src/IACore/imp/cpp/StreamWriter.cpp
Normal file
162
Src/IACore/imp/cpp/StreamWriter.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/StreamWriter.hpp>
|
||||
|
||||
namespace IACore {
|
||||
|
||||
auto StreamWriter::create_from_file(Ref<Path> path) -> Result<StreamWriter> {
|
||||
Mut<FILE *> f = std::fopen(path.string().c_str(), "wb");
|
||||
if (!f) {
|
||||
return fail("Failed to open file for writing: {}", path.string());
|
||||
}
|
||||
std::fclose(f);
|
||||
|
||||
Mut<StreamWriter> writer;
|
||||
writer.m_file_path = path;
|
||||
writer.m_storage_type = StorageType::OwningFile;
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
StreamWriter::StreamWriter() : m_storage_type(StorageType::OwningVector) {
|
||||
m_capacity = 256;
|
||||
m_owning_vector.resize(m_capacity);
|
||||
m_buffer = m_owning_vector.data();
|
||||
}
|
||||
|
||||
StreamWriter::StreamWriter(Const<Span<u8>> data)
|
||||
: m_buffer(data.data()), m_cursor(0), m_capacity(data.size()),
|
||||
m_storage_type(StorageType::NonOwning) {}
|
||||
|
||||
StreamWriter::StreamWriter(ForwardRef<StreamWriter> other)
|
||||
: m_buffer(other.m_buffer), m_cursor(other.m_cursor),
|
||||
m_capacity(other.m_capacity), m_file_path(other.m_file_path),
|
||||
m_owning_vector(std::move(other.m_owning_vector)),
|
||||
m_storage_type(other.m_storage_type) {
|
||||
other.m_capacity = {};
|
||||
other.m_buffer = {};
|
||||
other.m_storage_type = StorageType::NonOwning;
|
||||
|
||||
if (m_storage_type == StorageType::OwningVector)
|
||||
m_buffer = m_owning_vector.data();
|
||||
}
|
||||
|
||||
auto StreamWriter::operator=(ForwardRef<StreamWriter> other)
|
||||
-> MutRef<StreamWriter> {
|
||||
if (this != &other) {
|
||||
if (m_storage_type == StorageType::OwningFile) {
|
||||
if (Const<Result<void>> res = flush_to_disk(); !res) {
|
||||
std::fprintf(stderr, "[IACore] Data loss in StreamWriter move: %s\n",
|
||||
res.error().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
m_buffer = other.m_buffer;
|
||||
m_cursor = other.m_cursor;
|
||||
m_capacity = other.m_capacity;
|
||||
m_file_path = std::move(other.m_file_path);
|
||||
m_owning_vector = std::move(other.m_owning_vector);
|
||||
m_storage_type = other.m_storage_type;
|
||||
|
||||
if (m_storage_type == StorageType::OwningVector)
|
||||
m_buffer = m_owning_vector.data();
|
||||
|
||||
other.m_capacity = 0;
|
||||
other.m_cursor = 0;
|
||||
other.m_buffer = nullptr;
|
||||
other.m_storage_type = StorageType::NonOwning;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamWriter::~StreamWriter() {
|
||||
if (m_storage_type == StorageType::OwningFile) {
|
||||
if (Const<Result<void>> res = flush_to_disk(); !res) {
|
||||
std::fprintf(stderr, "[IACore] LOST DATA in ~StreamWriter: %s\n",
|
||||
res.error().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto StreamWriter::flush() -> Result<void> {
|
||||
Mut<Result<void>> res = flush_to_disk();
|
||||
if (res.has_value()) {
|
||||
m_storage_type = StorageType::OwningVector;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
auto StreamWriter::flush_to_disk() -> Result<void> {
|
||||
if (m_storage_type != StorageType::OwningFile || m_file_path.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Mut<FILE *> f = std::fopen(m_file_path.string().c_str(), "wb");
|
||||
if (!f) {
|
||||
return fail("Failed to open file for writing: {}", m_file_path.string());
|
||||
}
|
||||
|
||||
Const<usize> written = std::fwrite(m_buffer, 1, m_cursor, f);
|
||||
std::fclose(f);
|
||||
|
||||
if (written != m_cursor) {
|
||||
return fail("Incomplete write: {} of {} bytes written", written, m_cursor);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
auto StreamWriter::write(Const<u8> byte, Const<usize> count) -> Result<void> {
|
||||
if (m_cursor + count > m_capacity) {
|
||||
if (m_storage_type == StorageType::NonOwning) {
|
||||
return fail("StreamWriter buffer overflow (NonOwning)");
|
||||
}
|
||||
|
||||
Const<usize> required = m_cursor + count;
|
||||
Const<usize> double_cap = m_capacity * 2;
|
||||
Const<usize> new_capacity = (double_cap > required) ? double_cap : required;
|
||||
|
||||
m_owning_vector.resize(new_capacity);
|
||||
m_capacity = m_owning_vector.size();
|
||||
m_buffer = m_owning_vector.data();
|
||||
}
|
||||
|
||||
std::memset(m_buffer + m_cursor, byte, count);
|
||||
m_cursor += count;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto StreamWriter::write(Const<const void *> buffer, Const<usize> size)
|
||||
-> Result<void> {
|
||||
if (m_cursor + size > m_capacity) {
|
||||
if (m_storage_type == StorageType::NonOwning) {
|
||||
return fail("StreamWriter buffer overflow (NonOwning)");
|
||||
}
|
||||
|
||||
Const<usize> required = m_cursor + size;
|
||||
Const<usize> double_cap = m_capacity * 2;
|
||||
Const<usize> new_capacity = (double_cap > required) ? double_cap : required;
|
||||
|
||||
m_owning_vector.resize(new_capacity);
|
||||
m_capacity = m_owning_vector.size();
|
||||
m_buffer = m_owning_vector.data();
|
||||
}
|
||||
|
||||
std::memcpy(m_buffer + m_cursor, buffer, size);
|
||||
m_cursor += size;
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
122
Src/IACore/imp/cpp/StringOps.cpp
Normal file
122
Src/IACore/imp/cpp/StringOps.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/StringOps.hpp>
|
||||
|
||||
namespace IACore {
|
||||
|
||||
static Const<String> BASE64_CHAR_TABLE =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
static auto is_base64(Const<u8> c) -> bool {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
||||
(c >= '0' && c <= '9') || (c == '+') || (c == '/');
|
||||
}
|
||||
|
||||
static auto get_base64_index(Const<u8> c) -> u8 {
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return c - 'A';
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return c - 'a' + 26;
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0' + 52;
|
||||
if (c == '+')
|
||||
return 62;
|
||||
if (c == '/')
|
||||
return 63;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto StringOps::encode_base64(Const<Span<Const<u8>>> data) -> String {
|
||||
Mut<String> result;
|
||||
result.reserve(((data.size() + 2) / 3) * 4);
|
||||
|
||||
for (Mut<usize> i = 0; i < data.size(); i += 3) {
|
||||
Const<u32> b0 = data[i];
|
||||
Const<u32> b1 = (i + 1 < data.size()) ? data[i + 1] : 0;
|
||||
Const<u32> b2 = (i + 2 < data.size()) ? data[i + 2] : 0;
|
||||
|
||||
Const<u32> triple = (b0 << 16) | (b1 << 8) | b2;
|
||||
|
||||
result += BASE64_CHAR_TABLE[(triple >> 18) & 0x3F];
|
||||
result += BASE64_CHAR_TABLE[(triple >> 12) & 0x3F];
|
||||
|
||||
if (i + 1 < data.size()) {
|
||||
result += BASE64_CHAR_TABLE[(triple >> 6) & 0x3F];
|
||||
} else {
|
||||
result += '=';
|
||||
}
|
||||
|
||||
if (i + 2 < data.size()) {
|
||||
result += BASE64_CHAR_TABLE[triple & 0x3F];
|
||||
} else {
|
||||
result += '=';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto StringOps::decode_base64(Ref<String> data) -> Vec<u8> {
|
||||
Mut<Vec<u8>> result;
|
||||
result.reserve(data.size() * 3 / 4);
|
||||
|
||||
Mut<i32> i = 0;
|
||||
Mut<Array<u8, 4>> tmp_buf = {};
|
||||
|
||||
for (Const<char> c_char : data) {
|
||||
Const<u8> c = static_cast<u8>(c_char);
|
||||
if (c == '=') {
|
||||
break;
|
||||
}
|
||||
if (!is_base64(c)) {
|
||||
break;
|
||||
}
|
||||
|
||||
tmp_buf[i++] = c;
|
||||
if (i == 4) {
|
||||
Const<u8> n0 = get_base64_index(tmp_buf[0]);
|
||||
Const<u8> n1 = get_base64_index(tmp_buf[1]);
|
||||
Const<u8> n2 = get_base64_index(tmp_buf[2]);
|
||||
Const<u8> n3 = get_base64_index(tmp_buf[3]);
|
||||
|
||||
result.push_back((n0 << 2) | ((n1 & 0x30) >> 4));
|
||||
result.push_back(((n1 & 0x0F) << 4) | ((n2 & 0x3C) >> 2));
|
||||
result.push_back(((n2 & 0x03) << 6) | n3);
|
||||
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
for (Mut<i32> j = i; j < 4; ++j) {
|
||||
tmp_buf[j] = 'A';
|
||||
}
|
||||
|
||||
Const<u8> n0 = get_base64_index(tmp_buf[0]);
|
||||
Const<u8> n1 = get_base64_index(tmp_buf[1]);
|
||||
Const<u8> n2 = get_base64_index(tmp_buf[2]);
|
||||
|
||||
if (i > 1) {
|
||||
result.push_back((n0 << 2) | ((n1 & 0x30) >> 4));
|
||||
}
|
||||
if (i > 2) {
|
||||
result.push_back(((n1 & 0x0F) << 4) | ((n2 & 0x3C) >> 2));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
112
Src/IACore/imp/cpp/Utils.cpp
Normal file
112
Src/IACore/imp/cpp/Utils.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/Utils.hpp>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace IACore {
|
||||
namespace {
|
||||
auto from_hex_char(Const<char> c) -> i32 {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return c - 'A' + 10;
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
extern Mut<std::chrono::high_resolution_clock::time_point> g_start_time;
|
||||
|
||||
auto Utils::get_unix_time() -> u64 {
|
||||
Const<std::chrono::system_clock::time_point> now =
|
||||
std::chrono::system_clock::now();
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(
|
||||
now.time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
|
||||
auto Utils::get_ticks_count() -> u64 {
|
||||
Const<std::chrono::high_resolution_clock::duration> duration =
|
||||
std::chrono::high_resolution_clock::now() - g_start_time;
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(duration)
|
||||
.count();
|
||||
}
|
||||
|
||||
auto Utils::get_seconds_count() -> f64 {
|
||||
Const<std::chrono::high_resolution_clock::duration> duration =
|
||||
std::chrono::high_resolution_clock::now() - g_start_time;
|
||||
return static_cast<f64>(
|
||||
std::chrono::duration_cast<std::chrono::seconds>(duration).count());
|
||||
}
|
||||
|
||||
auto Utils::get_random() -> f32 {
|
||||
return static_cast<f32>(std::rand()) / static_cast<f32>(RAND_MAX);
|
||||
}
|
||||
|
||||
auto Utils::get_random(Const<u64> max) -> u64 {
|
||||
return static_cast<u64>(static_cast<f32>(max) * get_random());
|
||||
}
|
||||
|
||||
auto Utils::get_random(Const<i64> min, Const<i64> max) -> i64 {
|
||||
return min + static_cast<i64>(static_cast<f32>(max - min) * get_random());
|
||||
}
|
||||
|
||||
auto Utils::sleep(Const<u64> milliseconds) -> void {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
|
||||
}
|
||||
|
||||
auto Utils::binary_to_hex_string(Const<Span<Const<u8>>> data) -> String {
|
||||
static constexpr Const<char[17]> lut = "0123456789ABCDEF";
|
||||
Mut<String> res = String();
|
||||
res.reserve(data.size() * 2);
|
||||
|
||||
for (Const<u8> b : data) {
|
||||
res.push_back(lut[(b >> 4) & 0x0F]);
|
||||
res.push_back(lut[b & 0x0F]);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
auto Utils::hex_string_to_binary(Const<StringView> hex) -> Result<Vec<u8>> {
|
||||
if (hex.size() % 2 != 0) {
|
||||
return fail("Hex string must have even length");
|
||||
}
|
||||
|
||||
Mut<Vec<u8>> out = Vec<u8>();
|
||||
out.reserve(hex.size() / 2);
|
||||
|
||||
for (Mut<usize> i = 0; i < hex.size(); i += 2) {
|
||||
Const<char> high = hex[i];
|
||||
Const<char> low = hex[i + 1];
|
||||
|
||||
Const<i32> h = from_hex_char(high);
|
||||
Const<i32> l = from_hex_char(low);
|
||||
|
||||
if (h == -1 || l == -1) {
|
||||
return fail("Invalid hex character found");
|
||||
}
|
||||
|
||||
out.push_back(static_cast<u8>((h << 4) | l));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
} // namespace IACore
|
||||
82
Src/IACore/imp/cpp/XML.cpp
Normal file
82
Src/IACore/imp/cpp/XML.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/XML.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace IACore {
|
||||
|
||||
auto XML::parse_from_string(Ref<String> data) -> Result<Document> {
|
||||
Mut<Document> doc;
|
||||
Const<pugi::xml_parse_result> parse_result = doc.load_string(data.c_str());
|
||||
if (!parse_result) {
|
||||
return fail("Failed to parse XML {}", parse_result.description());
|
||||
}
|
||||
return std::move(doc);
|
||||
}
|
||||
|
||||
auto XML::parse_from_file(Ref<Path> path) -> Result<Document> {
|
||||
Mut<Document> doc;
|
||||
Const<pugi::xml_parse_result> parse_result =
|
||||
doc.load_file(path.string().c_str());
|
||||
if (!parse_result) {
|
||||
return fail("Failed to parse XML {}", parse_result.description());
|
||||
}
|
||||
return std::move(doc);
|
||||
}
|
||||
|
||||
auto XML::serialize_to_string(Ref<Node> node, Const<bool> escape) -> String {
|
||||
Mut<std::ostringstream> oss;
|
||||
node.print(oss);
|
||||
return escape ? escape_xml_string(oss.str()) : oss.str();
|
||||
}
|
||||
|
||||
auto XML::serialize_to_string(Ref<Document> doc, Const<bool> escape) -> String {
|
||||
Mut<std::ostringstream> oss;
|
||||
doc.save(oss);
|
||||
return escape ? escape_xml_string(oss.str()) : oss.str();
|
||||
}
|
||||
|
||||
auto XML::escape_xml_string(Ref<String> xml) -> String {
|
||||
Mut<String> buffer;
|
||||
buffer.reserve(xml.size() + (xml.size() / 10));
|
||||
|
||||
for (Const<char> c : xml) {
|
||||
switch (c) {
|
||||
case '&':
|
||||
buffer.append("&");
|
||||
break;
|
||||
case '\"':
|
||||
buffer.append(""");
|
||||
break;
|
||||
case '\'':
|
||||
buffer.append("'");
|
||||
break;
|
||||
case '<':
|
||||
buffer.append("<");
|
||||
break;
|
||||
case '>':
|
||||
buffer.append(">");
|
||||
break;
|
||||
default:
|
||||
buffer.push_back(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
264
Src/IACore/inc/IACore/ADT/RingBuffer.hpp
Normal file
264
Src/IACore/inc/IACore/ADT/RingBuffer.hpp
Normal file
@ -0,0 +1,264 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
namespace IACore {
|
||||
class RingBufferView {
|
||||
public:
|
||||
static constexpr Const<u16> PACKET_ID_SKIP = 0;
|
||||
|
||||
struct ControlBlock {
|
||||
struct alignas(64) {
|
||||
Mut<std::atomic<u32>> write_offset{0};
|
||||
} producer;
|
||||
|
||||
struct alignas(64) {
|
||||
Mut<std::atomic<u32>> read_offset{0};
|
||||
Mut<u32> capacity{0};
|
||||
} consumer;
|
||||
};
|
||||
|
||||
static_assert(offsetof(ControlBlock, consumer) == 64,
|
||||
"False sharing detected in ControlBlock");
|
||||
|
||||
struct PacketHeader {
|
||||
PacketHeader() : id(0), payload_size(0) {}
|
||||
|
||||
PacketHeader(Const<u16> id) : id(id), payload_size(0) {}
|
||||
|
||||
PacketHeader(Const<u16> id, Const<u16> payload_size)
|
||||
: id(id), payload_size(payload_size) {}
|
||||
|
||||
Mut<u16> id{};
|
||||
Mut<u16> payload_size{};
|
||||
};
|
||||
|
||||
public:
|
||||
static auto default_instance() -> RingBufferView;
|
||||
|
||||
static auto create(Ref<Span<u8>> buffer, Const<bool> is_owner)
|
||||
-> Result<RingBufferView>;
|
||||
static auto create(Const<ControlBlock *> control_block, Ref<Span<u8>> buffer,
|
||||
Const<bool> is_owner) -> Result<RingBufferView>;
|
||||
|
||||
// Returns:
|
||||
// - nullopt if empty
|
||||
// - bytes_read if success
|
||||
// - Error if buffer too small
|
||||
auto pop(MutRef<PacketHeader> out_header, Ref<Span<u8>> out_buffer)
|
||||
-> Result<Option<usize>>;
|
||||
|
||||
auto push(Const<u16> packet_id, Ref<Span<const u8>> data) -> Result<void>;
|
||||
|
||||
auto get_control_block() -> ControlBlock *;
|
||||
|
||||
[[nodiscard]] auto is_valid() const -> bool;
|
||||
|
||||
protected:
|
||||
RingBufferView(Ref<Span<u8>> buffer, Const<bool> is_owner);
|
||||
RingBufferView(Const<ControlBlock *> control_block, Ref<Span<u8>> buffer,
|
||||
Const<bool> is_owner);
|
||||
|
||||
private:
|
||||
Mut<u8 *> m_data_ptr{};
|
||||
Mut<u32> m_capacity{};
|
||||
Mut<ControlBlock *> m_control_block{};
|
||||
|
||||
private:
|
||||
auto write_wrapped(Const<u32> offset, Const<const void *> data,
|
||||
Const<u32> size) -> void;
|
||||
auto read_wrapped(Const<u32> offset, Const<void *> out_data, Const<u32> size)
|
||||
-> void;
|
||||
};
|
||||
|
||||
inline auto RingBufferView::default_instance() -> RingBufferView {
|
||||
return RingBufferView(nullptr, {}, false);
|
||||
}
|
||||
|
||||
inline auto RingBufferView::create(Ref<Span<u8>> buffer, Const<bool> is_owner)
|
||||
-> Result<RingBufferView> {
|
||||
if (buffer.size() <= sizeof(ControlBlock)) {
|
||||
return fail("Buffer too small for ControlBlock");
|
||||
}
|
||||
|
||||
if (!is_owner) {
|
||||
Const<ControlBlock *> cb = reinterpret_cast<ControlBlock *>(buffer.data());
|
||||
Const<u32> capacity =
|
||||
static_cast<u32>(buffer.size()) - sizeof(ControlBlock);
|
||||
if (cb->consumer.capacity != capacity) {
|
||||
return fail("Capacity mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
return RingBufferView(buffer, is_owner);
|
||||
}
|
||||
|
||||
inline auto RingBufferView::create(Const<ControlBlock *> control_block,
|
||||
Ref<Span<u8>> buffer, Const<bool> is_owner)
|
||||
-> Result<RingBufferView> {
|
||||
if (control_block == nullptr) {
|
||||
return fail("ControlBlock is null");
|
||||
}
|
||||
if (buffer.empty()) {
|
||||
return fail("Buffer is empty");
|
||||
}
|
||||
|
||||
return RingBufferView(control_block, buffer, is_owner);
|
||||
}
|
||||
|
||||
inline RingBufferView::RingBufferView(Ref<Span<u8>> buffer,
|
||||
Const<bool> is_owner) {
|
||||
m_control_block = reinterpret_cast<ControlBlock *>(buffer.data());
|
||||
m_data_ptr = buffer.data() + sizeof(ControlBlock);
|
||||
|
||||
m_capacity = static_cast<u32>(buffer.size()) - sizeof(ControlBlock);
|
||||
|
||||
if (is_owner) {
|
||||
m_control_block->consumer.capacity = m_capacity;
|
||||
m_control_block->producer.write_offset.store(0, std::memory_order_release);
|
||||
m_control_block->consumer.read_offset.store(0, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
inline RingBufferView::RingBufferView(Const<ControlBlock *> control_block,
|
||||
Ref<Span<u8>> buffer,
|
||||
Const<bool> is_owner) {
|
||||
m_control_block = control_block;
|
||||
m_data_ptr = buffer.data();
|
||||
m_capacity = static_cast<u32>(buffer.size());
|
||||
|
||||
if (is_owner) {
|
||||
m_control_block->consumer.capacity = m_capacity;
|
||||
m_control_block->producer.write_offset.store(0, std::memory_order_release);
|
||||
m_control_block->consumer.read_offset.store(0, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
inline auto RingBufferView::pop(MutRef<PacketHeader> out_header,
|
||||
Ref<Span<u8>> out_buffer)
|
||||
-> Result<Option<usize>> {
|
||||
Const<u32> write =
|
||||
m_control_block->producer.write_offset.load(std::memory_order_acquire);
|
||||
Const<u32> read =
|
||||
m_control_block->consumer.read_offset.load(std::memory_order_relaxed);
|
||||
Const<u32> cap = m_capacity;
|
||||
|
||||
if (read == write) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
read_wrapped(read, &out_header, sizeof(PacketHeader));
|
||||
|
||||
if (out_header.payload_size > out_buffer.size()) {
|
||||
return fail("Buffer too small: needed {}, provided {}",
|
||||
out_header.payload_size, out_buffer.size());
|
||||
}
|
||||
|
||||
if (out_header.payload_size > 0) {
|
||||
Const<u32> data_read_offset = (read + sizeof(PacketHeader)) % cap;
|
||||
read_wrapped(data_read_offset, out_buffer.data(), out_header.payload_size);
|
||||
}
|
||||
|
||||
Const<u32> new_read_offset =
|
||||
(read + sizeof(PacketHeader) + out_header.payload_size) % cap;
|
||||
m_control_block->consumer.read_offset.store(new_read_offset,
|
||||
std::memory_order_release);
|
||||
|
||||
return std::make_optional(static_cast<usize>(out_header.payload_size));
|
||||
}
|
||||
|
||||
inline auto RingBufferView::push(Const<u16> packet_id, Ref<Span<const u8>> data)
|
||||
-> Result<void> {
|
||||
if (data.size() > std::numeric_limits<u16>::max()) {
|
||||
return fail("Data size exceeds u16 limit");
|
||||
}
|
||||
|
||||
Const<u32> total_size = sizeof(PacketHeader) + static_cast<u32>(data.size());
|
||||
|
||||
Const<u32> read =
|
||||
m_control_block->consumer.read_offset.load(std::memory_order_acquire);
|
||||
Const<u32> write =
|
||||
m_control_block->producer.write_offset.load(std::memory_order_relaxed);
|
||||
Const<u32> cap = m_capacity;
|
||||
|
||||
Const<u32> free_space =
|
||||
(read <= write) ? (m_capacity - write) + read : (read - write);
|
||||
|
||||
// Leave 1 byte empty (prevent ambiguities)
|
||||
if (free_space <= total_size) {
|
||||
return fail("RingBuffer full");
|
||||
}
|
||||
|
||||
Const<PacketHeader> header{packet_id, static_cast<u16>(data.size())};
|
||||
write_wrapped(write, &header, sizeof(PacketHeader));
|
||||
|
||||
Const<u32> data_write_offset = (write + sizeof(PacketHeader)) % cap;
|
||||
|
||||
if (!data.empty()) {
|
||||
write_wrapped(data_write_offset, data.data(),
|
||||
static_cast<u32>(data.size()));
|
||||
}
|
||||
|
||||
Const<u32> new_write_offset = (data_write_offset + data.size()) % cap;
|
||||
m_control_block->producer.write_offset.store(new_write_offset,
|
||||
std::memory_order_release);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
inline auto RingBufferView::get_control_block() -> ControlBlock * {
|
||||
return m_control_block;
|
||||
}
|
||||
|
||||
inline auto RingBufferView::write_wrapped(Const<u32> offset,
|
||||
Const<const void *> data,
|
||||
Const<u32> size) -> void {
|
||||
if (offset + size <= m_capacity) {
|
||||
std::memcpy(m_data_ptr + offset, data, size);
|
||||
} else {
|
||||
Const<u32> first_chunk = m_capacity - offset;
|
||||
Const<u32> second_chunk = size - first_chunk;
|
||||
|
||||
Const<const u8 *> src = static_cast<const u8 *>(data);
|
||||
|
||||
std::memcpy(m_data_ptr + offset, src, first_chunk);
|
||||
std::memcpy(m_data_ptr, src + first_chunk, second_chunk);
|
||||
}
|
||||
}
|
||||
|
||||
inline auto RingBufferView::read_wrapped(Const<u32> offset,
|
||||
Const<void *> out_data,
|
||||
Const<u32> size) -> void {
|
||||
if (offset + size <= m_capacity) {
|
||||
std::memcpy(out_data, m_data_ptr + offset, size);
|
||||
} else {
|
||||
Const<u32> first_chunk = m_capacity - offset;
|
||||
Const<u32> second_chunk = size - first_chunk;
|
||||
|
||||
Const<u8 *> dst = static_cast<u8 *>(out_data);
|
||||
|
||||
std::memcpy(dst, m_data_ptr + offset, first_chunk);
|
||||
std::memcpy(dst + first_chunk, m_data_ptr, second_chunk);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto RingBufferView::is_valid() const -> bool {
|
||||
return m_control_block && m_data_ptr && m_capacity;
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
71
Src/IACore/inc/IACore/AsyncOps.hpp
Normal file
71
Src/IACore/inc/IACore/AsyncOps.hpp
Normal file
@ -0,0 +1,71 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <stop_token>
|
||||
|
||||
namespace IACore {
|
||||
class AsyncOps {
|
||||
public:
|
||||
using TaskTag = u64;
|
||||
using WorkerId = u16;
|
||||
|
||||
static constexpr Const<WorkerId> MAIN_THREAD_WORKER_ID = 0;
|
||||
|
||||
enum class Priority : u8 { High, Normal };
|
||||
|
||||
struct Schedule {
|
||||
Mut<std::atomic<i32>> counter{0};
|
||||
};
|
||||
|
||||
public:
|
||||
static auto initialize_scheduler(Const<u8> worker_count = 0) -> Result<void>;
|
||||
static auto terminate_scheduler() -> void;
|
||||
|
||||
static auto schedule_task(Mut<std::function<void(Const<WorkerId>)>> task,
|
||||
Const<TaskTag> tag, Mut<Schedule *> schedule,
|
||||
Const<Priority> priority = Priority::Normal)
|
||||
-> void;
|
||||
|
||||
static auto cancel_tasks_of_tag(Const<TaskTag> tag) -> void;
|
||||
|
||||
static auto wait_for_schedule_completion(Mut<Schedule *> schedule) -> void;
|
||||
|
||||
static auto run_task(Mut<std::function<void()>> task) -> void;
|
||||
|
||||
IA_NODISCARD static auto get_worker_count() -> WorkerId;
|
||||
|
||||
private:
|
||||
struct ScheduledTask {
|
||||
Mut<TaskTag> tag{};
|
||||
Mut<Schedule *> schedule_handle{};
|
||||
Mut<std::function<void(Const<WorkerId>)>> task{};
|
||||
};
|
||||
|
||||
static auto schedule_worker_loop(Mut<std::stop_token> stop_token,
|
||||
Const<WorkerId> worker_id) -> void;
|
||||
|
||||
private:
|
||||
static Mut<std::mutex> s_queue_mutex;
|
||||
static Mut<std::condition_variable> s_wake_condition;
|
||||
static Mut<Vec<std::jthread>> s_schedule_workers;
|
||||
static Mut<std::deque<ScheduledTask>> s_high_priority_queue;
|
||||
static Mut<std::deque<ScheduledTask>> s_normal_priority_queue;
|
||||
};
|
||||
} // namespace IACore
|
||||
63
Src/IACore/inc/IACore/CLI.hpp
Normal file
63
Src/IACore/inc/IACore/CLI.hpp
Normal file
@ -0,0 +1,63 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
namespace IACore {
|
||||
class CLIParser {
|
||||
/*
|
||||
* PLEASE READ
|
||||
*
|
||||
* CLIParser is still very much in it's baby stages.
|
||||
* Subject to heavy and frequent changes, use with
|
||||
* caution!
|
||||
*/
|
||||
|
||||
public:
|
||||
CLIParser(Const<Span<Const<String>>> args);
|
||||
~CLIParser() = default;
|
||||
|
||||
public:
|
||||
IA_NODISCARD auto remaining() const -> bool {
|
||||
return m_current_arg < m_arg_list.end();
|
||||
}
|
||||
|
||||
IA_NODISCARD auto peek() const -> StringView {
|
||||
if (!remaining())
|
||||
return "";
|
||||
return *m_current_arg;
|
||||
}
|
||||
|
||||
auto next() -> StringView {
|
||||
if (!remaining())
|
||||
return "";
|
||||
return *m_current_arg++;
|
||||
}
|
||||
|
||||
auto consume(Ref<StringView> expected) -> bool {
|
||||
if (peek() == expected) {
|
||||
next();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
Const<Span<Const<String>>> m_arg_list;
|
||||
Mut<Span<Const<String>>::const_iterator> m_current_arg;
|
||||
};
|
||||
} // namespace IACore
|
||||
47
Src/IACore/inc/IACore/DataOps.hpp
Normal file
47
Src/IACore/inc/IACore/DataOps.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
namespace IACore {
|
||||
class DataOps {
|
||||
public:
|
||||
enum class CompressionType { None, Gzip, Zlib };
|
||||
|
||||
public:
|
||||
static auto hash_fnv1a(Ref<String> string) -> u32;
|
||||
static auto hash_fnv1a(Ref<Span<Const<u8>>> data) -> u32;
|
||||
|
||||
static auto hash_xxhash(Ref<String> string, Const<u32> seed = 0) -> u32;
|
||||
static auto hash_xxhash(Ref<Span<Const<u8>>> data, Const<u32> seed = 0)
|
||||
-> u32;
|
||||
|
||||
static auto crc32(Ref<Span<Const<u8>>> data) -> u32;
|
||||
|
||||
static auto detect_compression(Const<Span<Const<u8>>> data)
|
||||
-> CompressionType;
|
||||
|
||||
static auto gzip_inflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>>;
|
||||
static auto gzip_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>>;
|
||||
|
||||
static auto zlib_inflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>>;
|
||||
static auto zlib_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>>;
|
||||
|
||||
static auto zstd_inflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>>;
|
||||
static auto zstd_deflate(Ref<Span<Const<u8>>> data) -> Result<Vec<u8>>;
|
||||
};
|
||||
} // namespace IACore
|
||||
151
Src/IACore/inc/IACore/DynamicLib.hpp
Normal file
151
Src/IACore/inc/IACore/DynamicLib.hpp
Normal file
@ -0,0 +1,151 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#if !IA_PLATFORM_WINDOWS
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace IACore {
|
||||
|
||||
class DynamicLib {
|
||||
public:
|
||||
IA_NODISCARD static auto load(Ref<String> search_path, Ref<String> name)
|
||||
-> Result<DynamicLib> {
|
||||
namespace fs = std::filesystem;
|
||||
Mut<Path> full_path = fs::path(search_path) / name;
|
||||
|
||||
if (!full_path.has_extension()) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
full_path += ".dll";
|
||||
#elif IA_PLATFORM_APPLE
|
||||
full_path += ".dylib";
|
||||
#else
|
||||
full_path += ".so";
|
||||
#endif
|
||||
}
|
||||
|
||||
Mut<DynamicLib> lib;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Const<HMODULE> h = LoadLibraryA(full_path.string().c_str());
|
||||
if (!h) {
|
||||
return fail(get_windows_error());
|
||||
}
|
||||
lib.m_handle = static_cast<void *>(h);
|
||||
#else
|
||||
Mut<void *> h = dlopen(full_path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||
if (!h) {
|
||||
Const<char *> err = dlerror();
|
||||
return fail(err ? err : "Unknown dlopen error");
|
||||
}
|
||||
lib.m_handle = h;
|
||||
#endif
|
||||
|
||||
return lib;
|
||||
}
|
||||
|
||||
DynamicLib() = default;
|
||||
|
||||
DynamicLib(ForwardRef<DynamicLib> other) noexcept : m_handle(other.m_handle) {
|
||||
other.m_handle = nullptr;
|
||||
}
|
||||
|
||||
auto operator=(ForwardRef<DynamicLib> other) noexcept -> MutRef<DynamicLib> {
|
||||
if (this != &other) {
|
||||
unload();
|
||||
m_handle = other.m_handle;
|
||||
other.m_handle = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
DynamicLib(Ref<DynamicLib>) = delete;
|
||||
auto operator=(Ref<DynamicLib>) -> MutRef<DynamicLib> = delete;
|
||||
|
||||
~DynamicLib() { unload(); }
|
||||
|
||||
IA_NODISCARD auto get_symbol(Ref<String> name) const -> Result<void *> {
|
||||
if (!m_handle) {
|
||||
return fail("Library not loaded");
|
||||
}
|
||||
|
||||
Mut<void *> sym = nullptr;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
sym = static_cast<void *>(
|
||||
GetProcAddress(static_cast<HMODULE>(m_handle), name.c_str()));
|
||||
if (!sym) {
|
||||
return fail(get_windows_error());
|
||||
}
|
||||
#else
|
||||
dlerror(); // Clear prev errors
|
||||
sym = dlsym(m_handle, name.c_str());
|
||||
if (Const<char *> err = dlerror()) {
|
||||
return fail(err);
|
||||
}
|
||||
#endif
|
||||
|
||||
return sym;
|
||||
}
|
||||
|
||||
template <typename FuncT>
|
||||
IA_NODISCARD auto get_function(Ref<String> name) const -> Result<FuncT> {
|
||||
Mut<void *> sym = nullptr;
|
||||
sym = OX_TRY(get_symbol(name));
|
||||
return reinterpret_cast<FuncT>(sym);
|
||||
}
|
||||
|
||||
void unload() {
|
||||
if (m_handle) {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
FreeLibrary(static_cast<HMODULE>(m_handle));
|
||||
#else
|
||||
dlclose(m_handle);
|
||||
#endif
|
||||
m_handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
IA_NODISCARD auto is_loaded() const -> bool { return m_handle != nullptr; }
|
||||
|
||||
private:
|
||||
Mut<void *> m_handle = nullptr;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
static auto get_windows_error() -> String {
|
||||
Const<DWORD> error_id = ::GetLastError();
|
||||
if (error_id == 0) {
|
||||
return String();
|
||||
}
|
||||
|
||||
Mut<LPSTR> message_buffer = nullptr;
|
||||
Const<usize> size = FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
reinterpret_cast<LPSTR>(&message_buffer), 0, nullptr);
|
||||
|
||||
Const<String> message(message_buffer, size);
|
||||
LocalFree(message_buffer);
|
||||
return "Win32 Error: " + message;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace IACore
|
||||
99
Src/IACore/inc/IACore/Environment.hpp
Normal file
99
Src/IACore/inc/IACore/Environment.hpp
Normal file
@ -0,0 +1,99 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace IACore {
|
||||
|
||||
class Environment {
|
||||
public:
|
||||
static auto find(Ref<String> name) -> Option<String> {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Const<u32> buffer_size =
|
||||
static_cast<u32>(GetEnvironmentVariableA(name.c_str(), nullptr, 0));
|
||||
|
||||
if (buffer_size == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Mut<String> result;
|
||||
result.resize(buffer_size);
|
||||
|
||||
Const<u32> actual_size = static_cast<u32>(
|
||||
GetEnvironmentVariableA(name.c_str(), result.data(), buffer_size));
|
||||
|
||||
if (actual_size == 0 || actual_size > buffer_size) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
result.resize(actual_size);
|
||||
return result;
|
||||
|
||||
#else
|
||||
Const<char *> val = std::getenv(name.c_str());
|
||||
if (val == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return String(val);
|
||||
#endif
|
||||
}
|
||||
|
||||
static auto get(Ref<String> name, Ref<String> default_value = "") -> String {
|
||||
return find(name).value_or(default_value);
|
||||
}
|
||||
|
||||
static auto set(Ref<String> name, Ref<String> value) -> Result<void> {
|
||||
if (name.empty()) {
|
||||
return fail("Environment variable name cannot be empty");
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
if (SetEnvironmentVariableA(name.c_str(), value.c_str()) == 0) {
|
||||
return fail("Failed to set environment variable: {}", name);
|
||||
}
|
||||
#else
|
||||
if (setenv(name.c_str(), value.c_str(), 1) != 0) {
|
||||
return fail("Failed to set environment variable: {}", name);
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
static auto unset(Ref<String> name) -> Result<void> {
|
||||
if (name.empty()) {
|
||||
return fail("Environment variable name cannot be empty");
|
||||
}
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
if (SetEnvironmentVariableA(name.c_str(), nullptr) == 0) {
|
||||
return fail("Failed to unset environment variable: {}", name);
|
||||
}
|
||||
#else
|
||||
if (unsetenv(name.c_str()) != 0) {
|
||||
return fail("Failed to unset environment variable: {}", name);
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
static auto exists(Ref<String> name) -> bool {
|
||||
return find(name).has_value();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace IACore
|
||||
127
Src/IACore/inc/IACore/FileOps.hpp
Normal file
127
Src/IACore/inc/IACore/FileOps.hpp
Normal file
@ -0,0 +1,127 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
#include <IACore/StreamReader.hpp>
|
||||
#include <IACore/StreamWriter.hpp>
|
||||
#include <tuple>
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
using NativeFileHandle = HANDLE;
|
||||
static constexpr ox::Const<NativeFileHandle> INVALID_FILE_HANDLE =
|
||||
INVALID_HANDLE_VALUE;
|
||||
#else
|
||||
using NativeFileHandle = int;
|
||||
static constexpr ox::Const<NativeFileHandle> INVALID_FILE_HANDLE = -1;
|
||||
#endif
|
||||
|
||||
namespace IACore {
|
||||
|
||||
class FileOps {
|
||||
public:
|
||||
class MemoryMappedRegion;
|
||||
|
||||
enum class FileAccess : u8 {
|
||||
Read, // Read-only
|
||||
Write, // Write-only
|
||||
ReadWrite // Read and Write
|
||||
};
|
||||
|
||||
enum class FileMode : u8 {
|
||||
OpenExisting, // Fails if file doesn't exist
|
||||
OpenAlways, // Opens if exists, creates if not
|
||||
CreateNew, // Fails if file exists
|
||||
CreateAlways, // Overwrites existing
|
||||
TruncateExisting // Opens existing and clears it
|
||||
};
|
||||
|
||||
static auto native_open_file(Ref<Path> path, Const<FileAccess> access,
|
||||
Const<FileMode> mode,
|
||||
Const<u32> permissions = 0644)
|
||||
-> Result<NativeFileHandle>;
|
||||
|
||||
static auto native_close_file(Const<NativeFileHandle> handle) -> void;
|
||||
|
||||
public:
|
||||
static auto normalize_executable_path(Ref<Path> path) -> Path;
|
||||
|
||||
public:
|
||||
static auto unmap_file(Const<const u8 *> mapped_ptr) -> void;
|
||||
|
||||
static auto map_file(Ref<Path> path, MutRef<usize> size)
|
||||
-> Result<const u8 *>;
|
||||
|
||||
// @param `is_owner` true to allocate/truncate. false to just open.
|
||||
static auto map_shared_memory(Ref<String> name, Const<usize> size,
|
||||
Const<bool> is_owner) -> Result<u8 *>;
|
||||
|
||||
static auto unlink_shared_memory(Ref<String> name) -> void;
|
||||
|
||||
static auto stream_from_file(Ref<Path> path) -> Result<StreamReader>;
|
||||
|
||||
static auto stream_to_file(Ref<Path> path, Const<bool> overwrite = false)
|
||||
-> Result<StreamWriter>;
|
||||
|
||||
static auto read_text_file(Ref<Path> path) -> Result<String>;
|
||||
|
||||
static auto read_binary_file(Ref<Path> path) -> Result<Vec<u8>>;
|
||||
|
||||
static auto write_text_file(Ref<Path> path, Ref<String> contents,
|
||||
Const<bool> overwrite = false) -> Result<usize>;
|
||||
|
||||
static auto write_binary_file(Ref<Path> path, Const<Span<const u8>> contents,
|
||||
Const<bool> overwrite = false) -> Result<usize>;
|
||||
|
||||
private:
|
||||
static Mut<HashMap<const u8 *, std::tuple<void *, void *, void *>>>
|
||||
s_mapped_files;
|
||||
};
|
||||
|
||||
class FileOps::MemoryMappedRegion {
|
||||
public:
|
||||
MemoryMappedRegion() = default;
|
||||
~MemoryMappedRegion();
|
||||
|
||||
MemoryMappedRegion(Ref<MemoryMappedRegion>) = delete;
|
||||
auto operator=(Ref<MemoryMappedRegion>) -> MemoryMappedRegion & = delete;
|
||||
|
||||
MemoryMappedRegion(ForwardRef<MemoryMappedRegion> other) noexcept;
|
||||
auto operator=(ForwardRef<MemoryMappedRegion> other) noexcept
|
||||
-> MemoryMappedRegion &;
|
||||
|
||||
auto map(Const<NativeFileHandle> handle, Const<u64> offset, Const<usize> size)
|
||||
-> Result<void>;
|
||||
|
||||
auto unmap() -> void;
|
||||
auto flush() -> void;
|
||||
|
||||
[[nodiscard]] auto get_ptr() const -> u8 * { return m_ptr; }
|
||||
|
||||
[[nodiscard]] auto get_size() const -> usize { return m_size; }
|
||||
|
||||
[[nodiscard]] auto is_valid() const -> bool { return m_ptr != nullptr; }
|
||||
|
||||
private:
|
||||
Mut<u8 *> m_ptr = nullptr;
|
||||
Mut<usize> m_size = 0;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Mut<HANDLE> m_map_handle = NULL;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace IACore
|
||||
95
Src/IACore/inc/IACore/Http/Client.hpp
Normal file
95
Src/IACore/inc/IACore/Http/Client.hpp
Normal file
@ -0,0 +1,95 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/Http/Common.hpp>
|
||||
#include <IACore/JSON.hpp>
|
||||
|
||||
namespace IACore {
|
||||
class HttpClient : public HttpCommon {
|
||||
public:
|
||||
static auto create(Ref<String> host) -> Result<Box<HttpClient>>;
|
||||
|
||||
~HttpClient();
|
||||
|
||||
HttpClient(ForwardRef<HttpClient>) = default;
|
||||
HttpClient(Ref<HttpClient>) = delete;
|
||||
auto operator=(ForwardRef<HttpClient>) -> MutRef<HttpClient> = default;
|
||||
auto operator=(Ref<HttpClient>) -> MutRef<HttpClient> = delete;
|
||||
|
||||
public:
|
||||
auto raw_get(Ref<String> path, Span<Const<Header>> headers,
|
||||
Const<char> *default_content_type =
|
||||
"application/x-www-form-urlencoded") -> Result<String>;
|
||||
|
||||
auto raw_post(Ref<String> path, Span<Const<Header>> headers, Ref<String> body,
|
||||
Const<char> *default_content_type =
|
||||
"application/x-www-form-urlencoded") -> Result<String>;
|
||||
|
||||
template <typename ResponseType>
|
||||
auto json_get(Ref<String> path, Span<Const<Header>> headers)
|
||||
-> Result<ResponseType>;
|
||||
|
||||
template <typename PayloadType, typename ResponseType>
|
||||
auto json_post(Ref<String> path, Span<Const<Header>> headers,
|
||||
Ref<PayloadType> body) -> Result<ResponseType>;
|
||||
|
||||
// Certificate verification is enabled by default
|
||||
auto enable_certificate_verification() -> void;
|
||||
auto disable_certificate_verification() -> void;
|
||||
|
||||
public:
|
||||
auto last_response_code() -> EResponseCode { return m_last_response_code; }
|
||||
|
||||
private:
|
||||
Mut<httplib::Client> m_client;
|
||||
Mut<EResponseCode> m_last_response_code;
|
||||
|
||||
private:
|
||||
auto preprocess_response(Ref<String> response) -> String;
|
||||
|
||||
protected:
|
||||
explicit HttpClient(ForwardRef<httplib::Client> client);
|
||||
};
|
||||
|
||||
template <typename ResponseType>
|
||||
auto HttpClient::json_get(Ref<String> path, Span<Const<Header>> headers)
|
||||
-> Result<ResponseType> {
|
||||
Const<String> raw_response =
|
||||
OX_TRY(raw_get(path, headers, "application/json"));
|
||||
|
||||
if (last_response_code() != EResponseCode::OK) {
|
||||
return fail("Server responded with code {}",
|
||||
static_cast<i32>(last_response_code()));
|
||||
}
|
||||
return Json::parse_to_struct<ResponseType>(raw_response);
|
||||
}
|
||||
|
||||
template <typename PayloadType, typename ResponseType>
|
||||
auto HttpClient::json_post(Ref<String> path, Span<Const<Header>> headers,
|
||||
Ref<PayloadType> body) -> Result<ResponseType> {
|
||||
Const<String> encoded_body = OX_TRY(Json::encode_struct(body));
|
||||
|
||||
Const<String> raw_response =
|
||||
OX_TRY(raw_post(path, headers, encoded_body, "application/json"));
|
||||
|
||||
if (last_response_code() != EResponseCode::OK) {
|
||||
return fail("Server responded with code {}",
|
||||
static_cast<i32>(last_response_code()));
|
||||
}
|
||||
return Json::parse_to_struct<ResponseType>(raw_response);
|
||||
}
|
||||
} // namespace IACore
|
||||
155
Src/IACore/inc/IACore/Http/Common.hpp
Normal file
155
Src/IACore/inc/IACore/Http/Common.hpp
Normal file
@ -0,0 +1,155 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/JSON.hpp>
|
||||
|
||||
#include <httplib.h>
|
||||
|
||||
namespace IACore {
|
||||
class HttpCommon {
|
||||
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 : i32 {
|
||||
// 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 = Pair<String, String>;
|
||||
|
||||
static auto url_encode(Ref<String> value) -> String;
|
||||
static auto url_decode(Ref<String> value) -> String;
|
||||
|
||||
static auto header_type_to_string(Const<EHeaderType> type) -> String;
|
||||
|
||||
static inline auto create_header(Const<EHeaderType> key, Ref<String> value)
|
||||
-> Header;
|
||||
static inline auto create_header(Ref<String> key, Ref<String> value)
|
||||
-> Header;
|
||||
|
||||
static auto is_success_response_code(Const<EResponseCode> code) -> bool;
|
||||
|
||||
protected:
|
||||
HttpCommon() = default;
|
||||
};
|
||||
|
||||
auto HttpCommon::create_header(Const<EHeaderType> key, Ref<String> value)
|
||||
-> HttpCommon::Header {
|
||||
return Header{header_type_to_string(key), value};
|
||||
}
|
||||
|
||||
auto HttpCommon::create_header(Ref<String> key, Ref<String> value)
|
||||
-> HttpCommon::Header {
|
||||
return Header{key, value};
|
||||
}
|
||||
} // namespace IACore
|
||||
151
Src/IACore/inc/IACore/Http/Server.hpp
Normal file
151
Src/IACore/inc/IACore/Http/Server.hpp
Normal file
@ -0,0 +1,151 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/Http/Common.hpp>
|
||||
#include <IACore/JSON.hpp>
|
||||
#include <functional>
|
||||
|
||||
namespace IACore {
|
||||
class HttpServer : public HttpCommon {
|
||||
public:
|
||||
struct Request {
|
||||
Mut<String> path;
|
||||
Mut<String> method;
|
||||
Mut<String> body;
|
||||
Mut<HashMap<String, String>> headers;
|
||||
Mut<HashMap<String, String>> params; // Query params
|
||||
Mut<HashMap<String, String>> path_params; // Path params (like /object/:id)
|
||||
|
||||
[[nodiscard]] auto get_header(Ref<String> key) const -> String;
|
||||
[[nodiscard]] auto get_param(Ref<String> key) const -> String;
|
||||
[[nodiscard]] auto get_path_param(Ref<String> key) const -> String;
|
||||
|
||||
[[nodiscard]] auto has_header(Ref<String> key) const -> bool;
|
||||
[[nodiscard]] auto has_param(Ref<String> key) const -> bool;
|
||||
[[nodiscard]] auto has_path_param(Ref<String> key) const -> bool;
|
||||
};
|
||||
|
||||
struct Response {
|
||||
Mut<EResponseCode> code = EResponseCode::OK;
|
||||
Mut<String> body;
|
||||
Mut<HashMap<String, String>> headers;
|
||||
Mut<String> content_type = "text/plain";
|
||||
|
||||
void set_content(Ref<String> content, Ref<String> type);
|
||||
void set_status(Const<EResponseCode> status_code);
|
||||
void add_header(Ref<String> key, Ref<String> value);
|
||||
};
|
||||
|
||||
using Handler = std::function<void(Ref<Request>, MutRef<Response>)>;
|
||||
|
||||
public:
|
||||
static auto create() -> Result<Box<HttpServer>>;
|
||||
|
||||
~HttpServer();
|
||||
|
||||
HttpServer(HttpServer &&) = delete;
|
||||
HttpServer(const HttpServer &) = delete;
|
||||
auto operator=(HttpServer &&) -> HttpServer & = delete;
|
||||
auto operator=(const HttpServer &) -> HttpServer & = delete;
|
||||
|
||||
auto listen(Ref<String> host, Const<u32> port) -> Result<void>;
|
||||
void stop();
|
||||
auto is_running() const -> bool;
|
||||
|
||||
void get(Ref<String> pattern, Const<Handler> handler);
|
||||
void post(Ref<String> pattern, Const<Handler> handler);
|
||||
void put(Ref<String> pattern, Const<Handler> handler);
|
||||
void del(Ref<String> pattern, Const<Handler> handler);
|
||||
void options(Ref<String> pattern, Const<Handler> handler);
|
||||
|
||||
template <typename ResponseType>
|
||||
void
|
||||
json_get(Ref<String> pattern,
|
||||
Const<std::function<Result<ResponseType>(Ref<Request>)>> handler);
|
||||
|
||||
template <typename PayloadType, typename ResponseType>
|
||||
void json_post(
|
||||
Ref<String> pattern,
|
||||
Const<std::function<Result<ResponseType>(Ref<PayloadType>)>> handler);
|
||||
|
||||
protected:
|
||||
HttpServer();
|
||||
|
||||
private:
|
||||
Mut<httplib::Server> m_server;
|
||||
|
||||
void register_handler(Ref<String> method, Ref<String> pattern,
|
||||
Const<Handler> handler);
|
||||
};
|
||||
|
||||
template <typename ResponseType>
|
||||
void HttpServer::json_get(
|
||||
Ref<String> pattern,
|
||||
Const<std::function<Result<ResponseType>(Ref<Request>)>> handler) {
|
||||
get(pattern, [handler](Ref<Request> req, MutRef<Response> res) {
|
||||
Const<Result<ResponseType>> result = handler(req);
|
||||
if (!result) {
|
||||
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
|
||||
res.set_content(result.error(), "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
Const<Result<String>> json_res = Json::encode_struct(*result);
|
||||
if (!json_res) {
|
||||
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
|
||||
res.set_content("Failed to encode JSON response", "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
res.set_status(EResponseCode::OK);
|
||||
res.set_content(*json_res, "application/json");
|
||||
});
|
||||
}
|
||||
|
||||
template <typename PayloadType, typename ResponseType>
|
||||
void HttpServer::json_post(
|
||||
Ref<String> pattern,
|
||||
Const<std::function<Result<ResponseType>(Ref<PayloadType>)>> handler) {
|
||||
post(pattern, [handler](Ref<Request> req, MutRef<Response> res) {
|
||||
Const<Result<PayloadType>> payload =
|
||||
Json::parse_to_struct<PayloadType>(req.body);
|
||||
if (!payload) {
|
||||
res.set_status(EResponseCode::BAD_REQUEST);
|
||||
res.set_content("Invalid JSON Payload", "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
Const<Result<ResponseType>> result = handler(*payload);
|
||||
if (!result) {
|
||||
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
|
||||
res.set_content(result.error(), "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
Const<Result<String>> json_res = Json::encode_struct(*result);
|
||||
if (!json_res) {
|
||||
res.set_status(EResponseCode::INTERNAL_SERVER_ERROR);
|
||||
res.set_content("Failed to encode JSON response", "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
res.set_status(EResponseCode::OK);
|
||||
res.set_content(*json_res, "application/json");
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
66
Src/IACore/inc/IACore/IACore.hpp
Normal file
66
Src/IACore/inc/IACore/IACore.hpp
Normal file
@ -0,0 +1,66 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/Logger.hpp>
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#define IACORE_MAIN() \
|
||||
auto _app_entry(IACore::Ref<IACore::Vec<IACore::String>> args) \
|
||||
-> IACore::Result<IACore::i32>; \
|
||||
auto main(Const<int> argc, Mut<char *> argv[]) -> int { \
|
||||
IACore::Mut<IACore::i32> exit_code = 0; \
|
||||
IACore::initialize(); \
|
||||
IACore::Mut<IACore::Vec<IACore::String>> args; \
|
||||
args.reserve(static_cast<IACore::usize>(argc)); \
|
||||
for (IACore::Mut<IACore::i32> i = 0; i < argc; ++i) { \
|
||||
args.push_back(argv[i]); \
|
||||
} \
|
||||
IACore::Const<IACore::Result<IACore::i32>> result = _app_entry(args); \
|
||||
if (!result) { \
|
||||
IACore::Logger::error("Application exited with an error: '{}'.", \
|
||||
result.error()); \
|
||||
exit_code = -20; \
|
||||
} else { \
|
||||
exit_code = *result; \
|
||||
if (exit_code == 0) { \
|
||||
IACore::Logger::info("Application exited successfully."); \
|
||||
} else { \
|
||||
IACore::Logger::error("Application exited with error code: {}.", \
|
||||
exit_code); \
|
||||
} \
|
||||
} \
|
||||
IACore::terminate(); \
|
||||
return exit_code; \
|
||||
} \
|
||||
auto _app_entry(IACore::Ref<IACore::Vec<IACore::String>> args) \
|
||||
-> IACore::Result<IACore::i32>
|
||||
|
||||
namespace IACore {
|
||||
// Must be called from main thread
|
||||
// Safe to call multiple times but, given every initialize call is paired with a
|
||||
// corresponding terminate call
|
||||
auto initialize() -> void;
|
||||
|
||||
// Must be called from same thread as initialize
|
||||
// Safe to call multiple times but, given every initialize call is paired with a
|
||||
// corresponding terminate call
|
||||
auto terminate() -> void;
|
||||
|
||||
auto is_initialized() -> bool;
|
||||
|
||||
auto is_main_thread() -> bool;
|
||||
} // namespace IACore
|
||||
293
Src/IACore/inc/IACore/IATest.hpp
Normal file
293
Src/IACore/inc/IACore/IATest.hpp
Normal file
@ -0,0 +1,293 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Macros
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#define __iat_micro_test(call) \
|
||||
if (!(call)) \
|
||||
return false
|
||||
|
||||
#define IAT_CHECK(v) __iat_micro_test(_test((v), #v))
|
||||
#define IAT_CHECK_NOT(v) __iat_micro_test(_test_not((v), "NOT " #v))
|
||||
#define IAT_CHECK_EQ(lhs, rhs) \
|
||||
__iat_micro_test(_test_eq((lhs), (rhs), #lhs " == " #rhs))
|
||||
#define IAT_CHECK_NEQ(lhs, rhs) \
|
||||
__iat_micro_test(_test_neq((lhs), (rhs), #lhs " != " #rhs))
|
||||
|
||||
#define IAT_CHECK_APPROX(lhs, rhs) \
|
||||
__iat_micro_test(_test_approx((lhs), (rhs), #lhs " ~= " #rhs))
|
||||
|
||||
#define IAT_UNIT(func) _test_unit([this]() { return this->func(); }, #func)
|
||||
#define IAT_NAMED_UNIT(n, func) _test_unit([this]() { return this->func(); }, n)
|
||||
|
||||
#define IAT_BLOCK(name) class name : public IACore::Test::Block
|
||||
|
||||
#define IAT_BEGIN_BLOCK(_group, _name) \
|
||||
class _group##_##_name : public IACore::Test::Block { \
|
||||
public: \
|
||||
[[nodiscard]] auto get_name() const -> const char * override { \
|
||||
return #_group "::" #_name; \
|
||||
} \
|
||||
\
|
||||
private:
|
||||
|
||||
#define IAT_END_BLOCK() \
|
||||
} \
|
||||
;
|
||||
|
||||
#define IAT_BEGIN_TEST_LIST() \
|
||||
public: \
|
||||
auto declare_tests() -> void override {
|
||||
#define IAT_ADD_TEST(name) IAT_UNIT(name)
|
||||
#define IAT_END_TEST_LIST() \
|
||||
} \
|
||||
\
|
||||
private:
|
||||
|
||||
namespace IACore::Test {
|
||||
// -------------------------------------------------------------------------
|
||||
// String Conversion Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
template <typename T> auto to_string(Ref<T> value) -> String {
|
||||
if constexpr (std::is_arithmetic_v<T>) {
|
||||
return std::to_string(value);
|
||||
} else if constexpr (std::is_same_v<T, String> ||
|
||||
std::is_same_v<T, const char *> ||
|
||||
std::is_same_v<T, char *>) {
|
||||
return String("\"") + String(value) + "\"";
|
||||
} else {
|
||||
return "{Object}";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> auto to_string(T *value) -> String {
|
||||
if (value == nullptr) {
|
||||
return "nullptr";
|
||||
}
|
||||
return std::format("ptr({})", static_cast<const void *>(value));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Types
|
||||
// -------------------------------------------------------------------------
|
||||
using TestFunctor = std::function<bool()>;
|
||||
|
||||
struct TestUnit {
|
||||
Mut<String> name;
|
||||
Mut<TestFunctor> functor;
|
||||
};
|
||||
|
||||
class Block {
|
||||
public:
|
||||
virtual ~Block() = default;
|
||||
[[nodiscard]] virtual auto get_name() const -> const char * = 0;
|
||||
virtual auto declare_tests() -> void = 0;
|
||||
|
||||
auto units() -> MutRef<Vec<TestUnit>> { return m_units; }
|
||||
|
||||
protected:
|
||||
template <typename T1, typename T2>
|
||||
auto _test_eq(Ref<T1> lhs, Ref<T2> rhs, Const<const char *> description)
|
||||
-> bool {
|
||||
if (lhs != rhs) {
|
||||
print_fail(description, to_string(lhs), to_string(rhs));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T1, typename T2>
|
||||
auto _test_neq(Ref<T1> lhs, Ref<T2> rhs, Const<const char *> description)
|
||||
-> bool {
|
||||
if (lhs == rhs) {
|
||||
print_fail(description, to_string(lhs), "NOT " + to_string(rhs));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto _test_approx(Const<T> lhs, Const<T> rhs, Const<const char *> description)
|
||||
-> bool {
|
||||
static_assert(std::is_floating_point_v<T>,
|
||||
"Approx only works for floats/doubles");
|
||||
Const<T> diff = std::abs(lhs - rhs);
|
||||
if (diff > static_cast<T>(0.0001)) {
|
||||
print_fail(description, to_string(lhs), to_string(rhs));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto _test(Const<bool> value, Const<const char *> description) -> bool {
|
||||
if (!value) {
|
||||
std::cout << console::BLUE << " " << description << "... "
|
||||
<< console::RED << "FAILED" << console::RESET << "\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto _test_not(Const<bool> value, Const<const char *> description) -> bool {
|
||||
if (value) {
|
||||
std::cout << console::BLUE << " " << description << "... "
|
||||
<< console::RED << "FAILED" << console::RESET << "\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto _test_unit(Mut<TestFunctor> functor, Const<const char *> name) -> void {
|
||||
m_units.push_back({name, std::move(functor)});
|
||||
}
|
||||
|
||||
private:
|
||||
auto print_fail(Const<const char *> desc, Ref<String> v1, Ref<String> v2)
|
||||
-> void {
|
||||
std::cout << console::BLUE << " " << desc << "... " << console::RED
|
||||
<< "FAILED" << console::RESET << "\n";
|
||||
std::cout << console::RED << " Expected: " << v2 << console::RESET
|
||||
<< "\n";
|
||||
std::cout << console::RED << " Actual: " << v1 << console::RESET
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
Mut<Vec<TestUnit>> m_units;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept ValidBlockClass = std::derived_from<T, Block>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Runner
|
||||
// -------------------------------------------------------------------------
|
||||
template <bool StopOnFail = false, bool IsVerbose = false> class Runner {
|
||||
public:
|
||||
Runner() = default;
|
||||
|
||||
~Runner() { summarize(); }
|
||||
|
||||
template <typename BlockClass>
|
||||
requires ValidBlockClass<BlockClass>
|
||||
auto test_block() -> void;
|
||||
|
||||
private:
|
||||
auto summarize() -> void;
|
||||
|
||||
Mut<usize> m_test_count{0};
|
||||
Mut<usize> m_fail_count{0};
|
||||
Mut<usize> m_block_count{0};
|
||||
};
|
||||
|
||||
template <bool StopOnFail, bool IsVerbose>
|
||||
template <typename BlockClass>
|
||||
requires ValidBlockClass<BlockClass>
|
||||
auto Runner<StopOnFail, IsVerbose>::test_block() -> void {
|
||||
m_block_count++;
|
||||
Mut<BlockClass> b;
|
||||
b.declare_tests();
|
||||
|
||||
std::cout << console::MAGENTA << "Testing [" << b.get_name() << "]..."
|
||||
<< console::RESET << "\n";
|
||||
|
||||
for (MutRef<TestUnit> v : b.units()) {
|
||||
m_test_count++;
|
||||
if constexpr (IsVerbose) {
|
||||
std::cout << console::YELLOW << " Testing " << v.name << "...\n"
|
||||
<< console::RESET;
|
||||
}
|
||||
|
||||
Const<bool> result = v.functor();
|
||||
|
||||
if (!result) {
|
||||
m_fail_count++;
|
||||
if constexpr (StopOnFail) {
|
||||
summarize();
|
||||
std::exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
template <bool StopOnFail, bool IsVerbose>
|
||||
auto Runner<StopOnFail, IsVerbose>::summarize() -> void {
|
||||
std::cout << console::GREEN
|
||||
<< "\n-----------------------------------\n\t "
|
||||
"SUMMARY\n-----------------------------------\n";
|
||||
|
||||
if (m_fail_count == 0) {
|
||||
std::cout << "\n\tALL TESTS PASSED!\n\n";
|
||||
} else {
|
||||
Const<f64> success_rate =
|
||||
(100.0 * static_cast<f64>(m_test_count - m_fail_count) /
|
||||
static_cast<f64>(m_test_count));
|
||||
std::cout << console::RED << m_fail_count << " OF " << m_test_count
|
||||
<< " TESTS FAILED\n"
|
||||
<< console::YELLOW
|
||||
<< std::format("Success Rate: {:.2f}%\n", success_rate);
|
||||
}
|
||||
|
||||
std::cout << console::MAGENTA << "Ran " << m_test_count << " test(s) across "
|
||||
<< m_block_count << " block(s)\n"
|
||||
<< console::GREEN << "-----------------------------------"
|
||||
<< console::RESET << "\n";
|
||||
}
|
||||
|
||||
using DefaultRunner = Runner<false, true>;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Registry
|
||||
// -------------------------------------------------------------------------
|
||||
class TestRegistry {
|
||||
public:
|
||||
using TestEntry = std::function<void(MutRef<DefaultRunner>)>;
|
||||
|
||||
static auto get_entries() -> MutRef<Vec<TestEntry>> {
|
||||
static Mut<Vec<TestEntry>> entries;
|
||||
return entries;
|
||||
}
|
||||
|
||||
static auto run_all() -> i32 {
|
||||
Mut<DefaultRunner> r;
|
||||
MutRef<Vec<TestEntry>> entries = get_entries();
|
||||
std::cout << console::CYAN << "[IATest] Discovered " << entries.size()
|
||||
<< " Test Blocks\n\n"
|
||||
<< console::RESET;
|
||||
|
||||
for (MutRef<TestEntry> entry : entries) {
|
||||
entry(r);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename BlockType> struct AutoRegister {
|
||||
AutoRegister() {
|
||||
TestRegistry::get_entries().push_back(
|
||||
[](MutRef<DefaultRunner> r) { r.test_block<BlockType>(); });
|
||||
}
|
||||
};
|
||||
} // namespace IACore::Test
|
||||
|
||||
#define IAT_REGISTER_ENTRY(Group, Name) \
|
||||
static IACore::Test::AutoRegister<Group##_##Name> _iat_reg_##Group##_##Name;
|
||||
160
Src/IACore/inc/IACore/IPC.hpp
Normal file
160
Src/IACore/inc/IACore/IPC.hpp
Normal file
@ -0,0 +1,160 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/ADT/RingBuffer.hpp>
|
||||
#include <IACore/ProcessOps.hpp>
|
||||
#include <IACore/SocketOps.hpp>
|
||||
|
||||
namespace IACore {
|
||||
using IpcPacketHeader = RingBufferView::PacketHeader;
|
||||
|
||||
struct alignas(64) IpcSharedMemoryLayout {
|
||||
// =========================================================
|
||||
// METADATA & HANDSHAKE
|
||||
// =========================================================
|
||||
struct Header {
|
||||
Mut<u32> magic; // 0x49414950 ("IAIP")
|
||||
Mut<u32> version; // 1
|
||||
Mut<u64> total_size; // Total size of SHM block
|
||||
};
|
||||
|
||||
Mut<Header> meta;
|
||||
|
||||
// Pad to ensure MONI starts on a fresh cache line (64 bytes)
|
||||
Const<Array<u8, 64 - sizeof(Header)>> _pad0;
|
||||
|
||||
// =========================================================
|
||||
// RING BUFFER CONTROL BLOCKS
|
||||
// =========================================================
|
||||
|
||||
// RingBufferView ControlBlock is already 64-byte aligned internally.
|
||||
Mut<RingBufferView::ControlBlock> moni_control;
|
||||
Mut<RingBufferView::ControlBlock> mino_control;
|
||||
|
||||
// =========================================================
|
||||
// DATA BUFFER OFFSETS
|
||||
// =========================================================
|
||||
|
||||
Mut<u64> moni_data_offset;
|
||||
Mut<u64> moni_data_size;
|
||||
|
||||
Mut<u64> mino_data_offset;
|
||||
Mut<u64> mino_data_size;
|
||||
|
||||
// Pad to ensure the actual Data Buffer starts on a fresh cache line
|
||||
Const<Array<u8, 64 - (sizeof(u64) * 4)>> _pad1;
|
||||
|
||||
static constexpr auto get_header_size() -> usize {
|
||||
return sizeof(IpcSharedMemoryLayout);
|
||||
}
|
||||
};
|
||||
|
||||
// Check padding logic is gucci
|
||||
static_assert(sizeof(IpcSharedMemoryLayout) % 64 == 0,
|
||||
"IPC Layout is not cache-line aligned!");
|
||||
|
||||
class IpcNode {
|
||||
public:
|
||||
virtual ~IpcNode();
|
||||
|
||||
// When Manager spawns a node, `connection_string` is passed
|
||||
// as the first command line argument
|
||||
auto connect(Const<const char *> connection_string) -> Result<void>;
|
||||
|
||||
auto update() -> void;
|
||||
|
||||
auto send_signal(Const<u8> signal) -> void;
|
||||
auto send_packet(Const<u16> packet_id, Const<Span<Const<u8>>> payload)
|
||||
-> Result<void>;
|
||||
|
||||
protected:
|
||||
virtual auto on_signal(Const<u8> signal) -> void = 0;
|
||||
virtual auto on_packet(Const<u16> packet_id, Const<Span<Const<u8>>> payload)
|
||||
-> void = 0;
|
||||
|
||||
private:
|
||||
Mut<String> m_shm_name;
|
||||
Mut<u8 *> m_shared_memory{};
|
||||
Mut<Vec<u8>> m_receive_buffer;
|
||||
Mut<SocketHandle> m_socket{INVALID_SOCKET};
|
||||
|
||||
Mut<RingBufferView> m_moni; // Manager Out, Node In
|
||||
Mut<RingBufferView> m_mino; // Manager In, Node Out
|
||||
};
|
||||
|
||||
class IpcManager {
|
||||
struct NodeSession {
|
||||
Mut<std::chrono::system_clock::time_point> creation_time{};
|
||||
Mut<Box<ProcessHandle>> node_process;
|
||||
|
||||
Mut<std::mutex> send_mutex;
|
||||
|
||||
Mut<String> shared_mem_name;
|
||||
Mut<u8 *> mapped_ptr{};
|
||||
|
||||
Mut<SocketHandle> listener_socket{INVALID_SOCKET};
|
||||
Mut<SocketHandle> data_socket{INVALID_SOCKET};
|
||||
|
||||
Mut<RingBufferView> moni =
|
||||
RingBufferView::default_instance(); // Manager Out, Node In
|
||||
Mut<RingBufferView> mino =
|
||||
RingBufferView::default_instance(); // Manager In, Node Out
|
||||
|
||||
Mut<bool> is_ready{false};
|
||||
|
||||
auto send_signal(Const<u8> signal) -> void;
|
||||
auto send_packet(Const<u16> packet_id, Const<Span<Const<u8>>> payload)
|
||||
-> Result<void>;
|
||||
};
|
||||
|
||||
public:
|
||||
static constexpr Const<u32> DEFAULT_NODE_SHARED_MEMORY_SIZE = 4 * 1024 * 1024;
|
||||
|
||||
public:
|
||||
virtual ~IpcManager();
|
||||
|
||||
auto update() -> void;
|
||||
|
||||
auto
|
||||
spawn_node(Ref<Path> executable_path,
|
||||
Const<u32> shared_memory_size = DEFAULT_NODE_SHARED_MEMORY_SIZE)
|
||||
-> Result<NativeProcessID>;
|
||||
|
||||
auto wait_till_node_is_online(Const<NativeProcessID> node) -> bool;
|
||||
|
||||
auto shutdown_node(Const<NativeProcessID> node) -> void;
|
||||
|
||||
auto send_signal(Const<NativeProcessID> node, Const<u8> signal) -> void;
|
||||
auto send_packet(Const<NativeProcessID> node, Const<u16> packet_id,
|
||||
Const<Span<Const<u8>>> payload) -> Result<void>;
|
||||
|
||||
protected:
|
||||
virtual auto on_signal(Const<NativeProcessID> node, Const<u8> signal)
|
||||
-> void = 0;
|
||||
virtual auto on_packet(Const<NativeProcessID> node, Const<u16> packet_id,
|
||||
Const<Span<Const<u8>>> payload) -> void = 0;
|
||||
|
||||
private:
|
||||
Mut<Vec<u8>> m_receive_buffer;
|
||||
Mut<Vec<Box<NodeSession>>> m_active_sessions;
|
||||
Mut<Vec<Box<NodeSession>>> m_pending_sessions;
|
||||
Mut<HashMap<NativeProcessID, NodeSession *>> m_active_session_map;
|
||||
|
||||
protected:
|
||||
IpcManager();
|
||||
};
|
||||
} // namespace IACore
|
||||
119
Src/IACore/inc/IACore/JSON.hpp
Normal file
119
Src/IACore/inc/IACore/JSON.hpp
Normal file
@ -0,0 +1,119 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#include <glaze/glaze.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <simdjson.h>
|
||||
|
||||
namespace IACore {
|
||||
class JsonDocument {
|
||||
public:
|
||||
JsonDocument(ForwardRef<JsonDocument>) noexcept = default;
|
||||
auto operator=(ForwardRef<JsonDocument>) noexcept
|
||||
-> MutRef<JsonDocument> = default;
|
||||
|
||||
JsonDocument(Ref<JsonDocument>) = delete;
|
||||
auto operator=(Ref<JsonDocument>) -> MutRef<JsonDocument> = delete;
|
||||
|
||||
[[nodiscard]]
|
||||
auto root() const noexcept -> simdjson::dom::element {
|
||||
return m_root;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Json;
|
||||
|
||||
JsonDocument(Mut<Box<simdjson::dom::parser>> p, Mut<simdjson::dom::element> r)
|
||||
: m_parser(std::move(p)), m_root(r) {}
|
||||
|
||||
Mut<Box<simdjson::dom::parser>> m_parser;
|
||||
Mut<simdjson::dom::element> m_root;
|
||||
};
|
||||
|
||||
class Json {
|
||||
private:
|
||||
static constexpr Const<glz::opts> GLAZE_OPTS =
|
||||
glz::opts{.error_on_unknown_keys = false};
|
||||
|
||||
public:
|
||||
static auto parse(Ref<String> json_str) -> Result<nlohmann::json>;
|
||||
static auto encode(Ref<nlohmann::json> data) -> String;
|
||||
|
||||
static auto parse_read_only(Ref<String> json_str) -> Result<JsonDocument>;
|
||||
|
||||
template <typename T>
|
||||
static auto parse_to_struct(Ref<String> json_str) -> Result<T>;
|
||||
|
||||
template <typename T>
|
||||
static auto encode_struct(Ref<T> data) -> Result<String>;
|
||||
};
|
||||
|
||||
inline auto Json::parse(Ref<String> json_str) -> Result<nlohmann::json> {
|
||||
Const<nlohmann::json> res =
|
||||
nlohmann::json::parse(json_str, nullptr, false, true);
|
||||
|
||||
if (res.is_discarded()) {
|
||||
return fail("Failed to parse JSON (Invalid Syntax)");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
inline auto Json::parse_read_only(Ref<String> json_str)
|
||||
-> Result<JsonDocument> {
|
||||
Mut<Box<simdjson::dom::parser>> parser = make_box<simdjson::dom::parser>();
|
||||
|
||||
Mut<simdjson::dom::element> root;
|
||||
|
||||
Const<simdjson::error_code> error = parser->parse(json_str).get(root);
|
||||
|
||||
if (error) {
|
||||
return fail("JSON Error: {}", simdjson::error_message(error));
|
||||
}
|
||||
|
||||
return JsonDocument(std::move(parser), root);
|
||||
}
|
||||
|
||||
inline auto Json::encode(Ref<nlohmann::json> data) -> String {
|
||||
return data.dump();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline auto Json::parse_to_struct(Ref<String> json_str) -> Result<T> {
|
||||
Mut<T> result{};
|
||||
|
||||
Const<glz::error_ctx> err = glz::read<GLAZE_OPTS>(result, json_str);
|
||||
|
||||
if (err) {
|
||||
return fail("JSON Struct Parse Error: {}",
|
||||
glz::format_error(err, json_str));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline auto Json::encode_struct(Ref<T> data) -> Result<String> {
|
||||
Mut<String> result;
|
||||
Const<glz::error_ctx> err = glz::write_json(data, result);
|
||||
|
||||
if (err) {
|
||||
return fail("JSON Struct Encode Error");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace IACore
|
||||
123
Src/IACore/inc/IACore/Logger.hpp
Normal file
123
Src/IACore/inc/IACore/Logger.hpp
Normal file
@ -0,0 +1,123 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#define IA_LOG_SET_FILE(path) IACore::Logger::enable_logging_to_disk(path)
|
||||
#define IA_LOG_SET_LEVEL(level) \
|
||||
IACore::Logger::set_log_level(IACore::Logger::LogLevel::level)
|
||||
|
||||
#define IA_LOG_TRACE(...) IACore::Logger::trace(__VA_ARGS__)
|
||||
#define IA_LOG_DEBUG(...) IACore::Logger::debug(__VA_ARGS__)
|
||||
#define IA_LOG_INFO(...) IACore::Logger::info(__VA_ARGS__)
|
||||
#define IA_LOG_WARN(...) IACore::Logger::warn(__VA_ARGS__)
|
||||
#define IA_LOG_ERROR(...) IACore::Logger::error(__VA_ARGS__)
|
||||
|
||||
namespace IACore {
|
||||
class Logger {
|
||||
public:
|
||||
enum class LogLevel { Trace, Debug, Info, Warn, Error };
|
||||
|
||||
public:
|
||||
static auto enable_logging_to_disk(Const<const char *> file_path)
|
||||
-> Result<void>;
|
||||
static auto set_log_level(Const<LogLevel> log_level) -> void;
|
||||
|
||||
template <typename... Args>
|
||||
static auto trace(Const<std::format_string<Args...>> fmt,
|
||||
ForwardRef<Args>... args) -> void {
|
||||
log_trace(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static auto debug(Const<std::format_string<Args...>> fmt,
|
||||
ForwardRef<Args>... args) -> void {
|
||||
log_debug(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static auto info(Const<std::format_string<Args...>> fmt,
|
||||
ForwardRef<Args>... args) -> void {
|
||||
log_info(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static auto warn(Const<std::format_string<Args...>> fmt,
|
||||
ForwardRef<Args>... args) -> void {
|
||||
log_warn(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static auto error(Const<std::format_string<Args...>> fmt,
|
||||
ForwardRef<Args>... args) -> void {
|
||||
log_error(std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
static auto flush_logs() -> void;
|
||||
|
||||
private:
|
||||
#if IA_DISABLE_LOGGING > 0
|
||||
static auto log_trace(ForwardRef<String> msg) -> void { IA_UNUSED(msg); }
|
||||
|
||||
static auto log_debug(ForwardRef<String> msg) -> void { IA_UNUSED(msg); }
|
||||
|
||||
static auto log_info(ForwardRef<String> msg) -> void { IA_UNUSED(msg); }
|
||||
|
||||
static auto log_warn(ForwardRef<String> msg) -> void { IA_UNUSED(msg); }
|
||||
|
||||
static auto log_error(ForwardRef<String> msg) -> void { IA_UNUSED(msg); }
|
||||
#else
|
||||
static auto log_trace(ForwardRef<String> msg) -> void {
|
||||
if (m_log_level <= LogLevel::Trace)
|
||||
log_internal(console::RESET, "TRACE", std::move(msg));
|
||||
}
|
||||
|
||||
static auto log_debug(ForwardRef<String> msg) -> void {
|
||||
if (m_log_level <= LogLevel::Debug)
|
||||
log_internal(console::CYAN, "DEBUG", std::move(msg));
|
||||
}
|
||||
|
||||
static auto log_info(ForwardRef<String> msg) -> void {
|
||||
if (m_log_level <= LogLevel::Info)
|
||||
log_internal(console::GREEN, "INFO", std::move(msg));
|
||||
}
|
||||
|
||||
static auto log_warn(ForwardRef<String> msg) -> void {
|
||||
if (m_log_level <= LogLevel::Warn)
|
||||
log_internal(console::YELLOW, "WARN", std::move(msg));
|
||||
}
|
||||
|
||||
static auto log_error(ForwardRef<String> msg) -> void {
|
||||
if (m_log_level <= LogLevel::Error)
|
||||
log_internal(console::RED, "ERROR", std::move(msg));
|
||||
}
|
||||
#endif
|
||||
|
||||
static auto log_internal(Const<const char *> prefix, Const<const char *> tag,
|
||||
ForwardRef<String> msg) -> void;
|
||||
|
||||
private:
|
||||
static Mut<LogLevel> m_log_level;
|
||||
static Mut<std::ofstream> m_log_file;
|
||||
|
||||
static auto initialize() -> void;
|
||||
static auto terminate() -> void;
|
||||
|
||||
friend void initialize();
|
||||
friend void terminate();
|
||||
};
|
||||
} // namespace IACore
|
||||
135
Src/IACore/inc/IACore/PCH.hpp
Normal file
135
Src/IACore/inc/IACore/PCH.hpp
Normal file
@ -0,0 +1,135 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)
|
||||
#define IA_ARCH_X64 1
|
||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
||||
#define IA_ARCH_ARM64 1
|
||||
#elif defined(__wasm__) || defined(__wasm32__) || defined(__wasm64__)
|
||||
#define IA_ARCH_WASM 1
|
||||
#else
|
||||
#error "IACore: Unsupported Architecture."
|
||||
#endif
|
||||
|
||||
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
|
||||
#define IA_PLATFORM_WINDOWS 1
|
||||
#elif __APPLE__
|
||||
#include <TargetConditionals.h>
|
||||
#define IA_PLATFORM_APPLE 1
|
||||
#define IA_PLATFORM_UNIX 1
|
||||
#elif __linux__
|
||||
#define IA_PLATFORM_LINUX 1
|
||||
#define IA_PLATFORM_UNIX 1
|
||||
#elif __wasm__
|
||||
#define IA_PLATFORM_WASM 1
|
||||
#else
|
||||
#error "IACore: Unsupported Platform."
|
||||
#endif
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#elif IA_PLATFORM_UNIX
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <oxide/oxide.hpp>
|
||||
|
||||
namespace IACore {
|
||||
|
||||
using namespace Oxide;
|
||||
|
||||
// =============================================================================
|
||||
// Build Environment & Constants
|
||||
// =============================================================================
|
||||
namespace Env {
|
||||
|
||||
using namespace Oxide::Env;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
constexpr Const<bool> IS_WINDOWS = true;
|
||||
constexpr Const<bool> IS_UNIX = false;
|
||||
#else
|
||||
constexpr Const<bool> IS_WINDOWS = false;
|
||||
constexpr Const<bool> IS_UNIX = true;
|
||||
#endif
|
||||
|
||||
constexpr Const<usize> MAX_PATH_LEN = 4096;
|
||||
|
||||
} // namespace Env
|
||||
|
||||
// =============================================================================
|
||||
// Data Structures & Aliases
|
||||
// =============================================================================
|
||||
template <typename K, typename V>
|
||||
using HashMap = ankerl::unordered_dense::map<K, V>;
|
||||
template <typename T> using HashSet = ankerl::unordered_dense::set<T>;
|
||||
|
||||
using Path = std::filesystem::path;
|
||||
|
||||
// =============================================================================
|
||||
// Versioning
|
||||
// =============================================================================
|
||||
struct Version {
|
||||
u32 major = 0;
|
||||
u32 minor = 0;
|
||||
u32 patch = 0;
|
||||
|
||||
[[nodiscard]] constexpr auto to_u64() const -> u64 {
|
||||
return (static_cast<u64>(major) << 40) | (static_cast<u64>(minor) << 16) |
|
||||
(static_cast<u64>(patch));
|
||||
}
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Console Colors
|
||||
// =============================================================================
|
||||
namespace console {
|
||||
constexpr Const<const char *> RESET = "\033[0m";
|
||||
constexpr Const<const char *> RED = "\033[31m";
|
||||
constexpr Const<const char *> GREEN = "\033[32m";
|
||||
constexpr Const<const char *> YELLOW = "\033[33m";
|
||||
constexpr Const<const char *> BLUE = "\033[34m";
|
||||
constexpr Const<const char *> MAGENTA = "\033[35m";
|
||||
constexpr Const<const char *> CYAN = "\033[36m";
|
||||
} // namespace console
|
||||
|
||||
} // namespace IACore
|
||||
|
||||
#define IA_NODISCARD [[nodiscard]]
|
||||
#define IA_UNUSED(v) (void)(v)
|
||||
52
Src/IACore/inc/IACore/Platform.hpp
Normal file
52
Src/IACore/inc/IACore/Platform.hpp
Normal file
@ -0,0 +1,52 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#if IA_ARCH_X64
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
#elif IA_ARCH_ARM64
|
||||
#include <arm_acle.h>
|
||||
#endif
|
||||
|
||||
namespace IACore {
|
||||
class Platform {
|
||||
public:
|
||||
struct Capabilities {
|
||||
Mut<bool> hardware_crc32 = false;
|
||||
};
|
||||
|
||||
static auto check_cpu() -> bool;
|
||||
|
||||
#if IA_ARCH_X64
|
||||
static auto cpuid(Const<i32> function, Const<i32> sub_function,
|
||||
Mut<i32 *> out) -> void;
|
||||
#endif
|
||||
|
||||
static auto get_architecture_name() -> const char *;
|
||||
static auto get_operating_system_name() -> const char *;
|
||||
|
||||
static auto get_capabilities() -> Ref<Capabilities> { return s_capabilities; }
|
||||
|
||||
private:
|
||||
static Mut<Capabilities> s_capabilities;
|
||||
};
|
||||
} // namespace IACore
|
||||
69
Src/IACore/inc/IACore/ProcessOps.hpp
Normal file
69
Src/IACore/inc/IACore/ProcessOps.hpp
Normal file
@ -0,0 +1,69 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#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 {
|
||||
Mut<std::atomic<NativeProcessID>> id{0};
|
||||
Mut<std::atomic<bool>> is_running{false};
|
||||
|
||||
[[nodiscard]] auto is_active() const -> bool { return is_running && id != 0; }
|
||||
|
||||
private:
|
||||
Mut<std::jthread> m_thread_handle;
|
||||
|
||||
friend class ProcessOps;
|
||||
};
|
||||
|
||||
class ProcessOps {
|
||||
public:
|
||||
static auto get_current_process_id() -> NativeProcessID;
|
||||
|
||||
static auto spawn_process_sync(
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback)
|
||||
-> Result<i32>;
|
||||
|
||||
static auto spawn_process_async(
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback,
|
||||
Const<std::function<void(Const<Result<i32>>)>> on_finish_callback)
|
||||
-> Result<Box<ProcessHandle>>;
|
||||
|
||||
static auto terminate_process(Ref<Box<ProcessHandle>> handle) -> void;
|
||||
|
||||
private:
|
||||
static auto spawn_process_windows(
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback,
|
||||
MutRef<std::atomic<NativeProcessID>> id) -> Result<i32>;
|
||||
|
||||
static auto spawn_process_posix(
|
||||
Ref<String> command, Ref<String> args,
|
||||
Const<std::function<void(Const<StringView>)>> on_output_line_callback,
|
||||
MutRef<std::atomic<NativeProcessID>> id) -> Result<i32>;
|
||||
};
|
||||
} // namespace IACore
|
||||
280
Src/IACore/inc/IACore/SIMD.hpp
Normal file
280
Src/IACore/inc/IACore/SIMD.hpp
Normal file
@ -0,0 +1,280 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
|
||||
#endif
|
||||
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace IACore {
|
||||
namespace hn = hwy::HWY_NAMESPACE;
|
||||
|
||||
#if HWY_TARGET == HWY_SCALAR
|
||||
#pragma message( \
|
||||
"Warning: Configuration mismatch. IACore is being compiled for SCALAR SIMD (Slow)")
|
||||
#endif
|
||||
|
||||
class alignas(16) IntVec4 {
|
||||
public:
|
||||
IntVec4() = default;
|
||||
|
||||
inline explicit IntVec4(Const<u32> s);
|
||||
inline explicit IntVec4(Const<const u32 *> values);
|
||||
inline explicit IntVec4(Const<u32> a, Const<u32> b, Const<u32> c,
|
||||
Const<u32> d);
|
||||
|
||||
inline auto operator+(Ref<IntVec4> other) const -> IntVec4;
|
||||
inline auto operator-(Ref<IntVec4> other) const -> IntVec4;
|
||||
inline auto operator*(Ref<IntVec4> other) const -> IntVec4;
|
||||
|
||||
inline auto operator&(Ref<IntVec4> other) const -> IntVec4;
|
||||
inline auto operator|(Ref<IntVec4> other) const -> IntVec4;
|
||||
inline auto operator^(Ref<IntVec4> other) const -> IntVec4;
|
||||
inline auto operator~() const -> IntVec4;
|
||||
|
||||
inline auto operator<<(Const<u32> amount) const -> IntVec4;
|
||||
inline auto operator>>(Const<u32> amount) const -> IntVec4;
|
||||
|
||||
[[nodiscard]] inline auto sat_add(Ref<IntVec4> other) const -> IntVec4;
|
||||
[[nodiscard]] inline auto sat_sub(Ref<IntVec4> other) const -> IntVec4;
|
||||
|
||||
[[nodiscard]] inline auto clamp(Const<u32> min, Const<u32> max) const
|
||||
-> IntVec4;
|
||||
|
||||
[[nodiscard]] inline auto mult_add(Ref<IntVec4> multiplier,
|
||||
Ref<IntVec4> addend) const -> IntVec4;
|
||||
|
||||
inline auto store(Mut<u32 *> values) -> void;
|
||||
static inline auto load(Const<const u32 *> values) -> IntVec4;
|
||||
|
||||
private:
|
||||
using Tag = hn::FixedTag<u32, 4>;
|
||||
|
||||
Mut<hn::Vec<Tag>> m_data;
|
||||
|
||||
inline explicit IntVec4(Const<hn::Vec<Tag>> v) : m_data(v) {}
|
||||
};
|
||||
|
||||
class alignas(16) FloatVec4 {
|
||||
public:
|
||||
FloatVec4() = default;
|
||||
|
||||
inline explicit FloatVec4(Const<f32> s);
|
||||
inline explicit FloatVec4(Const<const f32 *> values);
|
||||
inline explicit FloatVec4(Const<f32> a, Const<f32> b, Const<f32> c,
|
||||
Const<f32> d);
|
||||
|
||||
inline auto operator+(Ref<FloatVec4> other) const -> FloatVec4;
|
||||
inline auto operator-(Ref<FloatVec4> other) const -> FloatVec4;
|
||||
inline auto operator*(Ref<FloatVec4> other) const -> FloatVec4;
|
||||
inline auto operator/(Ref<FloatVec4> other) const -> FloatVec4;
|
||||
|
||||
[[nodiscard]] inline auto clamp(Const<f32> min, Const<f32> max) const
|
||||
-> FloatVec4;
|
||||
|
||||
[[nodiscard]] inline auto abs() const -> FloatVec4;
|
||||
[[nodiscard]] inline auto sqrt() const -> FloatVec4;
|
||||
[[nodiscard]] inline auto rsqrt() const -> FloatVec4;
|
||||
[[nodiscard]] inline auto normalize() const -> FloatVec4;
|
||||
|
||||
[[nodiscard]] inline auto dot(Ref<FloatVec4> other) const -> f32;
|
||||
|
||||
[[nodiscard]] inline auto mult_add(Ref<FloatVec4> multiplier,
|
||||
Ref<FloatVec4> addend) const -> FloatVec4;
|
||||
|
||||
inline auto store(Mut<f32 *> values) -> void;
|
||||
static inline auto load(Const<const f32 *> values) -> FloatVec4;
|
||||
|
||||
private:
|
||||
using Tag = hn::FixedTag<f32, 4>;
|
||||
|
||||
Mut<hn::Vec<Tag>> m_data;
|
||||
|
||||
inline explicit FloatVec4(Const<hn::Vec<Tag>> v) : m_data(v) {}
|
||||
};
|
||||
} // namespace IACore
|
||||
|
||||
namespace IACore {
|
||||
IntVec4::IntVec4(Const<u32> s) {
|
||||
Const<Tag> d;
|
||||
m_data = hn::Set(d, s);
|
||||
}
|
||||
|
||||
IntVec4::IntVec4(Const<const u32 *> values) {
|
||||
Const<Tag> data;
|
||||
m_data = hn::Load(data, values);
|
||||
}
|
||||
|
||||
IntVec4::IntVec4(Const<u32> a, Const<u32> b, Const<u32> c, Const<u32> d) {
|
||||
Const<Tag> data;
|
||||
alignas(16) Mut<Array<u32, 4>> values = {a, b, c, d};
|
||||
m_data = hn::Load(data, values.data());
|
||||
}
|
||||
|
||||
auto IntVec4::operator+(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::Add(m_data, other.m_data));
|
||||
}
|
||||
|
||||
auto IntVec4::operator-(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::Sub(m_data, other.m_data));
|
||||
}
|
||||
|
||||
auto IntVec4::operator*(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::Mul(m_data, other.m_data));
|
||||
}
|
||||
|
||||
auto IntVec4::operator&(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::And(m_data, other.m_data));
|
||||
}
|
||||
|
||||
auto IntVec4::operator|(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::Or(m_data, other.m_data));
|
||||
}
|
||||
|
||||
auto IntVec4::operator^(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::Xor(m_data, other.m_data));
|
||||
}
|
||||
|
||||
auto IntVec4::operator~() const -> IntVec4 { return IntVec4(hn::Not(m_data)); }
|
||||
|
||||
auto IntVec4::operator<<(Const<u32> amount) const -> IntVec4 {
|
||||
return IntVec4(hn::ShiftLeftSame(m_data, amount));
|
||||
}
|
||||
|
||||
auto IntVec4::operator>>(Const<u32> amount) const -> IntVec4 {
|
||||
return IntVec4(hn::ShiftRightSame(m_data, amount));
|
||||
}
|
||||
|
||||
auto IntVec4::mult_add(Ref<IntVec4> multiplier, Ref<IntVec4> addend) const
|
||||
-> IntVec4 {
|
||||
return IntVec4(hn::MulAdd(m_data, multiplier.m_data, addend.m_data));
|
||||
}
|
||||
|
||||
auto IntVec4::sat_add(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::SaturatedAdd(m_data, other.m_data));
|
||||
}
|
||||
|
||||
auto IntVec4::sat_sub(Ref<IntVec4> other) const -> IntVec4 {
|
||||
return IntVec4(hn::SaturatedSub(m_data, other.m_data));
|
||||
}
|
||||
|
||||
auto IntVec4::clamp(Const<u32> min, Const<u32> max) const -> IntVec4 {
|
||||
Const<Tag> d;
|
||||
Const<hn::Vec<Tag>> v_min = hn::Set(d, min);
|
||||
Const<hn::Vec<Tag>> v_max = hn::Set(d, max);
|
||||
return IntVec4(hn::Min(hn::Max(m_data, v_min), v_max));
|
||||
}
|
||||
|
||||
auto IntVec4::store(Mut<u32 *> values) -> void {
|
||||
Const<Tag> d;
|
||||
hn::Store(m_data, d, values);
|
||||
}
|
||||
|
||||
auto IntVec4::load(Const<const u32 *> values) -> IntVec4 {
|
||||
Const<Tag> d;
|
||||
return IntVec4(hn::Load(d, values));
|
||||
}
|
||||
} // namespace IACore
|
||||
|
||||
namespace IACore {
|
||||
FloatVec4::FloatVec4(Const<f32> s) {
|
||||
Const<Tag> d;
|
||||
m_data = hn::Set(d, s);
|
||||
}
|
||||
|
||||
FloatVec4::FloatVec4(Const<const f32 *> values) {
|
||||
Const<Tag> d;
|
||||
m_data = hn::Load(d, values);
|
||||
}
|
||||
|
||||
FloatVec4::FloatVec4(Const<f32> a, Const<f32> b, Const<f32> c, Const<f32> d) {
|
||||
Const<Tag> data;
|
||||
alignas(16) Mut<Array<f32, 4>> temp = {a, b, c, d};
|
||||
m_data = hn::Load(data, temp.data());
|
||||
}
|
||||
|
||||
auto FloatVec4::operator+(Ref<FloatVec4> other) const -> FloatVec4 {
|
||||
return FloatVec4(hn::Add(m_data, other.m_data));
|
||||
}
|
||||
|
||||
auto FloatVec4::operator-(Ref<FloatVec4> other) const -> FloatVec4 {
|
||||
return FloatVec4(hn::Sub(m_data, other.m_data));
|
||||
}
|
||||
|
||||
auto FloatVec4::operator*(Ref<FloatVec4> other) const -> FloatVec4 {
|
||||
return FloatVec4(hn::Mul(m_data, other.m_data));
|
||||
}
|
||||
|
||||
auto FloatVec4::operator/(Ref<FloatVec4> other) const -> FloatVec4 {
|
||||
return FloatVec4(hn::Div(m_data, other.m_data));
|
||||
}
|
||||
|
||||
auto FloatVec4::mult_add(Ref<FloatVec4> multiplier, Ref<FloatVec4> addend) const
|
||||
-> FloatVec4 {
|
||||
return FloatVec4(hn::MulAdd(m_data, multiplier.m_data, addend.m_data));
|
||||
}
|
||||
|
||||
auto FloatVec4::clamp(Const<f32> min, Const<f32> max) const -> FloatVec4 {
|
||||
Const<Tag> d;
|
||||
Const<hn::Vec<Tag>> v_min = hn::Set(d, min);
|
||||
Const<hn::Vec<Tag>> v_max = hn::Set(d, max);
|
||||
return FloatVec4(hn::Min(hn::Max(m_data, v_min), v_max));
|
||||
}
|
||||
|
||||
auto FloatVec4::sqrt() const -> FloatVec4 {
|
||||
return FloatVec4(hn::Sqrt(m_data));
|
||||
}
|
||||
|
||||
auto FloatVec4::rsqrt() const -> FloatVec4 {
|
||||
return FloatVec4(hn::ApproximateReciprocalSqrt(m_data));
|
||||
}
|
||||
|
||||
auto FloatVec4::abs() const -> FloatVec4 { return FloatVec4(hn::Abs(m_data)); }
|
||||
|
||||
auto FloatVec4::dot(Ref<FloatVec4> other) const -> f32 {
|
||||
Const<Tag> d;
|
||||
Const<hn::Vec<Tag>> v_mul = hn::Mul(m_data, other.m_data);
|
||||
return hn::ReduceSum(d, v_mul);
|
||||
}
|
||||
|
||||
auto FloatVec4::normalize() const -> FloatVec4 {
|
||||
Const<Tag> d;
|
||||
Const<hn::Vec<Tag>> v_mul = hn::Mul(m_data, m_data);
|
||||
Const<hn::Vec<Tag>> v_len_sq = hn::SumOfLanes(d, v_mul);
|
||||
Const<hn::Vec<Tag>> v_inv_len = hn::ApproximateReciprocalSqrt(v_len_sq);
|
||||
return FloatVec4(hn::Mul(m_data, v_inv_len));
|
||||
}
|
||||
|
||||
auto FloatVec4::store(Mut<f32 *> values) -> void {
|
||||
Const<Tag> d;
|
||||
hn::Store(m_data, d, values);
|
||||
}
|
||||
|
||||
auto FloatVec4::load(Const<const f32 *> values) -> FloatVec4 {
|
||||
Const<Tag> d;
|
||||
return FloatVec4(hn::Load(d, values));
|
||||
}
|
||||
} // namespace IACore
|
||||
116
Src/IACore/inc/IACore/SocketOps.hpp
Normal file
116
Src/IACore/inc/IACore/SocketOps.hpp
Normal file
@ -0,0 +1,116 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
#include <afunix.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#elif IA_PLATFORM_UNIX
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#ifndef INVALID_SOCKET
|
||||
#define INVALID_SOCKET -1
|
||||
#endif
|
||||
#else
|
||||
#error "IACore SocketOps is not supported on this platform."
|
||||
#endif
|
||||
|
||||
namespace IACore {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
using SocketHandle = SOCKET;
|
||||
#elif IA_PLATFORM_UNIX
|
||||
using SocketHandle = i32;
|
||||
#endif
|
||||
|
||||
class SocketOps {
|
||||
public:
|
||||
// SocketOps correctly handles multiple calls to initialize and terminate.
|
||||
// Make sure every initialize call is paired with a corresponding terminate
|
||||
// call.
|
||||
static auto initialize() -> Result<void> {
|
||||
s_init_count++;
|
||||
if (s_init_count > 1) {
|
||||
return {};
|
||||
}
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
Mut<WSADATA> wsa_data;
|
||||
Const<i32> res = WSAStartup(MAKEWORD(2, 2), &wsa_data);
|
||||
if (res != 0) {
|
||||
s_init_count--;
|
||||
return fail("WSAStartup failed with error: {}", res);
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
// SocketOps correctly handles multiple calls to initialize and terminate.
|
||||
// Make sure every initialize call is paired with a corresponding terminate
|
||||
// call.
|
||||
static auto terminate() -> void {
|
||||
s_init_count--;
|
||||
if (s_init_count > 0) {
|
||||
return;
|
||||
}
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
static auto is_initialized() -> bool { return s_init_count > 0; }
|
||||
|
||||
static auto is_port_available_tcp(Const<u16> port) -> bool {
|
||||
return is_port_available(port, SOCK_STREAM);
|
||||
}
|
||||
|
||||
static auto is_port_available_udp(Const<u16> port) -> bool {
|
||||
return is_port_available(port, SOCK_DGRAM);
|
||||
}
|
||||
|
||||
static auto is_would_block() -> bool;
|
||||
|
||||
static auto close(Const<SocketHandle> sock) -> void;
|
||||
|
||||
static auto listen(Const<SocketHandle> sock, Const<i32> queue_size = 5)
|
||||
-> Result<void>;
|
||||
|
||||
static auto create_unix_socket() -> Result<SocketHandle>;
|
||||
|
||||
static auto bind_unix_socket(Const<SocketHandle> sock,
|
||||
Const<const char *> path) -> Result<void>;
|
||||
static auto connect_unix_socket(Const<SocketHandle> sock,
|
||||
Const<const char *> path) -> Result<void>;
|
||||
|
||||
static auto unlink_file(Const<const char *> path) -> void {
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
DeleteFileA(path);
|
||||
#elif IA_PLATFORM_UNIX
|
||||
unlink(path);
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
static auto is_port_available(Const<u16> port, Const<i32> type) -> bool;
|
||||
|
||||
private:
|
||||
static Mut<i32> s_init_count;
|
||||
};
|
||||
} // namespace IACore
|
||||
106
Src/IACore/inc/IACore/StreamReader.hpp
Normal file
106
Src/IACore/inc/IACore/StreamReader.hpp
Normal file
@ -0,0 +1,106 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace IACore {
|
||||
class StreamReader {
|
||||
public:
|
||||
enum class StorageType {
|
||||
NonOwning,
|
||||
OwningMmap,
|
||||
OwningVector,
|
||||
};
|
||||
|
||||
static auto create_from_file(Ref<Path> path) -> Result<StreamReader>;
|
||||
|
||||
explicit StreamReader(ForwardRef<Vec<u8>> data);
|
||||
explicit StreamReader(Const<Span<const u8>> data);
|
||||
~StreamReader();
|
||||
|
||||
StreamReader(ForwardRef<StreamReader> other);
|
||||
auto operator=(ForwardRef<StreamReader> other) -> MutRef<StreamReader>;
|
||||
|
||||
StreamReader(Ref<StreamReader>) = delete;
|
||||
auto operator=(Ref<StreamReader>) -> MutRef<StreamReader> = delete;
|
||||
|
||||
auto read(Mut<void *> buffer, Const<usize> size) -> Result<void>;
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard("Check for EOF")]]
|
||||
auto read() -> Result<T>;
|
||||
|
||||
auto skip(Const<usize> amount) -> void {
|
||||
m_cursor = std::min(m_cursor + amount, m_data_size);
|
||||
}
|
||||
|
||||
auto seek(Const<usize> pos) -> void {
|
||||
m_cursor = (pos > m_data_size) ? m_data_size : pos;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto cursor() const -> usize { return m_cursor; }
|
||||
|
||||
[[nodiscard]] auto size() const -> usize { return m_data_size; }
|
||||
|
||||
[[nodiscard]] auto remaining() const -> usize {
|
||||
return m_data_size - m_cursor;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto is_eof() const -> bool { return m_cursor >= m_data_size; }
|
||||
|
||||
private:
|
||||
Mut<const u8 *> m_data = nullptr;
|
||||
Mut<usize> m_cursor = 0;
|
||||
Mut<usize> m_data_size = 0;
|
||||
Mut<Vec<u8>> m_owning_vector;
|
||||
Mut<StorageType> m_storage_type = StorageType::NonOwning;
|
||||
};
|
||||
|
||||
inline auto StreamReader::read(Mut<void *> buffer, Const<usize> size)
|
||||
-> Result<void> {
|
||||
if (m_cursor + size > m_data_size) [[unlikely]] {
|
||||
return fail("Unexpected EOF while reading");
|
||||
}
|
||||
|
||||
std::memcpy(buffer, &m_data[m_cursor], size);
|
||||
m_cursor += size;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard("Check for EOF")]]
|
||||
inline auto StreamReader::read() -> Result<T> {
|
||||
static_assert(std::is_trivially_copyable_v<T>,
|
||||
"T must be trivially copyable to read via memcpy");
|
||||
|
||||
constexpr Const<usize> SIZE = sizeof(T);
|
||||
|
||||
if (m_cursor + SIZE > m_data_size) [[unlikely]] {
|
||||
return fail("Unexpected EOF while reading");
|
||||
}
|
||||
|
||||
Mut<T> value;
|
||||
std::memcpy(&value, &m_data[m_cursor], SIZE);
|
||||
m_cursor += SIZE;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
71
Src/IACore/inc/IACore/StreamWriter.hpp
Normal file
71
Src/IACore/inc/IACore/StreamWriter.hpp
Normal file
@ -0,0 +1,71 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
namespace IACore {
|
||||
|
||||
class StreamWriter {
|
||||
public:
|
||||
enum class StorageType {
|
||||
NonOwning,
|
||||
OwningFile,
|
||||
OwningVector,
|
||||
};
|
||||
|
||||
static auto create_from_file(Ref<Path> path) -> Result<StreamWriter>;
|
||||
|
||||
StreamWriter();
|
||||
explicit StreamWriter(Const<Span<u8>> data);
|
||||
|
||||
StreamWriter(ForwardRef<StreamWriter> other);
|
||||
auto operator=(ForwardRef<StreamWriter> other) -> MutRef<StreamWriter>;
|
||||
|
||||
StreamWriter(Ref<StreamWriter>) = delete;
|
||||
auto operator=(Ref<StreamWriter>) -> MutRef<StreamWriter> = delete;
|
||||
|
||||
~StreamWriter();
|
||||
|
||||
auto write(Const<u8> byte, Const<usize> count) -> Result<void>;
|
||||
auto write(Const<const void *> buffer, Const<usize> size) -> Result<void>;
|
||||
|
||||
template <typename T> auto write(Ref<T> value) -> Result<void>;
|
||||
|
||||
[[nodiscard]] auto data() const -> const u8 * { return m_buffer; }
|
||||
|
||||
[[nodiscard]] auto cursor() const -> usize { return m_cursor; }
|
||||
|
||||
auto flush() -> Result<void>;
|
||||
|
||||
private:
|
||||
Mut<u8 *> m_buffer = nullptr;
|
||||
Mut<usize> m_cursor = 0;
|
||||
Mut<usize> m_capacity = 0;
|
||||
Mut<Path> m_file_path;
|
||||
Mut<Vec<u8>> m_owning_vector;
|
||||
Mut<StorageType> m_storage_type = StorageType::OwningVector;
|
||||
|
||||
private:
|
||||
auto flush_to_disk() -> Result<void>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline auto StreamWriter::write(Ref<T> value) -> Result<void> {
|
||||
return write(&value, sizeof(T));
|
||||
}
|
||||
|
||||
} // namespace IACore
|
||||
26
Src/IACore/inc/IACore/StringOps.hpp
Normal file
26
Src/IACore/inc/IACore/StringOps.hpp
Normal file
@ -0,0 +1,26 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
namespace IACore {
|
||||
class StringOps {
|
||||
public:
|
||||
static auto encode_base64(Const<Span<Const<u8>>> data) -> String;
|
||||
static auto decode_base64(Ref<String> data) -> Vec<u8>;
|
||||
};
|
||||
} // namespace IACore
|
||||
107
Src/IACore/inc/IACore/Utils.hpp
Normal file
107
Src/IACore/inc/IACore/Utils.hpp
Normal file
@ -0,0 +1,107 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace IACore {
|
||||
class Utils {
|
||||
public:
|
||||
static auto get_unix_time() -> u64;
|
||||
|
||||
static auto get_ticks_count() -> u64;
|
||||
|
||||
static auto get_seconds_count() -> f64;
|
||||
|
||||
static auto get_random() -> f32;
|
||||
static auto get_random(Const<u64> max) -> u64;
|
||||
static auto get_random(Const<i64> min, Const<i64> max) -> i64;
|
||||
|
||||
static auto sleep(Const<u64> milliseconds) -> void;
|
||||
|
||||
static auto binary_to_hex_string(Const<Span<Const<u8>>> data) -> String;
|
||||
|
||||
static auto hex_string_to_binary(Const<StringView> hex) -> Result<Vec<u8>>;
|
||||
|
||||
template <typename Range>
|
||||
inline static auto sort(ForwardRef<Range> range) -> void {
|
||||
std::ranges::sort(std::forward<Range>(range));
|
||||
}
|
||||
|
||||
template <typename Range, typename T>
|
||||
inline static auto binary_search_left(ForwardRef<Range> range, Ref<T> value)
|
||||
-> auto {
|
||||
return std::ranges::lower_bound(std::forward<Range>(range), value);
|
||||
}
|
||||
|
||||
template <typename Range, typename T>
|
||||
inline static auto binary_search_right(ForwardRef<Range> range, Ref<T> value)
|
||||
-> auto {
|
||||
return std::ranges::upper_bound(std::forward<Range>(range), value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline static auto hash_combine(MutRef<u64> seed, Ref<T> v) -> void {
|
||||
Mut<u64> h = 0;
|
||||
|
||||
if constexpr (std::is_constructible_v<StringView, T>) {
|
||||
Const<StringView> sv(v);
|
||||
Const<ankerl::unordered_dense::hash<StringView>> hasher;
|
||||
h = hasher(sv);
|
||||
} else {
|
||||
Const<ankerl::unordered_dense::hash<T>> hasher;
|
||||
h = hasher(v);
|
||||
}
|
||||
|
||||
seed ^= h + 0x9e3779b97f4a7c15 + (seed << 6) + (seed >> 2);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline static auto compute_hash(Ref<Args>... args) -> u64 {
|
||||
Mut<u64> seed = 0;
|
||||
(hash_combine(seed, args), ...);
|
||||
return seed;
|
||||
}
|
||||
|
||||
template <typename T, typename... MemberPtrs>
|
||||
inline static auto compute_hash_flat(Ref<T> obj, Const<MemberPtrs>... members)
|
||||
-> u64 {
|
||||
Mut<u64> seed = 0;
|
||||
(hash_combine(seed, obj.*members), ...);
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
} // namespace IACore
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// MACRO: IA_MAKE_HASHABLE
|
||||
//
|
||||
// Injects the specialization for ankerl::unordered_dense::hash.
|
||||
//
|
||||
// Usage:
|
||||
// struct Vector3 { float x, y, z; };
|
||||
// IA_MAKE_HASHABLE(Vector3, &Vector3::x, &Vector3::y, &Vector3::z)
|
||||
// -----------------------------------------------------------------------------
|
||||
#define IA_MAKE_HASHABLE(Type, ...) \
|
||||
template <> struct ankerl::unordered_dense::hash<Type> { \
|
||||
using is_avalanching = void; \
|
||||
IA_NODISCARD \
|
||||
auto operator()(IACore::Ref<Type> v) const noexcept -> IACore::u64 { \
|
||||
return IACore::Utils::compute_hash_flat(v, __VA_ARGS__); \
|
||||
} \
|
||||
};
|
||||
39
Src/IACore/inc/IACore/XML.hpp
Normal file
39
Src/IACore/inc/IACore/XML.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IACore/PCH.hpp>
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace IACore {
|
||||
class XML {
|
||||
public:
|
||||
using Node = pugi::xml_node;
|
||||
using Document = pugi::xml_document;
|
||||
|
||||
public:
|
||||
static auto parse_from_string(Ref<String> data) -> Result<Document>;
|
||||
static auto parse_from_file(Ref<Path> path) -> Result<Document>;
|
||||
|
||||
static auto serialize_to_string(Ref<Node> node, Const<bool> escape = false)
|
||||
-> String;
|
||||
static auto serialize_to_string(Ref<Document> doc, Const<bool> escape = false)
|
||||
-> String;
|
||||
|
||||
static auto escape_xml_string(Ref<String> xml) -> String;
|
||||
};
|
||||
} // namespace IACore
|
||||
4
Tests/CMakeLists.txt
Normal file
4
Tests/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
add_subdirectory(Subjects/)
|
||||
add_subdirectory(Unit/)
|
||||
add_subdirectory(Regression/)
|
||||
0
Tests/Regression/CMakeLists.txt
Normal file
0
Tests/Regression/CMakeLists.txt
Normal file
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;
|
||||
}
|
||||
160
Tests/Unit/AsyncOps.cpp
Normal file
160
Tests/Unit/AsyncOps.cpp
Normal file
@ -0,0 +1,160 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/AsyncOps.hpp>
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
struct SchedulerGuard {
|
||||
SchedulerGuard(u8 worker_count = 2) {
|
||||
|
||||
(void)AsyncOps::initialize_scheduler(worker_count);
|
||||
}
|
||||
|
||||
~SchedulerGuard() { AsyncOps::terminate_scheduler(); }
|
||||
};
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, AsyncOps)
|
||||
|
||||
auto test_initialization() -> bool {
|
||||
|
||||
AsyncOps::terminate_scheduler();
|
||||
|
||||
const auto res = AsyncOps::initialize_scheduler(4);
|
||||
IAT_CHECK(res.has_value());
|
||||
|
||||
IAT_CHECK_EQ(AsyncOps::get_worker_count(), static_cast<u16>(4));
|
||||
|
||||
AsyncOps::terminate_scheduler();
|
||||
|
||||
const auto res2 = AsyncOps::initialize_scheduler(1);
|
||||
IAT_CHECK(res2.has_value());
|
||||
IAT_CHECK_EQ(AsyncOps::get_worker_count(), static_cast<u16>(1));
|
||||
|
||||
AsyncOps::terminate_scheduler();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_basic_execution() -> bool {
|
||||
SchedulerGuard guard(2);
|
||||
|
||||
AsyncOps::Schedule schedule;
|
||||
std::atomic<i32> run_count{0};
|
||||
|
||||
AsyncOps::schedule_task([&](AsyncOps::WorkerId) { run_count++; }, 0,
|
||||
&schedule);
|
||||
|
||||
AsyncOps::wait_for_schedule_completion(&schedule);
|
||||
|
||||
IAT_CHECK_EQ(run_count.load(), 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_concurrency() -> bool {
|
||||
SchedulerGuard guard(4);
|
||||
|
||||
AsyncOps::Schedule schedule;
|
||||
std::atomic<i32> run_count{0};
|
||||
const i32 total_tasks = 100;
|
||||
|
||||
for (i32 i = 0; i < total_tasks; ++i) {
|
||||
AsyncOps::schedule_task(
|
||||
[&](AsyncOps::WorkerId) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(10));
|
||||
run_count++;
|
||||
},
|
||||
0, &schedule);
|
||||
}
|
||||
|
||||
AsyncOps::wait_for_schedule_completion(&schedule);
|
||||
|
||||
IAT_CHECK_EQ(run_count.load(), total_tasks);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_priorities() -> bool {
|
||||
SchedulerGuard guard(2);
|
||||
AsyncOps::Schedule schedule;
|
||||
std::atomic<i32> high_priority_ran{0};
|
||||
std::atomic<i32> normal_priority_ran{0};
|
||||
|
||||
AsyncOps::schedule_task([&](AsyncOps::WorkerId) { high_priority_ran++; }, 0,
|
||||
&schedule, AsyncOps::Priority::High);
|
||||
|
||||
AsyncOps::schedule_task([&](AsyncOps::WorkerId) { normal_priority_ran++; }, 0,
|
||||
&schedule, AsyncOps::Priority::Normal);
|
||||
|
||||
AsyncOps::wait_for_schedule_completion(&schedule);
|
||||
|
||||
IAT_CHECK_EQ(high_priority_ran.load(), 1);
|
||||
IAT_CHECK_EQ(normal_priority_ran.load(), 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_run_task_fire_and_forget() -> bool {
|
||||
SchedulerGuard guard(2);
|
||||
|
||||
std::atomic<bool> executed{false};
|
||||
|
||||
AsyncOps::run_task([&]() { executed = true; });
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
if (executed.load())
|
||||
break;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
IAT_CHECK(executed.load());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_cancellation_safety() -> bool {
|
||||
SchedulerGuard guard(2);
|
||||
|
||||
AsyncOps::cancel_tasks_of_tag(999);
|
||||
|
||||
AsyncOps::Schedule schedule;
|
||||
std::atomic<i32> counter{0};
|
||||
|
||||
AsyncOps::schedule_task([&](AsyncOps::WorkerId) { counter++; }, 10,
|
||||
&schedule);
|
||||
|
||||
AsyncOps::wait_for_schedule_completion(&schedule);
|
||||
IAT_CHECK_EQ(counter.load(), 1);
|
||||
|
||||
AsyncOps::cancel_tasks_of_tag(10);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_initialization);
|
||||
IAT_ADD_TEST(test_basic_execution);
|
||||
IAT_ADD_TEST(test_concurrency);
|
||||
IAT_ADD_TEST(test_priorities);
|
||||
IAT_ADD_TEST(test_run_task_fire_and_forget);
|
||||
IAT_ADD_TEST(test_cancellation_safety);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, AsyncOps)
|
||||
103
Tests/Unit/CLI.cpp
Normal file
103
Tests/Unit/CLI.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/CLI.hpp>
|
||||
#include <IACore/IATest.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, CLI)
|
||||
|
||||
auto test_basic_traversal() -> bool {
|
||||
const Vec<String> args = {"ignored", "one", "two", "three"};
|
||||
CLIParser parser(args);
|
||||
|
||||
IAT_CHECK(parser.remaining());
|
||||
|
||||
IAT_CHECK_EQ(String(parser.next()), "one");
|
||||
IAT_CHECK(parser.remaining());
|
||||
|
||||
IAT_CHECK_EQ(String(parser.next()), "two");
|
||||
IAT_CHECK(parser.remaining());
|
||||
|
||||
IAT_CHECK_EQ(String(parser.next()), "three");
|
||||
|
||||
IAT_CHECK_NOT(parser.remaining());
|
||||
|
||||
IAT_CHECK_EQ(String(parser.next()), "");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_peek() -> bool {
|
||||
const Vec<String> args = {"ignored", "peek_val", "next_val"};
|
||||
CLIParser parser(args);
|
||||
|
||||
IAT_CHECK_EQ(String(parser.peek()), "peek_val");
|
||||
IAT_CHECK(parser.remaining());
|
||||
|
||||
IAT_CHECK_EQ(String(parser.next()), "peek_val");
|
||||
|
||||
IAT_CHECK_EQ(String(parser.peek()), "next_val");
|
||||
IAT_CHECK_EQ(String(parser.next()), "next_val");
|
||||
|
||||
IAT_CHECK_NOT(parser.remaining());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_consume() -> bool {
|
||||
const Vec<String> args = {"ignored", "-v", "--output", "file.txt"};
|
||||
CLIParser parser(args);
|
||||
|
||||
IAT_CHECK_NOT(parser.consume("-x"));
|
||||
|
||||
IAT_CHECK_EQ(String(parser.peek()), "-v");
|
||||
|
||||
IAT_CHECK(parser.consume("-v"));
|
||||
|
||||
IAT_CHECK_EQ(String(parser.peek()), "--output");
|
||||
|
||||
IAT_CHECK(parser.consume("--output"));
|
||||
|
||||
IAT_CHECK_EQ(String(parser.next()), "file.txt");
|
||||
|
||||
IAT_CHECK_NOT(parser.remaining());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_empty() -> bool {
|
||||
const Vec<String> args = {};
|
||||
CLIParser parser(args);
|
||||
|
||||
IAT_CHECK_NOT(parser.remaining());
|
||||
IAT_CHECK_EQ(String(parser.peek()), "");
|
||||
IAT_CHECK_EQ(String(parser.next()), "");
|
||||
IAT_CHECK_NOT(parser.consume("-help"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_basic_traversal);
|
||||
IAT_ADD_TEST(test_peek);
|
||||
IAT_ADD_TEST(test_consume);
|
||||
IAT_ADD_TEST(test_empty);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, CLI)
|
||||
36
Tests/Unit/CMakeLists.txt
Normal file
36
Tests/Unit/CMakeLists.txt
Normal file
@ -0,0 +1,36 @@
|
||||
set(TEST_SOURCES
|
||||
AsyncOps.cpp
|
||||
CLI.cpp
|
||||
DataOps.cpp
|
||||
Environment.cpp
|
||||
FileOps.cpp
|
||||
IPC.cpp
|
||||
JSON.cpp
|
||||
Logger.cpp
|
||||
Main.cpp
|
||||
Platform.cpp
|
||||
ProcessOps.cpp
|
||||
RingBuffer.cpp
|
||||
SocketOps.cpp
|
||||
StreamReader.cpp
|
||||
StreamWriter.cpp
|
||||
StringOps.cpp
|
||||
Utils.cpp
|
||||
XML.cpp
|
||||
|
||||
SIMD/IntVec4.cpp
|
||||
SIMD/FloatVec4.cpp
|
||||
)
|
||||
|
||||
add_executable(IACore_Test_Suite ${TEST_SOURCES})
|
||||
|
||||
target_compile_options(IACore_Test_Suite PRIVATE -fexceptions)
|
||||
set_target_properties(IACore_Test_Suite PROPERTIES USE_EXCEPTIONS ON)
|
||||
|
||||
target_link_libraries(IACore_Test_Suite PRIVATE IACore)
|
||||
|
||||
add_custom_command(TARGET IACore_Test_Suite POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
$<TARGET_FILE:LongProcess>
|
||||
$<TARGET_FILE_DIR:IACore_Test_Suite>/LongProcess${CMAKE_EXECUTABLE_SUFFIX}
|
||||
)
|
||||
108
Tests/Unit/DataOps.cpp
Normal file
108
Tests/Unit/DataOps.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/DataOps.hpp>
|
||||
#include <IACore/IATest.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, DataOps)
|
||||
|
||||
auto test_crc32() -> bool {
|
||||
{
|
||||
const String s = "123456789";
|
||||
const Span<const u8> span(reinterpret_cast<const u8 *>(s.data()), s.size());
|
||||
const u32 result = DataOps::crc32(span);
|
||||
|
||||
IAT_CHECK_EQ(result, 0xE3069283);
|
||||
}
|
||||
|
||||
{
|
||||
const u32 result = DataOps::crc32({});
|
||||
IAT_CHECK_EQ(result, 0U);
|
||||
}
|
||||
|
||||
{
|
||||
Vec<u8> buffer(33);
|
||||
for (usize i = 1; i < 33; ++i) {
|
||||
buffer[i] = static_cast<u8>(i);
|
||||
}
|
||||
|
||||
Vec<u8> ref_data(32);
|
||||
for (usize i = 0; i < 32; ++i) {
|
||||
ref_data[i] = static_cast<u8>(i + 1);
|
||||
}
|
||||
|
||||
const u32 hash_ref =
|
||||
DataOps::crc32(Span<const u8>(ref_data.data(), ref_data.size()));
|
||||
|
||||
const u32 hash_unaligned =
|
||||
DataOps::crc32(Span<const u8>(buffer.data() + 1, 32));
|
||||
|
||||
IAT_CHECK_EQ(hash_ref, hash_unaligned);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_hash_xxhash() -> bool {
|
||||
{
|
||||
const String s = "123456789";
|
||||
const u32 result = DataOps::hash_xxhash(s);
|
||||
IAT_CHECK_EQ(result, 0x937bad67);
|
||||
}
|
||||
|
||||
{
|
||||
const String s = "The quick brown fox jumps over the lazy dog";
|
||||
const u32 result = DataOps::hash_xxhash(s);
|
||||
IAT_CHECK_EQ(result, 0xE85EA4DE);
|
||||
}
|
||||
|
||||
{
|
||||
const String s = "Test";
|
||||
const u32 r1 = DataOps::hash_xxhash(s);
|
||||
const u32 r2 = DataOps::hash_xxhash(
|
||||
Span<const u8>(reinterpret_cast<const u8 *>(s.data()), s.size()));
|
||||
IAT_CHECK_EQ(r1, r2);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_hash_fnv1a() -> bool {
|
||||
{
|
||||
const String s = "123456789";
|
||||
const u32 result = DataOps::hash_fnv1a(
|
||||
Span<const u8>(reinterpret_cast<const u8 *>(s.data()), s.size()));
|
||||
IAT_CHECK_EQ(result, 0xbb86b11c);
|
||||
}
|
||||
|
||||
{
|
||||
const u32 result = DataOps::hash_fnv1a(Span<const u8>{});
|
||||
IAT_CHECK_EQ(result, 0x811C9DC5);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_crc32);
|
||||
IAT_ADD_TEST(test_hash_fnv1a);
|
||||
IAT_ADD_TEST(test_hash_xxhash);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, DataOps)
|
||||
127
Tests/Unit/Environment.cpp
Normal file
127
Tests/Unit/Environment.cpp
Normal file
@ -0,0 +1,127 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/Environment.hpp>
|
||||
#include <IACore/IATest.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
static constexpr const char *TEST_KEY = "IA_TEST_ENV_VAR_12345";
|
||||
static constexpr const char *TEST_VAL = "Hello World";
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, Environment)
|
||||
|
||||
auto test_basic_cycle() -> bool {
|
||||
|
||||
(void)Environment::unset(TEST_KEY);
|
||||
IAT_CHECK_NOT(Environment::exists(TEST_KEY));
|
||||
|
||||
const auto set_res = Environment::set(TEST_KEY, TEST_VAL);
|
||||
IAT_CHECK(set_res.has_value());
|
||||
IAT_CHECK(Environment::exists(TEST_KEY));
|
||||
|
||||
const auto opt = Environment::find(TEST_KEY);
|
||||
IAT_CHECK(opt.has_value());
|
||||
IAT_CHECK_EQ(*opt, String(TEST_VAL));
|
||||
|
||||
const String val = Environment::get(TEST_KEY);
|
||||
IAT_CHECK_EQ(val, String(TEST_VAL));
|
||||
|
||||
(void)Environment::unset(TEST_KEY);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_overwrite() -> bool {
|
||||
(void)Environment::set(TEST_KEY, "ValueA");
|
||||
IAT_CHECK_EQ(Environment::get(TEST_KEY), String("ValueA"));
|
||||
|
||||
(void)Environment::set(TEST_KEY, "ValueB");
|
||||
IAT_CHECK_EQ(Environment::get(TEST_KEY), String("ValueB"));
|
||||
|
||||
(void)Environment::unset(TEST_KEY);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_unset() -> bool {
|
||||
(void)Environment::set(TEST_KEY, "To Be Deleted");
|
||||
IAT_CHECK(Environment::exists(TEST_KEY));
|
||||
|
||||
const auto unset_res = Environment::unset(TEST_KEY);
|
||||
IAT_CHECK(unset_res.has_value());
|
||||
|
||||
IAT_CHECK_NOT(Environment::exists(TEST_KEY));
|
||||
|
||||
const auto opt = Environment::find(TEST_KEY);
|
||||
IAT_CHECK_NOT(opt.has_value());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_defaults() -> bool {
|
||||
const char *ghost_key = "IA_THIS_KEY_DOES_NOT_EXIST";
|
||||
|
||||
(void)Environment::unset(ghost_key);
|
||||
|
||||
const String empty = Environment::get(ghost_key);
|
||||
IAT_CHECK(empty.empty());
|
||||
|
||||
const String fallback = Environment::get(ghost_key, "MyDefault");
|
||||
IAT_CHECK_EQ(fallback, String("MyDefault"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_empty_value() -> bool {
|
||||
(void)Environment::set(TEST_KEY, "");
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
|
||||
#else
|
||||
|
||||
IAT_CHECK(Environment::exists(TEST_KEY));
|
||||
const auto opt = Environment::find(TEST_KEY);
|
||||
IAT_CHECK(opt.has_value());
|
||||
IAT_CHECK(opt->empty());
|
||||
#endif
|
||||
|
||||
(void)Environment::unset(TEST_KEY);
|
||||
IAT_CHECK_NOT(Environment::exists(TEST_KEY));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_bad_input() -> bool {
|
||||
|
||||
const auto res = Environment::set("", "Value");
|
||||
IAT_CHECK_NOT(res.has_value());
|
||||
|
||||
const auto res_unset = Environment::unset("");
|
||||
IAT_CHECK_NOT(res_unset.has_value());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_basic_cycle);
|
||||
IAT_ADD_TEST(test_overwrite);
|
||||
IAT_ADD_TEST(test_unset);
|
||||
IAT_ADD_TEST(test_defaults);
|
||||
IAT_ADD_TEST(test_empty_value);
|
||||
IAT_ADD_TEST(test_bad_input);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, Environment)
|
||||
156
Tests/Unit/FileOps.cpp
Normal file
156
Tests/Unit/FileOps.cpp
Normal file
@ -0,0 +1,156 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/FileOps.hpp>
|
||||
#include <IACore/IATest.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, FileOps)
|
||||
|
||||
void cleanup_file(const Path &path) {
|
||||
std::error_code ec;
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
std::filesystem::remove(path, ec);
|
||||
}
|
||||
}
|
||||
|
||||
auto test_text_io() -> bool {
|
||||
const Path path = "iatest_fileops_text.txt";
|
||||
const String content = "Hello IACore FileOps!\nLine 2";
|
||||
|
||||
const auto write_res = FileOps::write_text_file(path, content, true);
|
||||
IAT_CHECK(write_res.has_value());
|
||||
IAT_CHECK_EQ(*write_res, content.size());
|
||||
|
||||
const auto read_res = FileOps::read_text_file(path);
|
||||
IAT_CHECK(read_res.has_value());
|
||||
IAT_CHECK_EQ(*read_res, content);
|
||||
|
||||
cleanup_file(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_binary_io() -> bool {
|
||||
const Path path = "iatest_fileops_bin.bin";
|
||||
const Vec<u8> content = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF};
|
||||
|
||||
const auto write_res = FileOps::write_binary_file(path, content, true);
|
||||
IAT_CHECK(write_res.has_value());
|
||||
IAT_CHECK_EQ(*write_res, content.size());
|
||||
|
||||
const auto read_res = FileOps::read_binary_file(path);
|
||||
IAT_CHECK(read_res.has_value());
|
||||
IAT_CHECK_EQ(read_res->size(), content.size());
|
||||
|
||||
for (usize i = 0; i < content.size(); ++i) {
|
||||
IAT_CHECK_EQ((*read_res)[i], content[i]);
|
||||
}
|
||||
|
||||
cleanup_file(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_file_mapping() -> bool {
|
||||
const Path path = "iatest_fileops_map.txt";
|
||||
const String content = "MappedContent";
|
||||
|
||||
(void)FileOps::write_text_file(path, content, true);
|
||||
|
||||
usize size = 0;
|
||||
const auto map_res = FileOps::map_file(path, size);
|
||||
IAT_CHECK(map_res.has_value());
|
||||
IAT_CHECK_EQ(size, content.size());
|
||||
|
||||
const u8 *ptr = *map_res;
|
||||
IAT_CHECK(ptr != nullptr);
|
||||
|
||||
String read_back(reinterpret_cast<const char *>(ptr), size);
|
||||
IAT_CHECK_EQ(read_back, content);
|
||||
|
||||
FileOps::unmap_file(ptr);
|
||||
|
||||
cleanup_file(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_shared_memory() -> bool {
|
||||
const String shm_name = "iatest_shm_block";
|
||||
const usize shm_size = 4096;
|
||||
|
||||
auto owner_res = FileOps::map_shared_memory(shm_name, shm_size, true);
|
||||
IAT_CHECK(owner_res.has_value());
|
||||
u8 *owner_ptr = *owner_res;
|
||||
|
||||
std::memset(owner_ptr, 0, shm_size);
|
||||
const String msg = "Shared Memory Message";
|
||||
std::memcpy(owner_ptr, msg.data(), msg.size());
|
||||
|
||||
auto client_res = FileOps::map_shared_memory(shm_name, shm_size, false);
|
||||
IAT_CHECK(client_res.has_value());
|
||||
u8 *client_ptr = *client_res;
|
||||
|
||||
String read_msg(reinterpret_cast<const char *>(client_ptr), msg.size());
|
||||
IAT_CHECK_EQ(read_msg, msg);
|
||||
|
||||
FileOps::unmap_file(owner_ptr);
|
||||
FileOps::unmap_file(client_ptr);
|
||||
FileOps::unlink_shared_memory(shm_name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_stream_integration() -> bool {
|
||||
const Path path = "iatest_fileops_stream.bin";
|
||||
cleanup_file(path);
|
||||
|
||||
{
|
||||
auto writer_res = FileOps::stream_to_file(path, true);
|
||||
IAT_CHECK(writer_res.has_value());
|
||||
auto &writer = *writer_res;
|
||||
|
||||
(void)writer.write<u32>(0x12345678);
|
||||
(void)writer.write<u8>(0xFF);
|
||||
}
|
||||
|
||||
{
|
||||
auto reader_res = FileOps::stream_from_file(path);
|
||||
IAT_CHECK(reader_res.has_value());
|
||||
auto &reader = *reader_res;
|
||||
|
||||
auto val_u32 = reader.read<u32>();
|
||||
IAT_CHECK(val_u32.has_value());
|
||||
IAT_CHECK_EQ(*val_u32, 0x12345678u);
|
||||
|
||||
auto val_u8 = reader.read<u8>();
|
||||
IAT_CHECK(val_u8.has_value());
|
||||
IAT_CHECK_EQ(*val_u8, 0xFF);
|
||||
}
|
||||
|
||||
cleanup_file(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_text_io);
|
||||
IAT_ADD_TEST(test_binary_io);
|
||||
IAT_ADD_TEST(test_file_mapping);
|
||||
IAT_ADD_TEST(test_shared_memory);
|
||||
IAT_ADD_TEST(test_stream_integration);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, FileOps)
|
||||
130
Tests/Unit/IPC.cpp
Normal file
130
Tests/Unit/IPC.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/FileOps.hpp>
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <IACore/IPC.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, IPC)
|
||||
|
||||
auto test_layout_constraints() -> bool {
|
||||
|
||||
IAT_CHECK_EQ(alignof(IpcSharedMemoryLayout), static_cast<usize>(64));
|
||||
|
||||
IAT_CHECK_EQ(offsetof(IpcSharedMemoryLayout, meta), static_cast<usize>(0));
|
||||
|
||||
IAT_CHECK_EQ(offsetof(IpcSharedMemoryLayout, moni_control),
|
||||
static_cast<usize>(64));
|
||||
|
||||
IAT_CHECK_EQ(offsetof(IpcSharedMemoryLayout, mino_control),
|
||||
static_cast<usize>(192));
|
||||
|
||||
IAT_CHECK_EQ(offsetof(IpcSharedMemoryLayout, moni_data_offset),
|
||||
static_cast<usize>(320));
|
||||
|
||||
IAT_CHECK_EQ(sizeof(IpcSharedMemoryLayout) % 64, static_cast<usize>(0));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_manual_shm_ringbuffer() -> bool {
|
||||
const String shm_name = "IA_TEST_IPC_LAYOUT_CHECK";
|
||||
const usize shm_size = 16 * 1024;
|
||||
|
||||
FileOps::unlink_shared_memory(shm_name);
|
||||
|
||||
auto map_res = FileOps::map_shared_memory(shm_name, shm_size, true);
|
||||
IAT_CHECK(map_res.has_value());
|
||||
|
||||
u8 *base_ptr = *map_res;
|
||||
auto *layout = reinterpret_cast<IpcSharedMemoryLayout *>(base_ptr);
|
||||
|
||||
layout->meta.magic = 0xDEADBEEF;
|
||||
layout->meta.version = 1;
|
||||
layout->meta.total_size = shm_size;
|
||||
|
||||
const usize header_size = IpcSharedMemoryLayout::get_header_size();
|
||||
const usize data_available = shm_size - header_size;
|
||||
const usize half_data = data_available / 2;
|
||||
|
||||
layout->moni_data_offset = header_size;
|
||||
layout->moni_data_size = half_data;
|
||||
|
||||
layout->mino_data_offset = header_size + half_data;
|
||||
layout->mino_data_size = half_data;
|
||||
|
||||
Span<u8> moni_data_span(base_ptr + layout->moni_data_offset,
|
||||
static_cast<usize>(layout->moni_data_size));
|
||||
auto moni_res =
|
||||
RingBufferView::create(&layout->moni_control, moni_data_span, true);
|
||||
IAT_CHECK(moni_res.has_value());
|
||||
auto moni = std::move(*moni_res);
|
||||
|
||||
Span<u8> mino_data_span(base_ptr + layout->mino_data_offset,
|
||||
static_cast<usize>(layout->mino_data_size));
|
||||
auto mino_res =
|
||||
RingBufferView::create(&layout->mino_control, mino_data_span, true);
|
||||
IAT_CHECK(mino_res.has_value());
|
||||
auto _ = std::move(*mino_res);
|
||||
|
||||
String msg = "IPC_TEST_MESSAGE";
|
||||
IAT_CHECK(
|
||||
moni.push(100, Span<const u8>(reinterpret_cast<const u8 *>(msg.data()),
|
||||
msg.size()))
|
||||
.has_value());
|
||||
|
||||
auto moni_reader_res =
|
||||
RingBufferView::create(&layout->moni_control, moni_data_span, false);
|
||||
IAT_CHECK(moni_reader_res.has_value());
|
||||
auto moni_reader = std::move(*moni_reader_res);
|
||||
|
||||
RingBufferView::PacketHeader header;
|
||||
u8 buffer[128];
|
||||
auto pop_res = moni_reader.pop(header, Span<u8>(buffer, 128));
|
||||
IAT_CHECK(pop_res.has_value());
|
||||
IAT_CHECK(pop_res->has_value());
|
||||
IAT_CHECK_EQ(header.id, static_cast<u16>(100));
|
||||
|
||||
String received((char *)buffer, *pop_res.value());
|
||||
IAT_CHECK_EQ(received, msg);
|
||||
|
||||
FileOps::unmap_file(base_ptr);
|
||||
FileOps::unlink_shared_memory(shm_name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class TestManager : public IpcManager {
|
||||
public:
|
||||
void on_signal(NativeProcessID, u8) override {}
|
||||
void on_packet(NativeProcessID, u16, Span<const u8>) override {}
|
||||
};
|
||||
|
||||
auto test_manager_instantiation() -> bool {
|
||||
TestManager mgr;
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_layout_constraints);
|
||||
IAT_ADD_TEST(test_manual_shm_ringbuffer);
|
||||
IAT_ADD_TEST(test_manager_instantiation);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, IPC)
|
||||
164
Tests/Unit/JSON.cpp
Normal file
164
Tests/Unit/JSON.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <IACore/JSON.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
struct UserProfile {
|
||||
String username;
|
||||
u32 id;
|
||||
bool is_active;
|
||||
Vec<String> roles;
|
||||
|
||||
bool operator==(const UserProfile &other) const {
|
||||
return username == other.username && id == other.id &&
|
||||
is_active == other.is_active && roles == other.roles;
|
||||
}
|
||||
};
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, JSON)
|
||||
|
||||
auto test_dynamic_parse() -> bool {
|
||||
const String json_text = R"({
|
||||
"string": "Hello World",
|
||||
"int": 42,
|
||||
"float": 3.14159,
|
||||
"bool": true,
|
||||
"array": [10, 20, 30],
|
||||
"object": { "key": "value" }
|
||||
})";
|
||||
|
||||
auto res = Json::parse(json_text);
|
||||
IAT_CHECK(res.has_value());
|
||||
|
||||
const auto &j = *res;
|
||||
|
||||
IAT_CHECK(j["string"].is_string());
|
||||
IAT_CHECK_EQ(j["string"].get<String>(), String("Hello World"));
|
||||
|
||||
IAT_CHECK(j["int"].is_number_integer());
|
||||
IAT_CHECK_EQ(j["int"].get<i32>(), 42);
|
||||
|
||||
IAT_CHECK(j["float"].is_number_float());
|
||||
IAT_CHECK_APPROX(j["float"].get<f32>(), 3.14159f);
|
||||
|
||||
IAT_CHECK(j["bool"].is_boolean());
|
||||
IAT_CHECK_EQ(j["bool"].get<bool>(), true);
|
||||
|
||||
IAT_CHECK(j["array"].is_array());
|
||||
IAT_CHECK_EQ(j["array"].size(), 3u);
|
||||
IAT_CHECK_EQ(j["array"][0].get<i32>(), 10);
|
||||
|
||||
IAT_CHECK(j["object"].is_object());
|
||||
IAT_CHECK_EQ(j["object"]["key"].get<String>(), String("value"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_dynamic_encode() -> bool {
|
||||
nlohmann::json j;
|
||||
j["name"] = "IACore";
|
||||
j["version"] = 2;
|
||||
|
||||
const String encoded = Json::encode(j);
|
||||
|
||||
IAT_CHECK(encoded.find("IACore") != String::npos);
|
||||
IAT_CHECK(encoded.find("version") != String::npos);
|
||||
IAT_CHECK(encoded.find("2") != String::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_parse_invalid() -> bool {
|
||||
const String bad_json = "{ key: value }";
|
||||
auto res = Json::parse(bad_json);
|
||||
IAT_CHECK_NOT(res.has_value());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_struct_round_trip() -> bool {
|
||||
UserProfile original{.username = "test_user",
|
||||
.id = 12345,
|
||||
.is_active = true,
|
||||
.roles = {"admin", "editor"}};
|
||||
|
||||
auto encode_res = Json::encode_struct(original);
|
||||
IAT_CHECK(encode_res.has_value());
|
||||
String json_str = *encode_res;
|
||||
|
||||
IAT_CHECK(json_str.find("test_user") != String::npos);
|
||||
IAT_CHECK(json_str.find("roles") != String::npos);
|
||||
|
||||
auto decode_res = Json::parse_to_struct<UserProfile>(json_str);
|
||||
IAT_CHECK(decode_res.has_value());
|
||||
|
||||
UserProfile decoded = *decode_res;
|
||||
IAT_CHECK(decoded == original);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_struct_parse_error() -> bool {
|
||||
const String malformed = "{ broken_json: ";
|
||||
auto res = Json::parse_to_struct<UserProfile>(malformed);
|
||||
IAT_CHECK_NOT(res.has_value());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_read_only() -> bool {
|
||||
const String json_text = R"({
|
||||
"id": 999,
|
||||
"name": "Simd",
|
||||
"scores": [1.1, 2.2]
|
||||
})";
|
||||
|
||||
auto res = Json::parse_read_only(json_text);
|
||||
IAT_CHECK(res.has_value());
|
||||
|
||||
auto &doc = *res;
|
||||
simdjson::dom::element root = doc.root();
|
||||
|
||||
u64 id = 0;
|
||||
auto err_id = root["id"].get(id);
|
||||
IAT_CHECK(!err_id);
|
||||
IAT_CHECK_EQ(id, 999ULL);
|
||||
|
||||
std::string_view name;
|
||||
auto err_name = root["name"].get(name);
|
||||
IAT_CHECK(!err_name);
|
||||
IAT_CHECK_EQ(String(name), String("Simd"));
|
||||
|
||||
simdjson::dom::array scores;
|
||||
auto err_arr = root["scores"].get(scores);
|
||||
IAT_CHECK(!err_arr);
|
||||
IAT_CHECK_EQ(scores.size(), 2u);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_dynamic_parse);
|
||||
IAT_ADD_TEST(test_dynamic_encode);
|
||||
IAT_ADD_TEST(test_parse_invalid);
|
||||
IAT_ADD_TEST(test_struct_round_trip);
|
||||
IAT_ADD_TEST(test_struct_parse_error);
|
||||
IAT_ADD_TEST(test_read_only);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, JSON)
|
||||
118
Tests/Unit/Logger.cpp
Normal file
118
Tests/Unit/Logger.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/FileOps.hpp>
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <IACore/Logger.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, Logger)
|
||||
|
||||
static constexpr const char *LOG_FILE = "iacore_test_log.txt";
|
||||
|
||||
auto test_file_logging() -> bool {
|
||||
|
||||
const auto res = Logger::enable_logging_to_disk(LOG_FILE);
|
||||
IAT_CHECK(res.has_value());
|
||||
|
||||
Logger::set_log_level(Logger::LogLevel::Trace);
|
||||
|
||||
const String msg_info = "Test_Info_Msg_123";
|
||||
const String msg_err = "Test_Error_Msg_456";
|
||||
const String msg_warn = "Test_Warn_Msg_789";
|
||||
|
||||
Logger::info("{}", msg_info);
|
||||
Logger::error("{}", msg_err);
|
||||
Logger::warn("{}", msg_warn);
|
||||
|
||||
Logger::flush_logs();
|
||||
|
||||
auto read_res = FileOps::read_text_file(LOG_FILE);
|
||||
if (!read_res) {
|
||||
std::cout << console::YELLOW << " Warning: Could not read log file ("
|
||||
<< read_res.error() << "). Skipping verification.\n"
|
||||
<< console::RESET;
|
||||
return true;
|
||||
}
|
||||
|
||||
const String content = *read_res;
|
||||
|
||||
IAT_CHECK(content.find(msg_info) != String::npos);
|
||||
IAT_CHECK(content.find(msg_err) != String::npos);
|
||||
IAT_CHECK(content.find(msg_warn) != String::npos);
|
||||
|
||||
IAT_CHECK(content.find("INFO") != String::npos);
|
||||
IAT_CHECK(content.find("ERROR") != String::npos);
|
||||
IAT_CHECK(content.find("WARN") != String::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_log_levels() -> bool {
|
||||
|
||||
Logger::set_log_level(Logger::LogLevel::Warn);
|
||||
|
||||
const String unique_info = "Hidden_Info_Msg";
|
||||
const String unique_warn = "Visible_Warn_Msg";
|
||||
|
||||
Logger::info("{}", unique_info);
|
||||
Logger::warn("{}", unique_warn);
|
||||
|
||||
Logger::flush_logs();
|
||||
|
||||
auto read_res = FileOps::read_text_file(LOG_FILE);
|
||||
if (!read_res) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const String content = *read_res;
|
||||
|
||||
IAT_CHECK(content.find(unique_info) == String::npos);
|
||||
|
||||
IAT_CHECK(content.find(unique_warn) != String::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_formatting() -> bool {
|
||||
Logger::set_log_level(Logger::LogLevel::Info);
|
||||
|
||||
const String name = "IACore";
|
||||
const i32 version = 99;
|
||||
|
||||
Logger::info("System {} online v{}", name, version);
|
||||
Logger::flush_logs();
|
||||
|
||||
auto read_res = FileOps::read_text_file(LOG_FILE);
|
||||
if (!read_res) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const String content = *read_res;
|
||||
IAT_CHECK(content.find("System IACore online v99") != String::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_file_logging);
|
||||
IAT_ADD_TEST(test_log_levels);
|
||||
IAT_ADD_TEST(test_formatting);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, Logger)
|
||||
41
Tests/Unit/Main.cpp
Normal file
41
Tests/Unit/Main.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/IACore.hpp>
|
||||
#include <IACore/IATest.hpp>
|
||||
|
||||
#include <IACore/SocketOps.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IACORE_MAIN() {
|
||||
(void)args;
|
||||
|
||||
OX_TRY_PURE(SocketOps::initialize());
|
||||
|
||||
std::cout
|
||||
<< console::GREEN
|
||||
<< "\n===============================================================\n";
|
||||
std::cout << " IACore (Independent Architecture Core) - Unit Test Suite\n";
|
||||
std::cout
|
||||
<< "===============================================================\n"
|
||||
<< console::RESET << "\n";
|
||||
|
||||
Const<i32> result = Test::TestRegistry::run_all();
|
||||
|
||||
SocketOps::terminate();
|
||||
|
||||
return result;
|
||||
}
|
||||
119
Tests/Unit/Platform.cpp
Normal file
119
Tests/Unit/Platform.cpp
Normal file
@ -0,0 +1,119 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <IACore/Platform.hpp>
|
||||
#include <cstring>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, Platform)
|
||||
|
||||
auto test_os_name() -> bool {
|
||||
const char *os_name = Platform::get_operating_system_name();
|
||||
IAT_CHECK(os_name != nullptr);
|
||||
|
||||
const String os(os_name);
|
||||
IAT_CHECK(!os.empty());
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
IAT_CHECK_EQ(os, String("Windows"));
|
||||
#elif IA_PLATFORM_LINUX
|
||||
IAT_CHECK_EQ(os, String("Linux"));
|
||||
#elif IA_PLATFORM_APPLE
|
||||
IAT_CHECK_EQ(os, String("MacOS"));
|
||||
#elif IA_PLATFORM_WASM
|
||||
IAT_CHECK_EQ(os, String("WebAssembly"));
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_arch_name() -> bool {
|
||||
const char *arch_name = Platform::get_architecture_name();
|
||||
IAT_CHECK(arch_name != nullptr);
|
||||
|
||||
const String arch(arch_name);
|
||||
IAT_CHECK(!arch.empty());
|
||||
|
||||
#if IA_ARCH_X64
|
||||
IAT_CHECK_EQ(arch, String("x86_64"));
|
||||
#elif IA_ARCH_ARM64
|
||||
IAT_CHECK_EQ(arch, String("ARM64"));
|
||||
#elif IA_ARCH_WASM
|
||||
IAT_CHECK_EQ(arch, String("WASM"));
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_capabilities() -> bool {
|
||||
|
||||
const bool check_result = Platform::check_cpu();
|
||||
IAT_CHECK(check_result);
|
||||
|
||||
const auto &caps = Platform::get_capabilities();
|
||||
|
||||
volatile bool has_crc = caps.hardware_crc32;
|
||||
(void)has_crc;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if IA_ARCH_X64
|
||||
auto test_cpuid() -> bool {
|
||||
i32 regs[4] = {0};
|
||||
|
||||
Platform::cpuid(0, 0, regs);
|
||||
|
||||
IAT_CHECK(regs[0] >= 0);
|
||||
|
||||
char vendor[13];
|
||||
std::memset(vendor, 0, 13);
|
||||
|
||||
std::memcpy(vendor, ®s[1], 4);
|
||||
std::memcpy(vendor + 4, ®s[3], 4);
|
||||
std::memcpy(vendor + 8, ®s[2], 4);
|
||||
vendor[12] = '\0';
|
||||
|
||||
const String vendor_str(vendor);
|
||||
IAT_CHECK(!vendor_str.empty());
|
||||
|
||||
bool is_known =
|
||||
(vendor_str == "GenuineIntel" || vendor_str == "AuthenticAMD" ||
|
||||
vendor_str == "KVMKVMKVM" || vendor_str == "Microsoft Hv" ||
|
||||
vendor_str == "VBoxVBoxVBox");
|
||||
|
||||
if (!is_known) {
|
||||
|
||||
std::cout << " [Info] Unknown CPU Vendor: " << vendor_str << "\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_os_name);
|
||||
IAT_ADD_TEST(test_arch_name);
|
||||
IAT_ADD_TEST(test_capabilities);
|
||||
#if IA_ARCH_X64
|
||||
IAT_ADD_TEST(test_cpuid);
|
||||
#endif
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, Platform)
|
||||
213
Tests/Unit/ProcessOps.cpp
Normal file
213
Tests/Unit/ProcessOps.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <IACore/ProcessOps.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
#define CMD_ECHO_EXE "cmd.exe"
|
||||
#define CMD_ARG_PREFIX "/c echo"
|
||||
#define NULL_DEVICE "NUL"
|
||||
#else
|
||||
#define CMD_ECHO_EXE "/bin/echo"
|
||||
#define CMD_ARG_PREFIX ""
|
||||
#define NULL_DEVICE "/dev/null"
|
||||
#endif
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, ProcessOps)
|
||||
|
||||
auto test_basic_run() -> bool {
|
||||
|
||||
String captured;
|
||||
|
||||
const auto result =
|
||||
ProcessOps::spawn_process_sync(CMD_ECHO_EXE, CMD_ARG_PREFIX " HelloIA",
|
||||
[&](StringView line) { captured = line; });
|
||||
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 0);
|
||||
|
||||
IAT_CHECK(captured.find("HelloIA") != String::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_arguments() -> bool {
|
||||
Vec<String> lines;
|
||||
|
||||
String args = String(CMD_ARG_PREFIX) + " one two";
|
||||
if (!args.empty() && args[0] == ' ') {
|
||||
args.erase(0, 1);
|
||||
}
|
||||
|
||||
const auto result =
|
||||
ProcessOps::spawn_process_sync(CMD_ECHO_EXE, args, [&](StringView line) {
|
||||
lines.push_back(String(line));
|
||||
});
|
||||
|
||||
IAT_CHECK_EQ(*result, 0);
|
||||
IAT_CHECK(lines.size() > 0);
|
||||
|
||||
IAT_CHECK(lines[0].find("one two") != String::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_exit_codes() -> bool {
|
||||
|
||||
String cmd;
|
||||
String arg;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
cmd = "cmd.exe";
|
||||
arg = "/c exit 42";
|
||||
#else
|
||||
cmd = "/bin/sh";
|
||||
arg = "-c \"exit 42\"";
|
||||
#endif
|
||||
|
||||
const auto result =
|
||||
ProcessOps::spawn_process_sync(cmd, arg, [](StringView) {});
|
||||
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 42);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_missing_exe() -> bool {
|
||||
|
||||
const auto result =
|
||||
ProcessOps::spawn_process_sync("sdflkjghsdflkjg", "", [](StringView) {});
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
IAT_CHECK_NOT(result.has_value());
|
||||
#else
|
||||
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 127);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_large_output() -> bool {
|
||||
|
||||
String massive_string;
|
||||
massive_string.reserve(5000);
|
||||
for (i32 i = 0; i < 500; ++i) {
|
||||
massive_string += "1234567890";
|
||||
}
|
||||
|
||||
String cmd;
|
||||
String arg;
|
||||
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
cmd = "cmd.exe";
|
||||
|
||||
arg = "/c echo " + massive_string;
|
||||
#else
|
||||
cmd = "/bin/echo";
|
||||
arg = massive_string;
|
||||
#endif
|
||||
|
||||
String captured;
|
||||
const auto result = ProcessOps::spawn_process_sync(
|
||||
cmd, arg, [&](StringView line) { captured += line; });
|
||||
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 0);
|
||||
|
||||
IAT_CHECK_EQ(captured.length(), massive_string.length());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_multi_line() -> bool {
|
||||
|
||||
String cmd;
|
||||
String arg;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
cmd = "cmd.exe";
|
||||
arg = "/c \"echo LineA && echo LineB\"";
|
||||
#else
|
||||
cmd = "/bin/sh";
|
||||
arg = "-c \"echo LineA; echo LineB\"";
|
||||
#endif
|
||||
|
||||
i32 line_count = 0;
|
||||
bool found_a = false;
|
||||
bool found_b = false;
|
||||
|
||||
const auto res =
|
||||
ProcessOps::spawn_process_sync(cmd, arg, [&](StringView line) {
|
||||
line_count++;
|
||||
if (line.find("LineA") != String::npos) {
|
||||
found_a = true;
|
||||
}
|
||||
if (line.find("LineB") != String::npos) {
|
||||
found_b = true;
|
||||
}
|
||||
});
|
||||
IAT_CHECK(res.has_value());
|
||||
|
||||
IAT_CHECK(found_a);
|
||||
IAT_CHECK(found_b);
|
||||
|
||||
IAT_CHECK(line_count >= 2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_complex_arguments() -> bool {
|
||||
|
||||
const String complex_args =
|
||||
"-DDEFINED_MSG=\\\"Hello World\\\" -v path/to/file";
|
||||
|
||||
const String cmd = CMD_ECHO_EXE;
|
||||
|
||||
String final_args;
|
||||
#if IA_PLATFORM_WINDOWS
|
||||
final_args = "/c echo " + complex_args;
|
||||
#else
|
||||
final_args = complex_args;
|
||||
#endif
|
||||
|
||||
String captured;
|
||||
const auto result = ProcessOps::spawn_process_sync(
|
||||
cmd, final_args, [&](StringView line) { captured += line; });
|
||||
|
||||
IAT_CHECK(result.has_value());
|
||||
IAT_CHECK_EQ(*result, 0);
|
||||
|
||||
IAT_CHECK(captured.find("Hello World") != String::npos);
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_basic_run);
|
||||
IAT_ADD_TEST(test_arguments);
|
||||
IAT_ADD_TEST(test_exit_codes);
|
||||
IAT_ADD_TEST(test_missing_exe);
|
||||
IAT_ADD_TEST(test_large_output);
|
||||
IAT_ADD_TEST(test_multi_line);
|
||||
IAT_ADD_TEST(test_complex_arguments);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, ProcessOps)
|
||||
107
Tests/Unit/RingBuffer.cpp
Normal file
107
Tests/Unit/RingBuffer.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/ADT/RingBuffer.hpp>
|
||||
#include <IACore/IATest.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, RingBuffer)
|
||||
|
||||
auto test_push_pop() -> bool {
|
||||
|
||||
Vec<u8> memory(sizeof(RingBufferView::ControlBlock) + 1024);
|
||||
|
||||
auto producer_res = RingBufferView::create(Span<u8>(memory), true);
|
||||
IAT_CHECK(producer_res.has_value());
|
||||
auto producer = std::move(*producer_res);
|
||||
|
||||
auto consumer_res = RingBufferView::create(Span<u8>(memory), false);
|
||||
IAT_CHECK(consumer_res.has_value());
|
||||
auto consumer = std::move(*consumer_res);
|
||||
|
||||
String msg = "Hello RingBuffer";
|
||||
const auto push_res = producer.push(
|
||||
1, Span<const u8>(reinterpret_cast<const u8 *>(msg.data()), msg.size()));
|
||||
IAT_CHECK(push_res.has_value());
|
||||
|
||||
RingBufferView::PacketHeader header;
|
||||
u8 read_buf[128];
|
||||
|
||||
const auto pop_res = consumer.pop(header, Span<u8>(read_buf, 128));
|
||||
IAT_CHECK(pop_res.has_value());
|
||||
|
||||
const auto bytes_read_opt = *pop_res;
|
||||
IAT_CHECK(bytes_read_opt.has_value());
|
||||
|
||||
const usize bytes_read = *bytes_read_opt;
|
||||
|
||||
IAT_CHECK_EQ(header.id, static_cast<u16>(1));
|
||||
IAT_CHECK_EQ(bytes_read, static_cast<usize>(msg.size()));
|
||||
|
||||
String read_msg(reinterpret_cast<char *>(read_buf), bytes_read);
|
||||
IAT_CHECK_EQ(read_msg, msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_wrap_around() -> bool {
|
||||
|
||||
Vec<u8> memory(sizeof(RingBufferView::ControlBlock) + 100);
|
||||
|
||||
auto rb_res = RingBufferView::create(Span<u8>(memory), true);
|
||||
IAT_CHECK(rb_res.has_value());
|
||||
auto rb = std::move(*rb_res);
|
||||
|
||||
Vec<u8> junk(80, 0xFF);
|
||||
const auto push1 = rb.push(1, junk);
|
||||
IAT_CHECK(push1.has_value());
|
||||
|
||||
RingBufferView::PacketHeader header;
|
||||
u8 out_buf[100];
|
||||
const auto pop1 = rb.pop(header, out_buf);
|
||||
IAT_CHECK(pop1.has_value());
|
||||
IAT_CHECK(pop1->has_value());
|
||||
|
||||
Vec<u8> wrap_data(40, 0xAA);
|
||||
const auto push2 = rb.push(2, wrap_data);
|
||||
IAT_CHECK(push2.has_value());
|
||||
|
||||
const auto pop2 = rb.pop(header, out_buf);
|
||||
IAT_CHECK(pop2.has_value());
|
||||
IAT_CHECK(pop2->has_value());
|
||||
|
||||
const usize pop_size = *pop2.value();
|
||||
IAT_CHECK_EQ(pop_size, static_cast<usize>(40));
|
||||
|
||||
bool match = true;
|
||||
for (usize i = 0; i < 40; i++) {
|
||||
if (out_buf[i] != 0xAA) {
|
||||
match = false;
|
||||
}
|
||||
}
|
||||
IAT_CHECK(match);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_push_pop);
|
||||
IAT_ADD_TEST(test_wrap_around);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, RingBuffer)
|
||||
102
Tests/Unit/SIMD/FloatVec4.cpp
Normal file
102
Tests/Unit/SIMD/FloatVec4.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <IACore/SIMD.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, FloatVec4)
|
||||
|
||||
auto test_float_arithmetic() -> bool {
|
||||
FloatVec4 v1(10.0f, 20.0f, 30.0f, 40.0f);
|
||||
FloatVec4 v2(2.0f, 4.0f, 5.0f, 8.0f);
|
||||
|
||||
alignas(16) f32 res[4];
|
||||
|
||||
(v1 / v2).store(res);
|
||||
IAT_CHECK_APPROX(res[0], 5.0f);
|
||||
IAT_CHECK_APPROX(res[3], 5.0f);
|
||||
|
||||
(v1 * v2).store(res);
|
||||
IAT_CHECK_APPROX(res[0], 20.0f);
|
||||
|
||||
(v1 + v2).store(res);
|
||||
IAT_CHECK_APPROX(res[0], 12.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_math_helpers() -> bool {
|
||||
alignas(16) f32 res[4];
|
||||
|
||||
FloatVec4 v_sq(4.0f, 9.0f, 16.0f, 25.0f);
|
||||
v_sq.sqrt().store(res);
|
||||
IAT_CHECK_APPROX(res[0], 2.0f);
|
||||
IAT_CHECK_APPROX(res[3], 5.0f);
|
||||
|
||||
FloatVec4 v_neg(-1.0f, -5.0f, 10.0f, -0.0f);
|
||||
v_neg.abs().store(res);
|
||||
IAT_CHECK_APPROX(res[0], 1.0f);
|
||||
IAT_CHECK_APPROX(res[2], 10.0f);
|
||||
|
||||
FloatVec4 v_clamp(-100.0f, 0.0f, 50.0f, 200.0f);
|
||||
v_clamp.clamp(0.0f, 100.0f).store(res);
|
||||
IAT_CHECK_APPROX(res[0], 0.0f);
|
||||
IAT_CHECK_APPROX(res[2], 50.0f);
|
||||
IAT_CHECK_APPROX(res[3], 100.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_approx_math() -> bool {
|
||||
alignas(16) f32 res[4];
|
||||
FloatVec4 v(16.0f, 25.0f, 100.0f, 1.0f);
|
||||
|
||||
v.rsqrt().store(res);
|
||||
|
||||
IAT_CHECK_APPROX(res[0], 0.25f);
|
||||
IAT_CHECK_APPROX(res[2], 0.1f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_linear_algebra() -> bool {
|
||||
FloatVec4 v1(1.0f, 2.0f, 3.0f, 4.0f);
|
||||
FloatVec4 v2(1.0f, 0.0f, 1.0f, 0.0f);
|
||||
|
||||
f32 dot = v1.dot(v2);
|
||||
IAT_CHECK_APPROX(dot, 4.0f);
|
||||
|
||||
FloatVec4 v_norm(10.0f, 0.0f, 0.0f, 0.0f);
|
||||
alignas(16) f32 res[4];
|
||||
|
||||
v_norm.normalize().store(res);
|
||||
IAT_CHECK_APPROX(res[0], 1.0f);
|
||||
IAT_CHECK_APPROX(res[1], 0.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_float_arithmetic);
|
||||
IAT_ADD_TEST(test_math_helpers);
|
||||
IAT_ADD_TEST(test_approx_math);
|
||||
IAT_ADD_TEST(test_linear_algebra);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, FloatVec4)
|
||||
144
Tests/Unit/SIMD/IntVec4.cpp
Normal file
144
Tests/Unit/SIMD/IntVec4.cpp
Normal file
@ -0,0 +1,144 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <IACore/SIMD.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, IntVec4)
|
||||
|
||||
auto test_constructors() -> bool {
|
||||
IntVec4 v_broadcast(10);
|
||||
alignas(16) u32 store_buf[4];
|
||||
v_broadcast.store(store_buf);
|
||||
|
||||
IAT_CHECK_EQ(store_buf[0], 10U);
|
||||
IAT_CHECK_EQ(store_buf[3], 10U);
|
||||
|
||||
IntVec4 v_comp(1, 2, 3, 4);
|
||||
v_comp.store(store_buf);
|
||||
IAT_CHECK_EQ(store_buf[0], 1U);
|
||||
IAT_CHECK_EQ(store_buf[3], 4U);
|
||||
|
||||
alignas(16) u32 src_buf[4] = {100, 200, 300, 400};
|
||||
IntVec4 v_load = IntVec4::load(src_buf);
|
||||
v_load.store(store_buf);
|
||||
IAT_CHECK_EQ(store_buf[1], 200U);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_arithmetic() -> bool {
|
||||
const IntVec4 v1(10, 20, 30, 40);
|
||||
const IntVec4 v2(1, 2, 3, 4);
|
||||
|
||||
IntVec4 v_add = v1 + v2;
|
||||
alignas(16) u32 res[4];
|
||||
v_add.store(res);
|
||||
IAT_CHECK_EQ(res[0], 11U);
|
||||
IAT_CHECK_EQ(res[3], 44U);
|
||||
|
||||
IntVec4 v_sub = v1 - v2;
|
||||
v_sub.store(res);
|
||||
IAT_CHECK_EQ(res[0], 9U);
|
||||
|
||||
IntVec4 v_mul = v1 * v2;
|
||||
v_mul.store(res);
|
||||
IAT_CHECK_EQ(res[0], 10U);
|
||||
IAT_CHECK_EQ(res[2], 90U);
|
||||
IAT_CHECK_EQ(res[3], 160U);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_bitwise() -> bool {
|
||||
const IntVec4 v_all_ones(0xFFFFFFFF);
|
||||
const IntVec4 v_zero((u32)0);
|
||||
const IntVec4 v_pattern(0xAAAAAAAA);
|
||||
|
||||
alignas(16) u32 res[4];
|
||||
|
||||
(v_all_ones & v_pattern).store(res);
|
||||
IAT_CHECK_EQ(res[0], 0xAAAAAAAAU);
|
||||
|
||||
(v_zero | v_pattern).store(res);
|
||||
IAT_CHECK_EQ(res[0], 0xAAAAAAAAU);
|
||||
|
||||
(v_all_ones ^ v_pattern).store(res);
|
||||
IAT_CHECK_EQ(res[0], 0x55555555U);
|
||||
|
||||
(~v_pattern).store(res);
|
||||
IAT_CHECK_EQ(res[0], 0x55555555U);
|
||||
|
||||
const IntVec4 v_shift(1);
|
||||
(v_shift << 1).store(res);
|
||||
IAT_CHECK_EQ(res[0], 2U);
|
||||
|
||||
const IntVec4 v_shift_right(4);
|
||||
(v_shift_right >> 1).store(res);
|
||||
IAT_CHECK_EQ(res[0], 2U);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_saturation() -> bool {
|
||||
const u32 max = 0xFFFFFFFF;
|
||||
const IntVec4 v_high(max - 10);
|
||||
const IntVec4 v_add(20);
|
||||
|
||||
alignas(16) u32 res[4];
|
||||
|
||||
v_high.sat_add(v_add).store(res);
|
||||
IAT_CHECK_EQ(res[0], max);
|
||||
|
||||
const IntVec4 v_low(10);
|
||||
const IntVec4 v_sub(20);
|
||||
v_low.sat_sub(v_sub).store(res);
|
||||
IAT_CHECK_EQ(res[0], 0U);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_advanced_ops() -> bool {
|
||||
const IntVec4 v(0, 50, 100, 150);
|
||||
alignas(16) u32 res[4];
|
||||
|
||||
v.clamp(40, 110).store(res);
|
||||
IAT_CHECK_EQ(res[0], 40U);
|
||||
IAT_CHECK_EQ(res[1], 50U);
|
||||
IAT_CHECK_EQ(res[2], 100U);
|
||||
IAT_CHECK_EQ(res[3], 110U);
|
||||
|
||||
const IntVec4 a(2);
|
||||
const IntVec4 b(10);
|
||||
const IntVec4 c(5);
|
||||
a.mult_add(b, c).store(res);
|
||||
IAT_CHECK_EQ(res[0], 25U);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_constructors);
|
||||
IAT_ADD_TEST(test_arithmetic);
|
||||
IAT_ADD_TEST(test_bitwise);
|
||||
IAT_ADD_TEST(test_saturation);
|
||||
IAT_ADD_TEST(test_advanced_ops);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, IntVec4)
|
||||
107
Tests/Unit/SocketOps.cpp
Normal file
107
Tests/Unit/SocketOps.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <IACore/SocketOps.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, SocketOps)
|
||||
|
||||
auto test_initialization() -> bool {
|
||||
IAT_CHECK(SocketOps::is_initialized());
|
||||
|
||||
const auto res = SocketOps::initialize();
|
||||
IAT_CHECK(res.has_value());
|
||||
|
||||
SocketOps::terminate();
|
||||
|
||||
IAT_CHECK(SocketOps::is_initialized());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_port_availability() -> bool {
|
||||
|
||||
const u16 port = 54321;
|
||||
|
||||
(void)SocketOps::is_port_available_tcp(port);
|
||||
(void)SocketOps::is_port_available_udp(port);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_unix_socket_lifecycle() -> bool {
|
||||
const String socket_path = "iatest_ipc.sock";
|
||||
|
||||
SocketOps::unlink_file(socket_path.c_str());
|
||||
|
||||
auto server_res = SocketOps::create_unix_socket();
|
||||
IAT_CHECK(server_res.has_value());
|
||||
SocketHandle server = *server_res;
|
||||
|
||||
auto bind_res = SocketOps::bind_unix_socket(server, socket_path.c_str());
|
||||
if (!bind_res) {
|
||||
|
||||
SocketOps::close(server);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto listen_res = SocketOps::listen(server);
|
||||
IAT_CHECK(listen_res.has_value());
|
||||
|
||||
auto client_res = SocketOps::create_unix_socket();
|
||||
IAT_CHECK(client_res.has_value());
|
||||
SocketHandle client = *client_res;
|
||||
|
||||
auto connect_res =
|
||||
SocketOps::connect_unix_socket(client, socket_path.c_str());
|
||||
IAT_CHECK(connect_res.has_value());
|
||||
|
||||
SocketOps::close(client);
|
||||
SocketOps::close(server);
|
||||
SocketOps::unlink_file(socket_path.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_unix_socket_errors() -> bool {
|
||||
const String socket_path = "iatest_missing.sock";
|
||||
|
||||
SocketOps::unlink_file(socket_path.c_str());
|
||||
|
||||
auto client_res = SocketOps::create_unix_socket();
|
||||
IAT_CHECK(client_res.has_value());
|
||||
SocketHandle client = *client_res;
|
||||
|
||||
auto connect_res =
|
||||
SocketOps::connect_unix_socket(client, socket_path.c_str());
|
||||
IAT_CHECK_NOT(connect_res.has_value());
|
||||
|
||||
SocketOps::close(client);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_initialization);
|
||||
IAT_ADD_TEST(test_port_availability);
|
||||
IAT_ADD_TEST(test_unix_socket_lifecycle);
|
||||
IAT_ADD_TEST(test_unix_socket_errors);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, SocketOps)
|
||||
137
Tests/Unit/StreamReader.cpp
Normal file
137
Tests/Unit/StreamReader.cpp
Normal file
@ -0,0 +1,137 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <IACore/StreamReader.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, StreamReader)
|
||||
|
||||
auto test_read_uint8() -> bool {
|
||||
u8 data[] = {0xAA, 0xBB, 0xCC};
|
||||
StreamReader reader(data);
|
||||
|
||||
auto val1 = reader.read<u8>();
|
||||
IAT_CHECK(val1.has_value());
|
||||
IAT_CHECK_EQ(*val1, 0xAA);
|
||||
IAT_CHECK_EQ(reader.cursor(), static_cast<usize>(1));
|
||||
|
||||
auto val2 = reader.read<u8>();
|
||||
IAT_CHECK(val2.has_value());
|
||||
IAT_CHECK_EQ(*val2, 0xBB);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_read_multi_byte() -> bool {
|
||||
|
||||
u8 data[] = {0x01, 0x02, 0x03, 0x04};
|
||||
StreamReader reader(data);
|
||||
|
||||
auto val = reader.read<u32>();
|
||||
IAT_CHECK(val.has_value());
|
||||
|
||||
IAT_CHECK_EQ(*val, static_cast<u32>(0x04030201));
|
||||
|
||||
IAT_CHECK_EQ(reader.cursor(), static_cast<usize>(4));
|
||||
IAT_CHECK(reader.is_eof());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_read_float() -> bool {
|
||||
const f32 pi = 3.14159f;
|
||||
|
||||
u8 data[4];
|
||||
std::memcpy(data, &pi, 4);
|
||||
|
||||
StreamReader reader(data);
|
||||
auto val = reader.read<f32>();
|
||||
|
||||
IAT_CHECK(val.has_value());
|
||||
IAT_CHECK_APPROX(*val, pi);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_read_buffer() -> bool {
|
||||
u8 src[] = {1, 2, 3, 4, 5};
|
||||
u8 dst[3] = {0};
|
||||
StreamReader reader(src);
|
||||
|
||||
const auto res = reader.read(dst, 3);
|
||||
IAT_CHECK(res.has_value());
|
||||
|
||||
IAT_CHECK_EQ(dst[0], 1);
|
||||
IAT_CHECK_EQ(dst[1], 2);
|
||||
IAT_CHECK_EQ(dst[2], 3);
|
||||
|
||||
IAT_CHECK_EQ(reader.cursor(), static_cast<usize>(3));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_navigation() -> bool {
|
||||
u8 data[10] = {0};
|
||||
StreamReader reader(data);
|
||||
|
||||
IAT_CHECK_EQ(reader.remaining(), static_cast<usize>(10));
|
||||
|
||||
reader.skip(5);
|
||||
IAT_CHECK_EQ(reader.cursor(), static_cast<usize>(5));
|
||||
IAT_CHECK_EQ(reader.remaining(), static_cast<usize>(5));
|
||||
|
||||
reader.skip(100);
|
||||
IAT_CHECK_EQ(reader.cursor(), static_cast<usize>(10));
|
||||
IAT_CHECK(reader.is_eof());
|
||||
|
||||
reader.seek(2);
|
||||
IAT_CHECK_EQ(reader.cursor(), static_cast<usize>(2));
|
||||
IAT_CHECK_EQ(reader.remaining(), static_cast<usize>(8));
|
||||
IAT_CHECK_NOT(reader.is_eof());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_boundary_checks() -> bool {
|
||||
u8 data[] = {0x00, 0x00};
|
||||
StreamReader reader(data);
|
||||
|
||||
(void)reader.read<u16>();
|
||||
IAT_CHECK(reader.is_eof());
|
||||
|
||||
auto val = reader.read<u8>();
|
||||
IAT_CHECK_NOT(val.has_value());
|
||||
|
||||
u8 buf[1];
|
||||
auto batch = reader.read(buf, 1);
|
||||
IAT_CHECK_NOT(batch.has_value());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_read_uint8);
|
||||
IAT_ADD_TEST(test_read_multi_byte);
|
||||
IAT_ADD_TEST(test_read_float);
|
||||
IAT_ADD_TEST(test_read_buffer);
|
||||
IAT_ADD_TEST(test_navigation);
|
||||
IAT_ADD_TEST(test_boundary_checks);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, StreamReader)
|
||||
116
Tests/Unit/StreamWriter.cpp
Normal file
116
Tests/Unit/StreamWriter.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/FileOps.hpp>
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <IACore/StreamWriter.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, StreamWriter)
|
||||
|
||||
auto test_memory_writer() -> bool {
|
||||
StreamWriter writer;
|
||||
|
||||
IAT_CHECK(writer.write(static_cast<u8>(0xAA), 1).has_value());
|
||||
|
||||
const u32 val = 0x12345678;
|
||||
IAT_CHECK(writer.write(val).has_value());
|
||||
|
||||
IAT_CHECK_EQ(writer.cursor(), static_cast<usize>(1 + 4));
|
||||
|
||||
const u8 *ptr = writer.data();
|
||||
IAT_CHECK_EQ(ptr[0], 0xAA);
|
||||
IAT_CHECK_EQ(ptr[1], 0x78);
|
||||
IAT_CHECK_EQ(ptr[4], 0x12);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_fixed_buffer() -> bool {
|
||||
u8 buffer[4] = {0};
|
||||
StreamWriter writer(Span<u8>(buffer, 4));
|
||||
|
||||
IAT_CHECK(writer.write(static_cast<u8>(0xFF), 2).has_value());
|
||||
IAT_CHECK_EQ(writer.cursor(), static_cast<usize>(2));
|
||||
|
||||
IAT_CHECK(writer.write(static_cast<u8>(0xEE), 2).has_value());
|
||||
IAT_CHECK_EQ(writer.cursor(), static_cast<usize>(4));
|
||||
|
||||
const auto res = writer.write(static_cast<u8>(0x00), 1);
|
||||
IAT_CHECK_NOT(res.has_value());
|
||||
|
||||
IAT_CHECK_EQ(buffer[0], 0xFF);
|
||||
IAT_CHECK_EQ(buffer[1], 0xFF);
|
||||
IAT_CHECK_EQ(buffer[2], 0xEE);
|
||||
IAT_CHECK_EQ(buffer[3], 0xEE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_file_writer() -> bool {
|
||||
const Path path = "test_stream_writer.bin";
|
||||
|
||||
if (std::filesystem::exists(path)) {
|
||||
std::filesystem::remove(path);
|
||||
}
|
||||
|
||||
{
|
||||
auto res = StreamWriter::create_from_file(path);
|
||||
IAT_CHECK(res.has_value());
|
||||
StreamWriter writer = std::move(*res);
|
||||
|
||||
const String hello = "Hello World";
|
||||
IAT_CHECK(writer.write(hello.data(), hello.size()).has_value());
|
||||
|
||||
IAT_CHECK(writer.flush().has_value());
|
||||
}
|
||||
|
||||
auto read_res = FileOps::read_binary_file(path);
|
||||
IAT_CHECK(read_res.has_value());
|
||||
|
||||
const String read_str(reinterpret_cast<const char *>(read_res->data()),
|
||||
read_res->size());
|
||||
IAT_CHECK_EQ(read_str, String("Hello World"));
|
||||
|
||||
std::filesystem::remove(path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_primitives() -> bool {
|
||||
StreamWriter writer;
|
||||
|
||||
const f32 f = 1.5f;
|
||||
const u64 big = 0xDEADBEEFCAFEBABE;
|
||||
|
||||
IAT_CHECK(writer.write(f).has_value());
|
||||
IAT_CHECK(writer.write(big).has_value());
|
||||
|
||||
IAT_CHECK_EQ(writer.cursor(), sizeof(f32) + sizeof(u64));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_memory_writer);
|
||||
IAT_ADD_TEST(test_fixed_buffer);
|
||||
IAT_ADD_TEST(test_file_writer);
|
||||
IAT_ADD_TEST(test_primitives);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, StreamWriter)
|
||||
111
Tests/Unit/StringOps.cpp
Normal file
111
Tests/Unit/StringOps.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <IACore/StringOps.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, StringOps)
|
||||
|
||||
auto test_base64_encode() -> bool {
|
||||
|
||||
{
|
||||
const String s = "Hello World";
|
||||
const Span<const u8> data(reinterpret_cast<const u8 *>(s.data()), s.size());
|
||||
const String encoded = StringOps::encode_base64(data);
|
||||
IAT_CHECK_EQ(encoded, String("SGVsbG8gV29ybGQ="));
|
||||
}
|
||||
|
||||
{
|
||||
const String s = "M";
|
||||
const Span<const u8> data(reinterpret_cast<const u8 *>(s.data()), s.size());
|
||||
const String encoded = StringOps::encode_base64(data);
|
||||
IAT_CHECK_EQ(encoded, String("TQ=="));
|
||||
}
|
||||
|
||||
{
|
||||
const String s = "Ma";
|
||||
const Span<const u8> data(reinterpret_cast<const u8 *>(s.data()), s.size());
|
||||
const String encoded = StringOps::encode_base64(data);
|
||||
IAT_CHECK_EQ(encoded, String("TWE="));
|
||||
}
|
||||
|
||||
{
|
||||
const String s = "Man";
|
||||
const Span<const u8> data(reinterpret_cast<const u8 *>(s.data()), s.size());
|
||||
const String encoded = StringOps::encode_base64(data);
|
||||
IAT_CHECK_EQ(encoded, String("TWFu"));
|
||||
}
|
||||
|
||||
{
|
||||
const String encoded = StringOps::encode_base64({});
|
||||
IAT_CHECK(encoded.empty());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_base64_decode() -> bool {
|
||||
|
||||
{
|
||||
const String encoded = "SGVsbG8gV29ybGQ=";
|
||||
const Vec<u8> decoded = StringOps::decode_base64(encoded);
|
||||
const String result(reinterpret_cast<const char *>(decoded.data()),
|
||||
decoded.size());
|
||||
IAT_CHECK_EQ(result, String("Hello World"));
|
||||
}
|
||||
|
||||
{
|
||||
const Vec<u8> decoded = StringOps::decode_base64("");
|
||||
IAT_CHECK(decoded.empty());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_base64_round_trip() -> bool {
|
||||
Vec<u8> original;
|
||||
original.reserve(256);
|
||||
for (usize i = 0; i < 256; ++i) {
|
||||
original.push_back(static_cast<u8>(i));
|
||||
}
|
||||
|
||||
const String encoded = StringOps::encode_base64(original);
|
||||
const Vec<u8> decoded = StringOps::decode_base64(encoded);
|
||||
|
||||
IAT_CHECK_EQ(original.size(), decoded.size());
|
||||
|
||||
bool match = true;
|
||||
for (usize i = 0; i < original.size(); ++i) {
|
||||
if (original[i] != decoded[i]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
IAT_CHECK(match);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_base64_encode);
|
||||
IAT_ADD_TEST(test_base64_decode);
|
||||
IAT_ADD_TEST(test_base64_round_trip);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, StringOps)
|
||||
162
Tests/Unit/Utils.cpp
Normal file
162
Tests/Unit/Utils.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <IACore/Utils.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
struct TestVec3 {
|
||||
f32 x, y, z;
|
||||
|
||||
bool operator==(const TestVec3 &other) const {
|
||||
return x == other.x && y == other.y && z == other.z;
|
||||
}
|
||||
};
|
||||
|
||||
IA_MAKE_HASHABLE(TestVec3, &TestVec3::x, &TestVec3::y, &TestVec3::z);
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, Utils)
|
||||
|
||||
auto test_hex_conversion() -> bool {
|
||||
|
||||
u8 bin[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF};
|
||||
String hex = Utils::binary_to_hex_string(bin);
|
||||
|
||||
IAT_CHECK_EQ(hex, String("DEADBEEF00FF"));
|
||||
|
||||
auto res_upper = Utils::hex_string_to_binary("DEADBEEF00FF");
|
||||
IAT_CHECK(res_upper.has_value());
|
||||
IAT_CHECK_EQ(res_upper->size(), static_cast<usize>(6));
|
||||
IAT_CHECK_EQ((*res_upper)[0], 0xDE);
|
||||
IAT_CHECK_EQ((*res_upper)[5], 0xFF);
|
||||
|
||||
auto res_lower = Utils::hex_string_to_binary("deadbeef00ff");
|
||||
IAT_CHECK(res_lower.has_value());
|
||||
IAT_CHECK_EQ((*res_lower)[0], 0xDE);
|
||||
|
||||
Vec<u8> original = {1, 2, 3, 4, 5};
|
||||
String s = Utils::binary_to_hex_string(original);
|
||||
auto back = Utils::hex_string_to_binary(s);
|
||||
IAT_CHECK(back.has_value());
|
||||
IAT_CHECK_EQ(original.size(), back->size());
|
||||
IAT_CHECK_EQ(original[2], (*back)[2]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_hex_errors() -> bool {
|
||||
|
||||
auto odd = Utils::hex_string_to_binary("ABC");
|
||||
IAT_CHECK_NOT(odd.has_value());
|
||||
|
||||
auto invalid = Utils::hex_string_to_binary("ZZTOP");
|
||||
IAT_CHECK_NOT(invalid.has_value());
|
||||
|
||||
auto empty = Utils::hex_string_to_binary("");
|
||||
IAT_CHECK(empty.has_value());
|
||||
IAT_CHECK_EQ(empty->size(), static_cast<usize>(0));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_sort() -> bool {
|
||||
Vec<i32> nums = {5, 1, 4, 2, 3};
|
||||
|
||||
Utils::sort(nums);
|
||||
|
||||
IAT_CHECK_EQ(nums[0], 1);
|
||||
IAT_CHECK_EQ(nums[1], 2);
|
||||
IAT_CHECK_EQ(nums[2], 3);
|
||||
IAT_CHECK_EQ(nums[3], 4);
|
||||
IAT_CHECK_EQ(nums[4], 5);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_binary_search() -> bool {
|
||||
|
||||
Vec<i32> nums = {10, 20, 20, 20, 30};
|
||||
|
||||
auto it_left = Utils::binary_search_left(nums, 20);
|
||||
IAT_CHECK(it_left != nums.end());
|
||||
IAT_CHECK_EQ(*it_left, 20);
|
||||
IAT_CHECK_EQ(std::distance(nums.begin(), it_left), 1);
|
||||
|
||||
auto it_right = Utils::binary_search_right(nums, 20);
|
||||
IAT_CHECK(it_right != nums.end());
|
||||
IAT_CHECK_EQ(*it_right, 30);
|
||||
IAT_CHECK_EQ(std::distance(nums.begin(), it_right), 4);
|
||||
|
||||
auto it_fail = Utils::binary_search_left(nums, 99);
|
||||
IAT_CHECK(it_fail == nums.end());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_hash_basics() -> bool {
|
||||
u64 h1 = Utils::compute_hash(10, 20.5f, "Hello");
|
||||
u64 h2 = Utils::compute_hash(10, 20.5f, "Hello");
|
||||
u64 h3 = Utils::compute_hash(10, 20.5f, "World");
|
||||
|
||||
IAT_CHECK_EQ(h1, h2);
|
||||
|
||||
IAT_CHECK_NEQ(h1, h3);
|
||||
|
||||
u64 order_a = Utils::compute_hash(1, 2);
|
||||
u64 order_b = Utils::compute_hash(2, 1);
|
||||
IAT_CHECK_NEQ(order_a, order_b);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_hash_macro() -> bool {
|
||||
TestVec3 v1{1.0f, 2.0f, 3.0f};
|
||||
TestVec3 v2{1.0f, 2.0f, 3.0f};
|
||||
TestVec3 v3{1.0f, 2.0f, 4.0f};
|
||||
|
||||
ankerl::unordered_dense::hash<TestVec3> hasher;
|
||||
|
||||
u64 h1 = hasher(v1);
|
||||
u64 h2 = hasher(v2);
|
||||
u64 h3 = hasher(v3);
|
||||
|
||||
IAT_CHECK_EQ(h1, h2);
|
||||
IAT_CHECK_NEQ(h1, h3);
|
||||
|
||||
u64 h_manual = 0;
|
||||
Utils::hash_combine(h_manual, v1);
|
||||
|
||||
u64 h_wrapper = Utils::compute_hash(v1);
|
||||
|
||||
IAT_CHECK_EQ(h_manual, h_wrapper);
|
||||
|
||||
IAT_CHECK_NEQ(h1, h_wrapper);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_hex_conversion);
|
||||
IAT_ADD_TEST(test_hex_errors);
|
||||
IAT_ADD_TEST(test_sort);
|
||||
IAT_ADD_TEST(test_binary_search);
|
||||
IAT_ADD_TEST(test_hash_basics);
|
||||
IAT_ADD_TEST(test_hash_macro);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, Utils)
|
||||
112
Tests/Unit/XML.cpp
Normal file
112
Tests/Unit/XML.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
// IACore-OSS; The Core Library for All IA Open Source Projects
|
||||
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <IACore/FileOps.hpp>
|
||||
#include <IACore/IATest.hpp>
|
||||
#include <IACore/XML.hpp>
|
||||
|
||||
using namespace IACore;
|
||||
|
||||
IAT_BEGIN_BLOCK(Core, XML)
|
||||
|
||||
auto test_parse_string() -> bool {
|
||||
const String xml_content = R"(
|
||||
<root>
|
||||
<item id="1">Value1</item>
|
||||
<item id="2">Value2</item>
|
||||
</root>
|
||||
)";
|
||||
|
||||
auto res = XML::parse_from_string(xml_content);
|
||||
IAT_CHECK(res.has_value());
|
||||
|
||||
auto &doc = *res;
|
||||
auto root = doc.child("root");
|
||||
IAT_CHECK(root);
|
||||
|
||||
auto item1 = root.find_child_by_attribute("item", "id", "1");
|
||||
IAT_CHECK(item1);
|
||||
IAT_CHECK_EQ(String(item1.child_value()), String("Value1"));
|
||||
|
||||
auto item2 = root.find_child_by_attribute("item", "id", "2");
|
||||
IAT_CHECK(item2);
|
||||
IAT_CHECK_EQ(String(item2.child_value()), String("Value2"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_parse_error() -> bool {
|
||||
const String invalid_xml = "<root><unclosed>";
|
||||
auto res = XML::parse_from_string(invalid_xml);
|
||||
IAT_CHECK_NOT(res.has_value());
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_serialize() -> bool {
|
||||
const String xml_content = "<root><node>Text</node></root>";
|
||||
auto res = XML::parse_from_string(xml_content);
|
||||
IAT_CHECK(res.has_value());
|
||||
|
||||
String output = XML::serialize_to_string(*res);
|
||||
|
||||
IAT_CHECK(output.find("<root>") != String::npos);
|
||||
IAT_CHECK(output.find("<node>Text</node>") != String::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_escape() -> bool {
|
||||
const String raw = "< & > \" '";
|
||||
const String escaped = XML::escape_xml_string(raw);
|
||||
|
||||
IAT_CHECK(escaped.find("<") != String::npos);
|
||||
IAT_CHECK(escaped.find("&") != String::npos);
|
||||
IAT_CHECK(escaped.find(">") != String::npos);
|
||||
IAT_CHECK(escaped.find(""") != String::npos);
|
||||
IAT_CHECK(escaped.find("'") != String::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto test_file_io() -> bool {
|
||||
const Path path = "test_temp_xml_doc.xml";
|
||||
const String content = "<config><ver>1.0</ver></config>";
|
||||
|
||||
auto write_res = FileOps::write_text_file(path, content, true);
|
||||
IAT_CHECK(write_res.has_value());
|
||||
|
||||
auto parse_res = XML::parse_from_file(path);
|
||||
IAT_CHECK(parse_res.has_value());
|
||||
|
||||
auto &doc = *parse_res;
|
||||
IAT_CHECK_EQ(String(doc.child("config").child("ver").child_value()),
|
||||
String("1.0"));
|
||||
|
||||
std::filesystem::remove(path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IAT_BEGIN_TEST_LIST()
|
||||
IAT_ADD_TEST(test_parse_string);
|
||||
IAT_ADD_TEST(test_parse_error);
|
||||
IAT_ADD_TEST(test_serialize);
|
||||
IAT_ADD_TEST(test_escape);
|
||||
IAT_ADD_TEST(test_file_io);
|
||||
IAT_END_TEST_LIST()
|
||||
|
||||
IAT_END_BLOCK()
|
||||
|
||||
IAT_REGISTER_ENTRY(Core, XML)
|
||||
23
logo.svg
Normal file
23
logo.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<svg width="400" height="120" viewBox="0 0 400 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.text { font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-weight: bold; }
|
||||
.secondary { font-weight: 300; letter-spacing: 2px; }
|
||||
</style>
|
||||
|
||||
<g transform="translate(20, 10)">
|
||||
<path d="M50 5 L93.3 30 V80 L50 105 L6.7 80 V30 L50 5 Z" stroke="#3b82f6" stroke-width="4"
|
||||
fill="none" />
|
||||
|
||||
<rect x="42" y="25" width="16" height="60" rx="2" fill="#3b82f6" />
|
||||
|
||||
<circle cx="20" cy="55" r="4" fill="#60a5fa" />
|
||||
<circle cx="80" cy="55" r="4" fill="#60a5fa" />
|
||||
<path d="M24 55 H42" stroke="#60a5fa" stroke-width="2" />
|
||||
<path d="M58 55 H76" stroke="#60a5fa" stroke-width="2" />
|
||||
</g>
|
||||
|
||||
<text x="120" y="65" fill="#e2e8f0" font-size="52" class="text">IACore</text>
|
||||
|
||||
<text x="122" y="95" fill="#94a3b8" font-size="14" class="secondary">INDEPENDENT ARCHITECTURE</text>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 979 B |
Reference in New Issue
Block a user