diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml new file mode 100644 index 000000000..b52460dae --- /dev/null +++ b/.github/actions/setup-rust/action.yml @@ -0,0 +1,71 @@ +name: Setup Rust +description: 'Ensure Rust compiler and utilities are available, and setup caching' + +inputs: + kind: + type: choice + description: "Whether to install just a default target, or all targets (e.g. for during deployments / wheel building)" + default: "runner" + options: + - runner + - full + - wasm + +runs: + using: 'composite' + steps: + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + components: clippy, rustfmt + + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + with: + key: ${{ runner.os }} + + - name: Setup cargo cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + **/target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - run: rustup target add x86_64-unknown-linux-gnu + shell: bash + if: ${{ inputs.kind == 'runner' && runner.os == 'Linux' }} + + - run: | + rustup target add aarch64-unknown-linux-gnu + rustup target add x86_64-unknown-linux-gnu + shell: bash + if: ${{ inputs.kind == 'full' && runner.os == 'Linux' }} + + - run: rustup target add x86_64-apple-darwin + shell: bash + if: ${{ inputs.kind == 'runner' && runner.os == 'macOS' }} + + - run: | + rustup target add aarch64-apple-darwin + rustup target add x86_64-apple-darwin + shell: bash + if: ${{ inputs.kind == 'full' && runner.os == 'macOS' }} + + - run: rustup target add x86_64-pc-windows-msvc + shell: bash + if: ${{ inputs.kind == 'runner' && runner.os == 'Windows' }} + + - run: | + rustup target add x86_64-pc-windows-msvc + rustup target add aarch64-pc-windows-msvc + shell: bash + if: ${{ inputs.kind == 'full' && runner.os == 'Windows' }} + + - run: rustup target add wasm32-unknown-unknown + shell: bash + if: ${{ inputs.kind == 'wasm' }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d42f0b14e..8f4e8072b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -211,29 +211,40 @@ jobs: # Things to exclude if not a full matrix run # ############################################## - is-full-run: false - os: windows-2022 + os: ubuntu-24.04 cibuildwheel: "cp310" + - is-full-run: false + os: ubuntu-24.04 + cibuildwheel: "cp312" + + - is-full-run: false + os: ubuntu-24.04 + cibuildwheel: "cp313" + - is-full-run: false os: windows-2022 - cibuildwheel: "cp311" + cibuildwheel: "cp310" - is-full-run: false os: windows-2022 cibuildwheel: "cp312" - # avoid unnecessary use of mac resources + - is-full-run: false + os: windows-2022 + cibuildwheel: "cp313" + - is-full-run: false os: macos-14 cibuildwheel: "cp310" - is-full-run: false os: macos-14 - cibuildwheel: "cp311" + cibuildwheel: "cp312" - is-full-run: false os: macos-14 - cibuildwheel: "cp312" + cibuildwheel: "cp313" runs-on: ${{ matrix.os }} @@ -417,32 +428,41 @@ jobs: ############################################## # Things to exclude if not a full matrix run # ############################################## - - # Avoid extra resources for windows build - is-full-run: false - os: windows-2022 + os: ubuntu-24.04 python-version: "3.10" + - is-full-run: false + os: ubuntu-24.04 + python-version: "3.12" + + - is-full-run: false + os: ubuntu-24.04 + python-version: "3.13" + - is-full-run: false os: windows-2022 - python-version: "3.11" + python-version: "3.10" - is-full-run: false os: windows-2022 python-version: "3.12" - # avoid unnecessary use of mac resources + - is-full-run: false + os: windows-2022 + python-version: "3.13" + - is-full-run: false os: macos-14 python-version: "3.10" - is-full-run: false os: macos-14 - python-version: "3.11" + python-version: "3.12" - is-full-run: false os: macos-14 - python-version: "3.12" + python-version: "3.13" runs-on: ${{ matrix.os }} @@ -515,6 +535,106 @@ jobs: - name: Python Test Steps run: make test + #################################################################################################################### + #..................................................................................................................# + #..|########|..|########|..../####\....|########|.............../####\...|########\..|########\....................# + #..|########|..|##|......../##/..\##\..|########|............./##/.\##\..|##|../##/..|##|../##/....................# + #.....|##|.....|##|.........\##\..........|##|...............|##|........|##|./##/...|##|./##/.....................# + #.....|##|.....|########|.....\##\........|##|...............|##|........|##||##/....|##||##/......................# + #.....|##|.....|##|.............\##\......|##|...............|##|........|##|........|##|..........................# + #.....|##|.....|##|........\##\../##/.....|##|................\##\./##/..|##|........|##|..........................# + #.....|##|.....|########|...\####/........|##|.................\####/....|##|........|##|..........................# + #..................................................................................................................# + #...|########|...\##\..../##/...../#####\......./|\........./|\....|########\..|##|........|########|..../####\....# + #...|##|..........\##\../##/...../##/.\##\...../#|#\......./#|#\...|##|../##/..|##|........|##|......../##/..\##\..# + #...|##|...........\##\/##/...../##/...\##\...|##|\#\...../#/|##|..|##|./##/...|##|........|##|.........\##\.......# + #...|########|......|####|...../###########\..|##|.\#\.../#/.|##|..|##||##/....|##|........|########|.....\##\.....# + #...|##|.........../##/\##\....|##|.....|##|..|##|..\#\./#/..|##|..|##|........|##|........|##|.............\##\...# + #...|##|........../##/..\##\...|##|.....|##|..|##|...\#|#/...|##|..|##|........|########|..|##|........\##\./##/...# + #...|########|.../##/....\##\..|##|.....|##|..|##|....\|/....|##|..|##|........|########|..|########|...\####/.....# + #.........................................................................................................# + ############################################################## + # Build / test C++ examples (05_cpp) # + ############################################################## + test_cpp_examples: + needs: + - initialize + - build + + strategy: + matrix: + os: + - ubuntu-24.04 + - macos-14 + example: + - 1_cpp_node + - 2_cpp_node_with_struct + - 3_cpp_adapter + - 4_c_api_adapter + - 5_c_api_adapter_rust + python-version: + - 3.11 + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Set up Python ${{ matrix.python-version }} + uses: ./.github/actions/setup-python + with: + version: '${{ matrix.python-version }}' + cibuildwheel: false + + - name: Set up Caches + uses: ./.github/actions/setup-caches + + - name: Install python dependencies + run: make requirements + + - name: Download wheel + uses: actions/download-artifact@v7 + with: + name: csp-dist-${{ runner.os }}-${{ runner.arch }}-${{ matrix.python-version }} + + ######## + # Linux + - name: Install wheel (Linux) + run: | + python -m pip install -U *manylinux*.whl + if: ${{ runner.os == 'Linux' }} + + - name: Install build tools (Linux) + run: sudo apt-get install -y cmake build-essential + if: ${{ runner.os == 'Linux' }} + + ######## + # MacOS + - name: Install wheel (OSX arm) + run: | + python -m pip install -U *arm64*.whl + if: ${{ runner.os == 'macOS' && runner.arch == 'ARM64' }} + + - name: Install wheel (OSX x86) + run: | + python -m pip install -U *x86*.whl + if: ${{ runner.os == 'macOS' && runner.arch == 'X64' }} + + ########## + # Steps for Rust depend C API Rust Example (5_c_api_adapter_rust) + - name: Setup Rust + uses: ./.github/actions/setup-rust + if: ${{ matrix.example == '5_c_api_adapter_rust' }} + + - name: Build example ${{ matrix.example }} and run tests + run: | + cd examples/05_cpp/${{ matrix.example }} + hatch-build --hooks-only -t wheel + python -m pytest -vvv . + ################################################################ #..............................................................# #..|########|..|########|..../####\....|########|..............# @@ -624,15 +744,11 @@ jobs: os: - ubuntu-24.04 python-version: - - "3.10" + - "3.11" package: - sqlalchemy<2 - perspective-python<3 - # - pandas<3 - # uncomment the pandas pin once we move off Python 3.10 to ensure we maintain pandas 2.x compatibility - # pandas 3.0 does not support 3.10 so we currently get free coverage, but won't after 3.10 goes EOL - # Min supported version of pandas 2.2 - - numpy==1.22.4 + - pandas<3 runs-on: ${{ matrix.os }} @@ -731,6 +847,7 @@ jobs: - test - test_sdist - test_dependencies + - test_cpp_examples if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-24.04 diff --git a/.gitignore b/.gitignore index 7c508dbdd..8cca2f68e 100644 --- a/.gitignore +++ b/.gitignore @@ -99,6 +99,7 @@ target/ lib/ lib64/ csp/bin/ +csp/cmake/ csp/include/ csp/lib/ *.so diff --git a/CMakeLists.txt b/CMakeLists.txt index 68b35e6de..f7399a1cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,11 @@ if(NOT DEFINED CSP_CMAKE_MODULE_PATH) endif() list(PREPEND CMAKE_MODULE_PATH "${CSP_CMAKE_MODULE_PATH}") +# Add meta-target for cmake modules and install them with the package +add_library(csp_cmake_modules INTERFACE) +target_include_directories(csp_cmake_modules INTERFACE "${CMAKE_SOURCE_DIR}/cpp/cmake/modules") +install(DIRECTORY "${CMAKE_SOURCE_DIR}/cpp/cmake/modules" DESTINATION "cmake/modules") + ################################################################################################################################################### # Build Configuration # ####################### diff --git a/Makefile b/Makefile index 16dfdd923..98de770b2 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ lint-cpp: lint-docs: python -m mdformat --check docs/wiki/ README.md examples/ - python -m codespell_lib docs/wiki/ README.md examples/ --skip "*.cpp,*.h" + python -m codespell_lib docs/wiki/ README.md examples/ --skip "*.cpp,*.h,*.d,*.make,*.internal,CMakeConfigureLog.yaml" # lint: lint-py lint-cpp ## run lints lint: lint-py lint-docs ## run lints @@ -62,7 +62,7 @@ fix-cpp: fix-docs: python -m mdformat docs/wiki/ README.md examples/ - python -m codespell_lib --write docs/wiki/ README.md examples/ --skip "*.cpp,*.h" + python -m codespell_lib --write docs/wiki/ README.md examples/ --skip "*.cpp,*.h,*.d,*.make,*.internal,CMakeConfigureLog.yaml" fix: fix-py fix-cpp fix-docs ## run autofixers diff --git a/README.md b/README.md index 8a2c96edb..e0e03fd2d 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Here is a very simple example of a small `csp` program to calculate a [bid-ask s ```python import csp from csp import ts -from datetime import datetime +from csp.utils.datetime import utc_now @csp.node @@ -44,7 +44,7 @@ def my_graph(): if __name__ == '__main__': - csp.run(my_graph, starttime=datetime.utcnow()) + csp.run(my_graph, starttime=utc_now()) ``` Running this, our output should look like (with some slight variations for current time): diff --git a/cpp/csp/core/Platform.h b/cpp/csp/core/Platform.h index 37474faf6..fb3ecad73 100644 --- a/cpp/csp/core/Platform.h +++ b/cpp/csp/core/Platform.h @@ -17,21 +17,30 @@ #define DLL_LOCAL #ifdef CSPTYPESIMPL_EXPORTS -#define CSPTYPESIMPL_EXPORT __declspec(dllexport) +#define CSPTYPESIMPL_EXPORT __declspec( dllexport ) #else -#define CSPTYPESIMPL_EXPORT __declspec(dllimport) +#define CSPTYPESIMPL_EXPORT __declspec( dllimport ) #endif #ifdef CSPIMPL_EXPORTS -#define CSPIMPL_EXPORT __declspec(dllexport) +#define CSPIMPL_EXPORT __declspec( dllexport ) #else -#define CSPIMPL_EXPORT __declspec(dllimport) +#define CSPIMPL_EXPORT __declspec( dllimport ) #endif -#define START_PACKED __pragma( pack(push, 1) ) -#define END_PACKED __pragma( pack(pop)) +// C API export macro - used for ABI-stable C functions +// On Windows: export from cspimpl.dll +// On Unix: ensure symbols have default visibility +#ifdef CSPIMPL_EXPORTS +#define CSP_C_API_EXPORT __declspec( dllexport ) +#else +#define CSP_C_API_EXPORT __declspec( dllimport ) +#endif -#define NO_INLINE __declspec(noinline) +#define START_PACKED __pragma( pack( push, 1 ) ) +#define END_PACKED __pragma( pack( pop ) ) + +#define NO_INLINE __declspec( noinline ) inline tm * localtime_r( const time_t * timep, tm * result ) { @@ -73,7 +82,7 @@ inline uint8_t clz(uint8_t n) { return clz(static_cast(n)) - 24; } template::value, bool> = true> inline uint8_t ffs(U n) -{ +{ unsigned long index = 0; if (_BitScanForward(&index, n)) return index + 1; @@ -93,12 +102,15 @@ inline uint8_t ffs(uint64_t n) #define CSPIMPL_EXPORT #define CSPTYPESIMPL_EXPORT -#define DLL_LOCAL __attribute__ ((visibility ("hidden"))) +// C API export macro - ensure symbols have default visibility for external use +#define CSP_C_API_EXPORT __attribute__( ( visibility( "default" ) ) ) + +#define DLL_LOCAL __attribute__ ( ( visibility( "hidden" ) ) ) #define START_PACKED -#define END_PACKED __attribute__((packed)) +#define END_PACKED __attribute__( ( packed ) ) -#define NO_INLINE __attribute__ ((noinline)) +#define NO_INLINE __attribute__ ( ( noinline ) ) inline constexpr uint8_t clz(uint32_t n) { return __builtin_clz(n); } inline constexpr uint8_t clz(uint64_t n) { return __builtin_clzl(n); } diff --git a/cpp/csp/core/Time.h b/cpp/csp/core/Time.h index e5f8dfaeb..d0ddb7750 100644 --- a/cpp/csp/core/Time.h +++ b/cpp/csp/core/Time.h @@ -110,10 +110,10 @@ inline std::string TimeDelta::asString() const int32_t s = seconds(); int32_t n = nanoseconds(); - int idx = d ? sprintf( buf, "%d %s ", d, d == 1 ? "day" : "days" ) : 0; - idx += sprintf( buf + idx, "%02d:%02d:%02d", h, m, s ); + int idx = d ? snprintf( buf, sizeof(buf), "%d %s ", d, d == 1 ? "day" : "days" ) : 0; + idx += snprintf( buf + idx, sizeof(buf) - idx, "%02d:%02d:%02d", h, m, s ); if( n ) - sprintf( buf + idx, ".%09d", n ); + snprintf( buf + idx, sizeof(buf) - idx, ".%09d", n ); return buf; } @@ -306,7 +306,7 @@ inline Date Date::today() inline std::string Date::asYYYYMMDD() const { char buf[32]; - sprintf( buf, "%04d%02d%02d", year(), month(), day() ); + snprintf( buf, sizeof(buf), "%04d%02d%02d", year(), month(), day() ); return buf; } @@ -416,7 +416,7 @@ inline Time& Time::operator -=( const TimeDelta & delta ) inline std::string Time::asString() const { char buf[64]; - sprintf( buf, "%02d:%02d:%02d.%09d", hour(), minute(), second(), nanosecond() ); + snprintf( buf, sizeof(buf), "%02d:%02d:%02d.%09d", hour(), minute(), second(), nanosecond() ); return buf; } diff --git a/cpp/csp/engine/AdapterManagerExtern.cpp b/cpp/csp/engine/AdapterManagerExtern.cpp new file mode 100644 index 000000000..2f0d068ab --- /dev/null +++ b/cpp/csp/engine/AdapterManagerExtern.cpp @@ -0,0 +1,270 @@ +/* + * Implementation of the C++ AdapterManagerExtern wrapper and C API functions. + */ +#include +#include +#include +#include +#include +#include + +namespace csp +{ + +// ============================================================================ +// AdapterManagerExtern Implementation +// ============================================================================ + +AdapterManagerExtern::AdapterManagerExtern( Engine * engine, const CCspAdapterManagerVTable & vtable ) + : AdapterManager( engine ) + , m_vtable( vtable ) +{ + if( !vtable.name ) + { + CSP_THROW( ValueError, "AdapterManagerExtern: name callback is required" ); + } + if( !vtable.process_next_sim_time_slice ) + { + CSP_THROW( ValueError, "AdapterManagerExtern: process_next_sim_time_slice callback is required" ); + } + if( !vtable.destroy ) + { + CSP_THROW( ValueError, "AdapterManagerExtern: destroy callback is required" ); + } +} + +AdapterManagerExtern::~AdapterManagerExtern() +{ + if( m_vtable.destroy ) + { + m_vtable.destroy( m_vtable.user_data ); + } +} + +const char * AdapterManagerExtern::name() const +{ + if( m_name.empty() && m_vtable.name ) + { + const char * n = m_vtable.name( m_vtable.user_data ); + if( n ) + { + m_name = n; + } + } + return m_name.c_str(); +} + +void AdapterManagerExtern::start( DateTime startTime, DateTime endTime ) +{ + AdapterManager::start( startTime, endTime ); + + if( m_vtable.start ) + { + CCspAdapterManagerHandle handle = reinterpret_cast( this ); + m_vtable.start( m_vtable.user_data, handle, + startTime.asNanoseconds(), endTime.asNanoseconds() ); + } +} + +void AdapterManagerExtern::stop() +{ + if( m_vtable.stop ) + { + m_vtable.stop( m_vtable.user_data ); + } + AdapterManager::stop(); +} + +DateTime AdapterManagerExtern::processNextSimTimeSlice( DateTime time ) +{ + CCspDateTime result = m_vtable.process_next_sim_time_slice( m_vtable.user_data, time.asNanoseconds() ); + if( result == 0 ) + { + return DateTime::NONE(); + } + return DateTime::fromNanoseconds( result ); +} + +} // namespace csp + +// ============================================================================ +// C API Implementation +// ============================================================================ + +extern "C" { + +CCspAdapterManagerHandle ccsp_adapter_manager_extern_create( CCspEngineHandle engine, const CCspAdapterManagerVTable * vtable ) +{ + if( !engine || !vtable ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null engine or vtable" ); + return nullptr; + } + + try + { + auto * eng = reinterpret_cast( engine ); + auto * manager = eng -> createOwnedObject( *vtable ); + return reinterpret_cast( manager ); + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return nullptr; + } +} + +void ccsp_adapter_manager_extern_destroy( CCspAdapterManagerHandle manager ) +{ + // Engine-owned objects are cleaned up by the engine when the graph stops + // The vtable's destroy callback will be called from the destructor + (void)manager; +} + +CCspEngineHandle ccsp_adapter_manager_engine( CCspAdapterManagerHandle manager ) +{ + if( !manager ) return nullptr; + auto * m = reinterpret_cast( manager ); + return reinterpret_cast( m -> engine() ); +} + +CCspDateTime ccsp_adapter_manager_start_time( CCspAdapterManagerHandle manager ) +{ + if( !manager ) return 0; + auto * m = reinterpret_cast( manager ); + return m -> starttime().asNanoseconds(); +} + +CCspDateTime ccsp_adapter_manager_end_time( CCspAdapterManagerHandle manager ) +{ + if( !manager ) return 0; + auto * m = reinterpret_cast( manager ); + return m -> endtime().asNanoseconds(); +} + +CCspOutputAdapterHandle ccsp_adapter_manager_create_output_adapter( CCspAdapterManagerHandle manager, CCspType input_type, const CCspOutputAdapterVTable * vtable ) +{ + if( !manager || !vtable ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null manager or vtable" ); + return nullptr; + } + + try + { + auto * m = reinterpret_cast( manager ); + + // Map CCspType to CspTypePtr + csp::CspTypePtr cspType; + switch( input_type ) + { + case CCSP_TYPE_BOOL: cspType = csp::CspType::BOOL(); break; + case CCSP_TYPE_INT8: cspType = csp::CspType::INT8(); break; + case CCSP_TYPE_UINT8: cspType = csp::CspType::UINT8(); break; + case CCSP_TYPE_INT16: cspType = csp::CspType::INT16(); break; + case CCSP_TYPE_UINT16: cspType = csp::CspType::UINT16(); break; + case CCSP_TYPE_INT32: cspType = csp::CspType::INT32(); break; + case CCSP_TYPE_UINT32: cspType = csp::CspType::UINT32(); break; + case CCSP_TYPE_INT64: cspType = csp::CspType::INT64(); break; + case CCSP_TYPE_UINT64: cspType = csp::CspType::UINT64(); break; + case CCSP_TYPE_DOUBLE: cspType = csp::CspType::DOUBLE(); break; + case CCSP_TYPE_STRING: cspType = csp::CspType::STRING(); break; + case CCSP_TYPE_DATETIME: cspType = csp::CspType::DATETIME(); break; + case CCSP_TYPE_TIMEDELTA: cspType = csp::CspType::TIMEDELTA(); break; + case CCSP_TYPE_DATE: cspType = csp::CspType::DATE(); break; + case CCSP_TYPE_TIME: cspType = csp::CspType::TIME(); break; + default: + ccsp_set_error( CCSP_ERROR_INVALID_ARGUMENT, "unsupported input type" ); + return nullptr; + } + + auto * adapter = m -> engine() -> createOwnedObject( cspType, *vtable ); + return reinterpret_cast( adapter ); + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return nullptr; + } +} + +CCspErrorCode ccsp_adapter_manager_push_status( CCspAdapterManagerHandle manager, CCspStatusLevel level, int64_t err_code, const char * message ) +{ + if( !manager ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null manager" ); + return CCSP_ERROR_NULL_POINTER; + } + + try + { + auto * m = reinterpret_cast( manager ); + m -> pushStatus( static_cast( level ), err_code, + message ? message : "" ); + return CCSP_OK; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +// TODO: Implement these when ManagedSimInputAdapter extern support is added +CCspManagedSimInputAdapterHandle ccsp_adapter_manager_create_managed_sim_input_adapter( CCspAdapterManagerHandle manager, CCspType type, CCspPushMode push_mode ) +{ + ccsp_set_error( CCSP_ERROR_NOT_IMPLEMENTED, "managed sim input adapter not yet implemented" ); + return nullptr; +} + +CCspErrorCode ccsp_managed_sim_input_adapter_push_bool( CCspManagedSimInputAdapterHandle adapter, int8_t value ) +{ + ccsp_set_error( CCSP_ERROR_NOT_IMPLEMENTED, "managed sim input adapter not yet implemented" ); + return CCSP_ERROR_NOT_IMPLEMENTED; +} + +CCspErrorCode ccsp_managed_sim_input_adapter_push_int64( CCspManagedSimInputAdapterHandle adapter, int64_t value ) +{ + ccsp_set_error( CCSP_ERROR_NOT_IMPLEMENTED, "managed sim input adapter not yet implemented" ); + return CCSP_ERROR_NOT_IMPLEMENTED; +} + +CCspErrorCode ccsp_managed_sim_input_adapter_push_double( CCspManagedSimInputAdapterHandle adapter, double value ) +{ + ccsp_set_error( CCSP_ERROR_NOT_IMPLEMENTED, "managed sim input adapter not yet implemented" ); + return CCSP_ERROR_NOT_IMPLEMENTED; +} + +CCspErrorCode ccsp_managed_sim_input_adapter_push_string( CCspManagedSimInputAdapterHandle adapter, const char * data, size_t length ) +{ + ccsp_set_error( CCSP_ERROR_NOT_IMPLEMENTED, "managed sim input adapter not yet implemented" ); + return CCSP_ERROR_NOT_IMPLEMENTED; +} + +CCspErrorCode ccsp_managed_sim_input_adapter_push_datetime( CCspManagedSimInputAdapterHandle adapter, CCspDateTime value ) +{ + ccsp_set_error( CCSP_ERROR_NOT_IMPLEMENTED, "managed sim input adapter not yet implemented" ); + return CCSP_ERROR_NOT_IMPLEMENTED; +} + +// Push input adapter creation from manager +CCspPushInputAdapterHandle ccsp_adapter_manager_create_push_input_adapter( CCspAdapterManagerHandle manager, CCspType type, CCspPushMode push_mode, const CCspPushInputAdapterVTable * vtable ) +{ + // For now, delegate to the standalone creation + // In a full implementation, the manager would track these adapters + if( !manager ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null manager" ); + return nullptr; + } + + auto * m = reinterpret_cast( manager ); + ( void ) m; // Currently unused - will be used when implemented + + // Use the standalone push input adapter creation + // This is a simplification - full implementation would track adapters + ccsp_set_error( CCSP_ERROR_NOT_IMPLEMENTED, "push input adapter creation via manager not yet implemented" ); + return nullptr; +} + +} // extern "C" diff --git a/cpp/csp/engine/AdapterManagerExtern.h b/cpp/csp/engine/AdapterManagerExtern.h new file mode 100644 index 000000000..c8a9209b2 --- /dev/null +++ b/cpp/csp/engine/AdapterManagerExtern.h @@ -0,0 +1,39 @@ +#ifndef _IN_CSP_ENGINE_ADAPTER_MANAGER_EXTERN_H +#define _IN_CSP_ENGINE_ADAPTER_MANAGER_EXTERN_H + +/* + * C++ wrapper for external Adapter Managers using the C ABI interface. + * + * This class wraps an adapter manager implemented in C (or any language with C FFI) + * and integrates it with the CSP engine's AdapterManager interface. + */ + +#include +#include + +namespace csp +{ + +class AdapterManagerExtern final : public AdapterManager +{ +public: + AdapterManagerExtern( Engine * engine, const CCspAdapterManagerVTable & vtable ); + ~AdapterManagerExtern() override; + + const char * name() const override; + + void start( DateTime startTime, DateTime endTime ) override; + void stop() override; + DateTime processNextSimTimeSlice( DateTime time ) override; + + // Access for C API + const CCspAdapterManagerVTable & vtable() const { return m_vtable; } + +private: + CCspAdapterManagerVTable m_vtable; + mutable std::string m_name; // cached name +}; + +} // namespace csp + +#endif /* _IN_CSP_ENGINE_ADAPTER_MANAGER_EXTERN_H */ diff --git a/cpp/csp/engine/CMakeLists.txt b/cpp/csp/engine/CMakeLists.txt index b14a88b5c..ba3c1287a 100644 --- a/cpp/csp/engine/CMakeLists.txt +++ b/cpp/csp/engine/CMakeLists.txt @@ -18,6 +18,21 @@ set(CSP_TYPES_SOURCE_FILES TypeCast.h ) +# C API headers (ABI-stable interface) +set(CSP_C_API_HEADERS + c/CspExport.h + c/CspError.h + c/CspTime.h + c/CspString.h + c/CspType.h + c/CspValue.h + c/CspDictionary.h + c/CspStruct.h + c/OutputAdapter.h + c/InputAdapter.h + c/AdapterManager.h +) + set(ENGINE_PUBLIC_HEADERS ${ENGINE_AUTOGEN_HEADER} AdapterManager.h @@ -39,12 +54,15 @@ set(ENGINE_PUBLIC_HEADERS InputId.h Node.h OutputAdapter.h + OutputAdapterExtern.h + AdapterManagerExtern.h PendingPushEvents.h Profiler.h PushPullEvent.h PushEvent.h PullInputAdapter.h PushInputAdapter.h + PushInputAdapterExtern.h PushPullInputAdapter.h RootEngine.h Scheduler.h @@ -73,6 +91,11 @@ set(ENGINE_SOURCE_FILES InputAdapter.cpp Node.cpp OutputAdapter.cpp + OutputAdapterExtern.cpp + AdapterManagerExtern.cpp + DictionaryExtern.cpp + StructExtern.cpp + PushInputAdapterExtern.cpp PendingPushEvents.cpp PushPullInputAdapter.cpp RootEngine.cpp @@ -93,6 +116,9 @@ target_link_libraries(csp_engine csp_core csp_types) install(FILES ${CSP_TYPES_PUBLIC_HEADERS} ${ENGINE_PUBLIC_HEADERS} DESTINATION include/csp/engine) +# Install C API headers separately for external adapter compilation +install(FILES ${CSP_C_API_HEADERS} DESTINATION include/csp/engine/c) + install(TARGETS csp_types csp_engine PUBLIC_HEADER DESTINATION include/csp/engine RUNTIME DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR} diff --git a/cpp/csp/engine/DictionaryExtern.cpp b/cpp/csp/engine/DictionaryExtern.cpp new file mode 100644 index 000000000..a1bf8a208 --- /dev/null +++ b/cpp/csp/engine/DictionaryExtern.cpp @@ -0,0 +1,916 @@ +/* + * Implementation of the C API for Dictionary access. + */ +#include +#include +#include +#include +#include +#include + +// ============================================================================ +// Iterator State +// ============================================================================ + +struct CCspDictIteratorImpl +{ + const csp::Dictionary * dict; + csp::Dictionary::const_iterator current; + csp::Dictionary::const_iterator end; + bool started; // Have we called next() at least once? + + CCspDictIteratorImpl( const csp::Dictionary * d ) + : dict( d ) + , current( d -> begin() ) + , end( d -> end() ) + , started( false ) + {} +}; + +// ============================================================================ +// Helper to map std::variant index to CCspDictValueType +// ============================================================================ + +static CCspDictValueType variantIndexToType( size_t index ) +{ + // Dictionary::Value = std::variant> + switch( index ) + { + case 0: return CCSP_DICT_TYPE_NONE; // monostate + case 1: return CCSP_DICT_TYPE_BOOL; + case 2: return CCSP_DICT_TYPE_INT32; + case 3: return CCSP_DICT_TYPE_UINT32; + case 4: return CCSP_DICT_TYPE_INT64; + case 5: return CCSP_DICT_TYPE_UINT64; + case 6: return CCSP_DICT_TYPE_DOUBLE; + case 7: return CCSP_DICT_TYPE_STRING; + case 8: return CCSP_DICT_TYPE_DATETIME; + case 9: return CCSP_DICT_TYPE_TIMEDELTA; + case 10: return CCSP_DICT_TYPE_STRUCT_META; + case 11: return CCSP_DICT_TYPE_DIALECT; + case 12: return CCSP_DICT_TYPE_DICTIONARY; + case 13: return CCSP_DICT_TYPE_VECTOR; + case 14: return CCSP_DICT_TYPE_DATA; + default: return CCSP_DICT_TYPE_NONE; + } +} + +// ============================================================================ +// C API Implementation +// ============================================================================ + +extern "C" { + +// ---------------------------------------------------------------------------- +// Basic Operations +// ---------------------------------------------------------------------------- + +int ccsp_dictionary_exists( CCspDictionaryHandle dict, const char * key ) +{ + if( !dict || !key ) return 0; + auto * d = reinterpret_cast( dict ); + return d -> exists( key ) ? 1 : 0; +} + +size_t ccsp_dictionary_size( CCspDictionaryHandle dict ) +{ + if( !dict ) return 0; + auto * d = reinterpret_cast( dict ); + return d -> size(); +} + +int ccsp_dictionary_is_empty( CCspDictionaryHandle dict ) +{ + if( !dict ) return 1; + auto * d = reinterpret_cast( dict ); + return d -> empty() ? 1 : 0; +} + +CCspDictValueType ccsp_dictionary_get_type( CCspDictionaryHandle dict, const char * key ) +{ + if( !dict || !key ) return CCSP_DICT_TYPE_NONE; + auto * d = reinterpret_cast( dict ); + + if( !d -> exists( key ) ) + return CCSP_DICT_TYPE_NONE; + + try + { + const csp::Dictionary::Value & value = d -> getUntypedValue( key ); + return variantIndexToType( value.index() ); + } + catch( ... ) + { + return CCSP_DICT_TYPE_NONE; + } +} + +// ---------------------------------------------------------------------------- +// Type-Safe Getters +// ---------------------------------------------------------------------------- + +CCspErrorCode ccsp_dictionary_get_bool( CCspDictionaryHandle dict, const char * key, int8_t * out_value ) +{ + if( !dict || !key || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * d = reinterpret_cast( dict ); + try + { + *out_value = d -> get( key ) ? 1 : 0; + return CCSP_OK; + } + catch( const csp::KeyError & ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "key not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + catch( const csp::TypeError & ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_dictionary_get_int32( CCspDictionaryHandle dict, const char * key, int32_t * out_value ) +{ + if( !dict || !key || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * d = reinterpret_cast( dict ); + try + { + *out_value = d -> get( key ); + return CCSP_OK; + } + catch( const csp::KeyError & ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "key not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + catch( const csp::TypeError & ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_dictionary_get_uint32( CCspDictionaryHandle dict, const char * key, uint32_t * out_value ) +{ + if( !dict || !key || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * d = reinterpret_cast( dict ); + try + { + *out_value = d -> get( key ); + return CCSP_OK; + } + catch( const csp::KeyError & ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "key not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + catch( const csp::TypeError & ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_dictionary_get_int64( CCspDictionaryHandle dict, const char * key, int64_t * out_value ) +{ + if( !dict || !key || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * d = reinterpret_cast( dict ); + try + { + *out_value = d -> get( key ); + return CCSP_OK; + } + catch( const csp::KeyError & ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "key not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + catch( const csp::TypeError & ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_dictionary_get_uint64( CCspDictionaryHandle dict, const char * key, uint64_t * out_value ) +{ + if( !dict || !key || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * d = reinterpret_cast( dict ); + try + { + *out_value = d -> get( key ); + return CCSP_OK; + } + catch( const csp::KeyError & ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "key not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + catch( const csp::TypeError & ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_dictionary_get_double( CCspDictionaryHandle dict, const char * key, double * out_value ) +{ + if( !dict || !key || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * d = reinterpret_cast( dict ); + try + { + *out_value = d -> get( key ); + return CCSP_OK; + } + catch( const csp::KeyError & ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "key not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + catch( const csp::TypeError & ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_dictionary_get_datetime( CCspDictionaryHandle dict, const char * key, CCspDateTime * out_value ) +{ + if( !dict || !key || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * d = reinterpret_cast( dict ); + try + { + csp::DateTime dt = d -> get( key ); + *out_value = dt.asNanoseconds(); + return CCSP_OK; + } + catch( const csp::KeyError & ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "key not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + catch( const csp::TypeError & ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_dictionary_get_timedelta( CCspDictionaryHandle dict, const char * key, CCspTimeDelta * out_value ) +{ + if( !dict || !key || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * d = reinterpret_cast( dict ); + try + { + csp::TimeDelta td = d -> get( key ); + *out_value = td.asNanoseconds(); + return CCSP_OK; + } + catch( const csp::KeyError & ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "key not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + catch( const csp::TypeError & ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_dictionary_get_string( CCspDictionaryHandle dict, const char * key, const char ** out_data, size_t * out_length ) +{ + if( !dict || !key || !out_data || !out_length ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * d = reinterpret_cast( dict ); + try + { + const std::string & str = d -> get( key ); + *out_data = str.data(); + *out_length = str.size(); + return CCSP_OK; + } + catch( const csp::KeyError & ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "key not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + catch( const csp::TypeError & ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_dictionary_get_dict( CCspDictionaryHandle dict, const char * key, CCspDictionaryHandle * out_dict ) +{ + if( !dict || !key || !out_dict ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * d = reinterpret_cast( dict ); + try + { + csp::DictionaryPtr nested = d -> get( key ); + *out_dict = reinterpret_cast( nested.get() ); + return CCSP_OK; + } + catch( const csp::KeyError & ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "key not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + catch( const csp::TypeError & ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +// ---------------------------------------------------------------------------- +// Getters with Defaults +// ---------------------------------------------------------------------------- + +int8_t ccsp_dictionary_get_bool_or( CCspDictionaryHandle dict, const char * key, int8_t default_value ) +{ + if( !dict || !key ) return default_value; + auto * d = reinterpret_cast( dict ); + try + { + return d -> get( key, default_value != 0 ) ? 1 : 0; + } + catch( ... ) + { + return default_value; + } +} + +int32_t ccsp_dictionary_get_int32_or( CCspDictionaryHandle dict, const char * key, int32_t default_value ) +{ + if( !dict || !key ) return default_value; + auto * d = reinterpret_cast( dict ); + try + { + return d -> get( key, default_value ); + } + catch( ... ) + { + return default_value; + } +} + +uint32_t ccsp_dictionary_get_uint32_or( CCspDictionaryHandle dict, const char * key, uint32_t default_value ) +{ + if( !dict || !key ) return default_value; + auto * d = reinterpret_cast( dict ); + try + { + return d -> get( key, default_value ); + } + catch( ... ) + { + return default_value; + } +} + +int64_t ccsp_dictionary_get_int64_or( CCspDictionaryHandle dict, const char * key, int64_t default_value ) +{ + if( !dict || !key ) return default_value; + auto * d = reinterpret_cast( dict ); + try + { + return d -> get( key, default_value ); + } + catch( ... ) + { + return default_value; + } +} + +uint64_t ccsp_dictionary_get_uint64_or( CCspDictionaryHandle dict, const char * key, uint64_t default_value ) +{ + if( !dict || !key ) return default_value; + auto * d = reinterpret_cast( dict ); + try + { + return d -> get( key, default_value ); + } + catch( ... ) + { + return default_value; + } +} + +double ccsp_dictionary_get_double_or( CCspDictionaryHandle dict, const char * key, double default_value ) +{ + if( !dict || !key ) return default_value; + auto * d = reinterpret_cast( dict ); + try + { + return d -> get( key, default_value ); + } + catch( ... ) + { + return default_value; + } +} + +CCspDateTime ccsp_dictionary_get_datetime_or( CCspDictionaryHandle dict, const char * key, CCspDateTime default_value ) +{ + if( !dict || !key ) return default_value; + auto * d = reinterpret_cast( dict ); + try + { + csp::DateTime def = csp::DateTime::fromNanoseconds( default_value ); + csp::DateTime dt = d -> get( key, def ); + return dt.asNanoseconds(); + } + catch( ... ) + { + return default_value; + } +} + +CCspTimeDelta ccsp_dictionary_get_timedelta_or( CCspDictionaryHandle dict, const char * key, CCspTimeDelta default_value ) +{ + if( !dict || !key ) return default_value; + auto * d = reinterpret_cast( dict ); + try + { + csp::TimeDelta def = csp::TimeDelta::fromNanoseconds( default_value ); + csp::TimeDelta td = d -> get( key, def ); + return td.asNanoseconds(); + } + catch( ... ) + { + return default_value; + } +} + +const char * ccsp_dictionary_get_string_or( CCspDictionaryHandle dict, const char * key, const char * default_value, size_t * out_length ) +{ + if( !dict || !key ) + { + if( out_length && default_value ) + *out_length = strlen( default_value ); + else if( out_length ) + *out_length = 0; + return default_value; + } + + auto * d = reinterpret_cast( dict ); + try + { + if( !d -> exists( key ) ) + { + if( out_length && default_value ) + *out_length = strlen( default_value ); + else if( out_length ) + *out_length = 0; + return default_value; + } + + const std::string & str = d -> get( key ); + if( out_length ) + *out_length = str.size(); + return str.data(); + } + catch( ... ) + { + if( out_length && default_value ) + *out_length = strlen( default_value ); + else if( out_length ) + *out_length = 0; + return default_value; + } +} + +// ---------------------------------------------------------------------------- +// Iteration +// ---------------------------------------------------------------------------- + +CCspDictIteratorHandle ccsp_dictionary_iter_create( CCspDictionaryHandle dict ) +{ + if( !dict ) return nullptr; + + auto * d = reinterpret_cast( dict ); + auto * iter = new CCspDictIteratorImpl( d ); + return reinterpret_cast( iter ); +} + +void ccsp_dictionary_iter_destroy( CCspDictIteratorHandle iter ) +{ + if( !iter ) return; + auto * impl = reinterpret_cast( iter ); + delete impl; +} + +int ccsp_dictionary_iter_next( CCspDictIteratorHandle iter, const char ** out_key ) +{ + if( !iter || !out_key ) return 0; + + auto * impl = reinterpret_cast( iter ); + + if( !impl -> started ) + { + impl -> started = true; + } + else + { + ++( impl -> current ); + } + + if( impl -> current == impl -> end ) + return 0; + + *out_key = impl -> current.key().c_str(); + return 1; +} + +CCspDictValueType ccsp_dictionary_iter_value_type( CCspDictIteratorHandle iter ) +{ + if( !iter ) return CCSP_DICT_TYPE_NONE; + + auto * impl = reinterpret_cast( iter ); + if( !impl -> started || impl -> current == impl -> end ) + return CCSP_DICT_TYPE_NONE; + + const csp::Dictionary::Value & value = impl -> current.getUntypedValue(); + return variantIndexToType( value.index() ); +} + +CCspErrorCode ccsp_dictionary_iter_get_bool( CCspDictIteratorHandle iter, int8_t * out_value ) +{ + if( !iter || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * impl = reinterpret_cast( iter ); + if( !impl -> started || impl -> current == impl -> end ) + { + ccsp_set_error( CCSP_ERROR_INVALID_ARGUMENT, "iterator not positioned" ); + return CCSP_ERROR_INVALID_ARGUMENT; + } + + try + { + *out_value = impl -> current.value() ? 1 : 0; + return CCSP_OK; + } + catch( ... ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } +} + +CCspErrorCode ccsp_dictionary_iter_get_int32( CCspDictIteratorHandle iter, int32_t * out_value ) +{ + if( !iter || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * impl = reinterpret_cast( iter ); + if( !impl -> started || impl -> current == impl -> end ) + { + ccsp_set_error( CCSP_ERROR_INVALID_ARGUMENT, "iterator not positioned" ); + return CCSP_ERROR_INVALID_ARGUMENT; + } + + try + { + *out_value = impl -> current.value(); + return CCSP_OK; + } + catch( ... ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } +} + +CCspErrorCode ccsp_dictionary_iter_get_uint32( CCspDictIteratorHandle iter, uint32_t * out_value ) +{ + if( !iter || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * impl = reinterpret_cast( iter ); + if( !impl -> started || impl -> current == impl -> end ) + { + ccsp_set_error( CCSP_ERROR_INVALID_ARGUMENT, "iterator not positioned" ); + return CCSP_ERROR_INVALID_ARGUMENT; + } + + try + { + *out_value = impl -> current.value(); + return CCSP_OK; + } + catch( ... ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } +} + +CCspErrorCode ccsp_dictionary_iter_get_int64( CCspDictIteratorHandle iter, int64_t * out_value ) +{ + if( !iter || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * impl = reinterpret_cast( iter ); + if( !impl -> started || impl -> current == impl -> end ) + { + ccsp_set_error( CCSP_ERROR_INVALID_ARGUMENT, "iterator not positioned" ); + return CCSP_ERROR_INVALID_ARGUMENT; + } + + try + { + *out_value = impl -> current.value(); + return CCSP_OK; + } + catch( ... ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } +} + +CCspErrorCode ccsp_dictionary_iter_get_uint64( CCspDictIteratorHandle iter, uint64_t * out_value ) +{ + if( !iter || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * impl = reinterpret_cast( iter ); + if( !impl -> started || impl -> current == impl -> end ) + { + ccsp_set_error( CCSP_ERROR_INVALID_ARGUMENT, "iterator not positioned" ); + return CCSP_ERROR_INVALID_ARGUMENT; + } + + try + { + *out_value = impl -> current.value(); + return CCSP_OK; + } + catch( ... ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } +} + +CCspErrorCode ccsp_dictionary_iter_get_double( CCspDictIteratorHandle iter, double * out_value ) +{ + if( !iter || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * impl = reinterpret_cast( iter ); + if( !impl -> started || impl -> current == impl -> end ) + { + ccsp_set_error( CCSP_ERROR_INVALID_ARGUMENT, "iterator not positioned" ); + return CCSP_ERROR_INVALID_ARGUMENT; + } + + try + { + *out_value = impl -> current.value(); + return CCSP_OK; + } + catch( ... ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } +} + +CCspErrorCode ccsp_dictionary_iter_get_datetime( CCspDictIteratorHandle iter, CCspDateTime * out_value ) +{ + if( !iter || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * impl = reinterpret_cast( iter ); + if( !impl -> started || impl -> current == impl -> end ) + { + ccsp_set_error( CCSP_ERROR_INVALID_ARGUMENT, "iterator not positioned" ); + return CCSP_ERROR_INVALID_ARGUMENT; + } + + try + { + csp::DateTime dt = impl -> current.value(); + *out_value = dt.asNanoseconds(); + return CCSP_OK; + } + catch( ... ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } +} + +CCspErrorCode ccsp_dictionary_iter_get_timedelta( CCspDictIteratorHandle iter, CCspTimeDelta * out_value ) +{ + if( !iter || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * impl = reinterpret_cast( iter ); + if( !impl -> started || impl -> current == impl -> end ) + { + ccsp_set_error( CCSP_ERROR_INVALID_ARGUMENT, "iterator not positioned" ); + return CCSP_ERROR_INVALID_ARGUMENT; + } + + try + { + csp::TimeDelta td = impl -> current.value(); + *out_value = td.asNanoseconds(); + return CCSP_OK; + } + catch( ... ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } +} + +CCspErrorCode ccsp_dictionary_iter_get_string( CCspDictIteratorHandle iter, const char ** out_data, size_t * out_length ) +{ + if( !iter || !out_data || !out_length ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * impl = reinterpret_cast( iter ); + if( !impl -> started || impl -> current == impl -> end ) + { + ccsp_set_error( CCSP_ERROR_INVALID_ARGUMENT, "iterator not positioned" ); + return CCSP_ERROR_INVALID_ARGUMENT; + } + + try + { + const std::string & str = impl -> current.value(); + *out_data = str.data(); + *out_length = str.size(); + return CCSP_OK; + } + catch( ... ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } +} + +CCspErrorCode ccsp_dictionary_iter_get_dict( CCspDictIteratorHandle iter, CCspDictionaryHandle * out_dict ) +{ + if( !iter || !out_dict ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * impl = reinterpret_cast( iter ); + if( !impl -> started || impl -> current == impl -> end ) + { + ccsp_set_error( CCSP_ERROR_INVALID_ARGUMENT, "iterator not positioned" ); + return CCSP_ERROR_INVALID_ARGUMENT; + } + + try + { + csp::DictionaryPtr nested = impl -> current.value(); + *out_dict = reinterpret_cast( nested.get() ); + return CCSP_OK; + } + catch( ... ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } +} + +} // extern "C" diff --git a/cpp/csp/engine/OutputAdapterExtern.cpp b/cpp/csp/engine/OutputAdapterExtern.cpp new file mode 100644 index 000000000..d5896737f --- /dev/null +++ b/cpp/csp/engine/OutputAdapterExtern.cpp @@ -0,0 +1,291 @@ +/* + * Implementation of the C++ OutputAdapterExtern wrapper and C API functions. + */ +#include +#include +#include +#include +#include +#include +#include + +namespace csp +{ + +// ============================================================================ +// OutputAdapterExtern Implementation +// ============================================================================ + +OutputAdapterExtern::OutputAdapterExtern( Engine * engine, const CspTypePtr & type, + const CCspOutputAdapterVTable & vtable ) + : OutputAdapter( engine ) + , m_vtable( vtable ) + , m_startTime( DateTime::NONE() ) + , m_endTime( DateTime::NONE() ) +{ + if( !vtable.execute ) + { + CSP_THROW( ValueError, "OutputAdapterExtern: execute callback is required" ); + } + if( !vtable.destroy ) + { + CSP_THROW( ValueError, "OutputAdapterExtern: destroy callback is required" ); + } +} + +OutputAdapterExtern::~OutputAdapterExtern() +{ + if( m_vtable.destroy ) + { + m_vtable.destroy( m_vtable.user_data ); + } +} + +void OutputAdapterExtern::start() +{ + OutputAdapter::start(); + m_startTime = rootEngine() -> startTime(); + m_endTime = rootEngine() -> endTime(); + + if( m_vtable.start ) + { + CCspEngineHandle engineHandle = reinterpret_cast( engine() ); + m_vtable.start( m_vtable.user_data, engineHandle, + m_startTime.asNanoseconds(), m_endTime.asNanoseconds() ); + } +} + +void OutputAdapterExtern::stop() +{ + if( m_vtable.stop ) + { + m_vtable.stop( m_vtable.user_data ); + } + OutputAdapter::stop(); +} + +void OutputAdapterExtern::executeImpl() +{ + CCspEngineHandle engineHandle = reinterpret_cast( engine() ); + CCspInputHandle inputHandle = reinterpret_cast( input() ); + + m_vtable.execute( m_vtable.user_data, engineHandle, inputHandle ); +} + +} // namespace csp + +// ============================================================================ +// C API Implementation +// ============================================================================ + +extern "C" { + +// Thread-local error state +static thread_local CCspErrorCode s_lastError = CCSP_OK; +static thread_local char s_lastErrorMessage[256] = {0}; + +CCspErrorCode ccsp_get_last_error(void) +{ + return s_lastError; +} + +const char * ccsp_get_last_error_message(void) +{ + return s_lastErrorMessage[0] ? s_lastErrorMessage : nullptr; +} + +void ccsp_clear_error(void) +{ + s_lastError = CCSP_OK; + s_lastErrorMessage[0] = '\0'; +} + +void ccsp_set_error( CCspErrorCode code, const char * message ) +{ + s_lastError = code; + if( message ) + { + strncpy( s_lastErrorMessage, message, sizeof( s_lastErrorMessage ) - 1 ); + s_lastErrorMessage[sizeof( s_lastErrorMessage ) - 1] = '\0'; + } + else + { + s_lastErrorMessage[0] = '\0'; + } +} + +// ============================================================================ +// Input Access Functions +// ============================================================================ + +int ccsp_input_is_valid( CCspInputHandle input ) +{ + if( !input ) return 0; + auto * provider = reinterpret_cast( input ); + return provider -> valid() ? 1 : 0; +} + +int32_t ccsp_input_num_ticks( CCspInputHandle input ) +{ + if( !input ) return 0; + auto * provider = reinterpret_cast( input ); + return provider -> numTicks(); +} + +CCspType ccsp_input_get_type( CCspInputHandle input ) +{ + if( !input ) return CCSP_TYPE_UNKNOWN; + auto * provider = reinterpret_cast( input ); + + // Map CspType to CCspType + switch( provider -> type() -> type() ) + { + case csp::CspType::Type::BOOL: return CCSP_TYPE_BOOL; + case csp::CspType::Type::INT8: return CCSP_TYPE_INT8; + case csp::CspType::Type::UINT8: return CCSP_TYPE_UINT8; + case csp::CspType::Type::INT16: return CCSP_TYPE_INT16; + case csp::CspType::Type::UINT16: return CCSP_TYPE_UINT16; + case csp::CspType::Type::INT32: return CCSP_TYPE_INT32; + case csp::CspType::Type::UINT32: return CCSP_TYPE_UINT32; + case csp::CspType::Type::INT64: return CCSP_TYPE_INT64; + case csp::CspType::Type::UINT64: return CCSP_TYPE_UINT64; + case csp::CspType::Type::DOUBLE: return CCSP_TYPE_DOUBLE; + case csp::CspType::Type::STRING: return CCSP_TYPE_STRING; + case csp::CspType::Type::DATETIME: return CCSP_TYPE_DATETIME; + case csp::CspType::Type::TIMEDELTA: return CCSP_TYPE_TIMEDELTA; + case csp::CspType::Type::DATE: return CCSP_TYPE_DATE; + case csp::CspType::Type::TIME: return CCSP_TYPE_TIME; + case csp::CspType::Type::ENUM: return CCSP_TYPE_ENUM; + case csp::CspType::Type::STRUCT: return CCSP_TYPE_STRUCT; + case csp::CspType::Type::ARRAY: return CCSP_TYPE_ARRAY; + case csp::CspType::Type::DIALECT_GENERIC: return CCSP_TYPE_DIALECT_GENERIC; + default: return CCSP_TYPE_UNKNOWN; + } +} + +CCspDateTime ccsp_input_get_last_time( CCspInputHandle input ) +{ + if( !input ) return 0; + auto * provider = reinterpret_cast( input ); + return provider -> lastTime().asNanoseconds(); +} + +CCspErrorCode ccsp_input_get_last_string( CCspInputHandle input, + const char ** out_data, + size_t * out_length ) +{ + if( !input || !out_data || !out_length ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * provider = reinterpret_cast( input ); + if( provider -> type() -> type() != csp::CspType::Type::STRING ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "input is not a string type" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + const std::string & value = provider -> lastValueTyped(); + *out_data = value.data(); + *out_length = value.size(); + + return CCSP_OK; +} + +CCspErrorCode ccsp_input_get_last_int64( CCspInputHandle input, int64_t * out_value ) +{ + if( !input || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * provider = reinterpret_cast( input ); + if( provider -> type() -> type() != csp::CspType::Type::INT64 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "input is not an int64 type" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + *out_value = provider -> lastValueTyped(); + return CCSP_OK; +} + +CCspErrorCode ccsp_input_get_last_double( CCspInputHandle input, double * out_value ) +{ + if( !input || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * provider = reinterpret_cast( input ); + if( provider -> type() -> type() != csp::CspType::Type::DOUBLE ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "input is not a double type" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + *out_value = provider -> lastValueTyped(); + return CCSP_OK; +} + +CCspErrorCode ccsp_input_get_last_bool( CCspInputHandle input, int8_t * out_value ) +{ + if( !input || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * provider = reinterpret_cast( input ); + if( provider -> type() -> type() != csp::CspType::Type::BOOL ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "input is not a bool type" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + *out_value = provider -> lastValueTyped() ? 1 : 0; + return CCSP_OK; +} + +CCspErrorCode ccsp_input_get_last_datetime( CCspInputHandle input, CCspDateTime * out_value ) +{ + if( !input || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * provider = reinterpret_cast( input ); + if( provider -> type() -> type() != csp::CspType::Type::DATETIME ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "input is not a datetime type" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + *out_value = provider -> lastValueTyped().asNanoseconds(); + return CCSP_OK; +} + +// ============================================================================ +// Engine Access Functions +// ============================================================================ + +CCspDateTime ccsp_engine_now( CCspEngineHandle engine ) +{ + if( !engine ) return 0; + auto * e = reinterpret_cast( engine ); + return e -> rootEngine() -> now().asNanoseconds(); +} + +uint64_t ccsp_engine_cycle_count( CCspEngineHandle engine ) +{ + if( !engine ) return 0; + auto * e = reinterpret_cast( engine ); + return e -> rootEngine() -> cycleCount(); +} + +} // extern "C" diff --git a/cpp/csp/engine/OutputAdapterExtern.h b/cpp/csp/engine/OutputAdapterExtern.h new file mode 100644 index 000000000..d814713ed --- /dev/null +++ b/cpp/csp/engine/OutputAdapterExtern.h @@ -0,0 +1,38 @@ +#ifndef _IN_CSP_ENGINE_OUTPUT_ADAPTER_EXTERN_H +#define _IN_CSP_ENGINE_OUTPUT_ADAPTER_EXTERN_H + +/* + * C++ wrapper for external Output Adapters using the C ABI interface. + * + * This class wraps an adapter implemented in C (or any language with C FFI) + * and integrates it with the CSP engine's OutputAdapter interface. + */ + +#include +#include + +namespace csp +{ + +class OutputAdapterExtern final : public OutputAdapter +{ +public: + OutputAdapterExtern( Engine * engine, const CspTypePtr & type, const CCspOutputAdapterVTable & vtable ); + ~OutputAdapterExtern() override; + + const char* name() const override { return "OutputAdapterExtern"; } + + void start() override; + void stop() override; + void executeImpl() override; + +private: + CCspOutputAdapterVTable m_vtable; + DateTime m_startTime; + DateTime m_endTime; +}; + +} + +#endif /* _IN_CSP_ENGINE_OUTPUT_ADAPTER_EXTERN_H */ + diff --git a/cpp/csp/engine/PushInputAdapterExtern.cpp b/cpp/csp/engine/PushInputAdapterExtern.cpp new file mode 100644 index 000000000..a32a2830c --- /dev/null +++ b/cpp/csp/engine/PushInputAdapterExtern.cpp @@ -0,0 +1,564 @@ +/* + * Implementation of the C++ PushInputAdapterExtern wrapper and C API functions. + */ +#include +#include +#include +#include +#include +#include + +namespace csp +{ + +// ============================================================================ +// PushInputAdapterExtern Implementation +// ============================================================================ + +PushInputAdapterExtern::PushInputAdapterExtern( Engine * engine, CspTypePtr & type, + PushMode pushMode, PushGroup * group, + const CCspPushInputAdapterVTable & vtable ) + : PushInputAdapter( engine, type, pushMode, group ) + , m_vtable( vtable ) + , m_startTime( DateTime::NONE() ) + , m_endTime( DateTime::NONE() ) +{ + if( !vtable.destroy ) + { + CSP_THROW( ValueError, "PushInputAdapterExtern: destroy callback is required" ); + } +} + +PushInputAdapterExtern::~PushInputAdapterExtern() +{ + if( m_vtable.destroy ) + { + m_vtable.destroy( m_vtable.user_data ); + } +} + +void PushInputAdapterExtern::start( DateTime startTime, DateTime endTime ) +{ + m_startTime = startTime; + m_endTime = endTime; + + if( m_vtable.start ) + { + CCspEngineHandle engineHandle = reinterpret_cast( rootEngine() ); + CCspPushInputAdapterHandle adapterHandle = reinterpret_cast( this ); + + m_vtable.start( m_vtable.user_data, engineHandle, adapterHandle, + startTime.asNanoseconds(), endTime.asNanoseconds() ); + } +} + +void PushInputAdapterExtern::stop() +{ + if( m_vtable.stop ) + { + m_vtable.stop( m_vtable.user_data ); + } +} + +} // namespace csp + +// ============================================================================ +// C API Implementation +// ============================================================================ + +extern "C" { + +// Forward declaration of error functions from OutputAdapterExtern.cpp +extern void ccsp_set_error( CCspErrorCode code, const char * message ); + +// ============================================================================ +// Push Input Adapter Creation +// ============================================================================ + +CCspPushInputAdapterHandle ccsp_push_input_adapter_extern_create( CCspEngineHandle engine, CCspType type, + CCspPushMode push_mode, CCspPushGroupHandle group, + const CCspPushInputAdapterVTable* vtable ) +{ + if( !engine || !vtable ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null engine or vtable" ); + return nullptr; + } + + try + { + auto * eng = reinterpret_cast( engine ); + auto * grp = reinterpret_cast( group ); + + // Convert CCspType to CspTypePtr + csp::CspTypePtr cspType; + switch( type ) + { + case CCSP_TYPE_BOOL: cspType = csp::CspType::BOOL(); break; + case CCSP_TYPE_INT8: cspType = csp::CspType::INT8(); break; + case CCSP_TYPE_UINT8: cspType = csp::CspType::UINT8(); break; + case CCSP_TYPE_INT16: cspType = csp::CspType::INT16(); break; + case CCSP_TYPE_UINT16: cspType = csp::CspType::UINT16(); break; + case CCSP_TYPE_INT32: cspType = csp::CspType::INT32(); break; + case CCSP_TYPE_UINT32: cspType = csp::CspType::UINT32(); break; + case CCSP_TYPE_INT64: cspType = csp::CspType::INT64(); break; + case CCSP_TYPE_UINT64: cspType = csp::CspType::UINT64(); break; + case CCSP_TYPE_DOUBLE: cspType = csp::CspType::DOUBLE(); break; + case CCSP_TYPE_DATETIME: cspType = csp::CspType::DATETIME(); break; + case CCSP_TYPE_TIMEDELTA: cspType = csp::CspType::TIMEDELTA(); break; + case CCSP_TYPE_STRING: cspType = csp::CspType::STRING(); break; + default: + ccsp_set_error( CCSP_ERROR_INVALID_ARGUMENT, "unsupported type for push input adapter" ); + return nullptr; + } + + // Convert push mode + csp::PushMode cspPushMode; + switch( push_mode ) + { + case CCSP_PUSH_MODE_LAST_VALUE: cspPushMode = csp::PushMode::LAST_VALUE; break; + case CCSP_PUSH_MODE_NON_COLLAPSING: cspPushMode = csp::PushMode::NON_COLLAPSING; break; + case CCSP_PUSH_MODE_BURST: cspPushMode = csp::PushMode::BURST; break; + default: + ccsp_set_error( CCSP_ERROR_INVALID_ARGUMENT, "invalid push mode" ); + return nullptr; + } + + auto * adapter = eng -> createOwnedObject( cspType, cspPushMode, grp, *vtable ); + + return reinterpret_cast( adapter ); + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return nullptr; + } +} + +void ccsp_push_input_adapter_extern_destroy( CCspPushInputAdapterHandle adapter ) +{ + // The adapter is owned by the engine, destruction is handled there + ( void ) adapter; +} + +// ============================================================================ +// Type-specific push functions +// ============================================================================ + +CCspErrorCode ccsp_push_input_adapter_push_bool( CCspPushInputAdapterHandle adapter, int8_t value, CCspPushBatchHandle batch ) +{ + if( !adapter ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null adapter" ); + return CCSP_ERROR_NULL_POINTER; + } + + try + { + auto * pushAdapter = reinterpret_cast( adapter ); + auto * pushBatch = reinterpret_cast( batch ); + bool bvalue = static_cast( value ); + pushAdapter -> pushTick( std::move( bvalue ), pushBatch ); + return CCSP_OK; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_push_input_adapter_push_int8( CCspPushInputAdapterHandle adapter, int8_t value, CCspPushBatchHandle batch ) +{ + if( !adapter ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null adapter" ); + return CCSP_ERROR_NULL_POINTER; + } + + try + { + auto * pushAdapter = reinterpret_cast( adapter ); + auto * pushBatch = reinterpret_cast( batch ); + int8_t val = value; + pushAdapter -> pushTick( std::move( val ), pushBatch ); + return CCSP_OK; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_push_input_adapter_push_uint8( CCspPushInputAdapterHandle adapter, uint8_t value, CCspPushBatchHandle batch ) +{ + if( !adapter ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null adapter" ); + return CCSP_ERROR_NULL_POINTER; + } + + try + { + auto * pushAdapter = reinterpret_cast( adapter ); + auto * pushBatch = reinterpret_cast( batch ); + uint8_t val = value; + pushAdapter -> pushTick( std::move( val ), pushBatch ); + return CCSP_OK; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_push_input_adapter_push_int16( CCspPushInputAdapterHandle adapter, int16_t value, CCspPushBatchHandle batch ) +{ + if( !adapter ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null adapter" ); + return CCSP_ERROR_NULL_POINTER; + } + + try + { + auto * pushAdapter = reinterpret_cast( adapter ); + auto * pushBatch = reinterpret_cast( batch ); + int16_t val = value; + pushAdapter -> pushTick( std::move( val ), pushBatch ); + return CCSP_OK; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_push_input_adapter_push_uint16( CCspPushInputAdapterHandle adapter, uint16_t value, CCspPushBatchHandle batch ) +{ + if( !adapter ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null adapter" ); + return CCSP_ERROR_NULL_POINTER; + } + + try + { + auto * pushAdapter = reinterpret_cast( adapter ); + auto * pushBatch = reinterpret_cast( batch ); + uint16_t val = value; + pushAdapter -> pushTick( std::move( val ), pushBatch ); + return CCSP_OK; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_push_input_adapter_push_int32( CCspPushInputAdapterHandle adapter, int32_t value, CCspPushBatchHandle batch ) +{ + if( !adapter ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null adapter" ); + return CCSP_ERROR_NULL_POINTER; + } + + try + { + auto * pushAdapter = reinterpret_cast( adapter ); + auto * pushBatch = reinterpret_cast( batch ); + int32_t val = value; + pushAdapter -> pushTick( std::move( val ), pushBatch ); + return CCSP_OK; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_push_input_adapter_push_uint32( CCspPushInputAdapterHandle adapter, uint32_t value, CCspPushBatchHandle batch ) +{ + if( !adapter ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null adapter" ); + return CCSP_ERROR_NULL_POINTER; + } + + try + { + auto * pushAdapter = reinterpret_cast( adapter ); + auto * pushBatch = reinterpret_cast( batch ); + uint32_t val = value; + pushAdapter -> pushTick( std::move( val ), pushBatch ); + return CCSP_OK; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_push_input_adapter_push_int64( CCspPushInputAdapterHandle adapter, int64_t value, CCspPushBatchHandle batch ) +{ + if( !adapter ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null adapter" ); + return CCSP_ERROR_NULL_POINTER; + } + + try + { + auto * pushAdapter = reinterpret_cast( adapter ); + auto * pushBatch = reinterpret_cast( batch ); + int64_t val = value; + pushAdapter -> pushTick( std::move( val ), pushBatch ); + return CCSP_OK; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_push_input_adapter_push_uint64( CCspPushInputAdapterHandle adapter, uint64_t value, CCspPushBatchHandle batch ) +{ + if( !adapter ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null adapter" ); + return CCSP_ERROR_NULL_POINTER; + } + + try + { + auto * pushAdapter = reinterpret_cast( adapter ); + auto * pushBatch = reinterpret_cast( batch ); + uint64_t val = value; + pushAdapter -> pushTick( std::move( val ), pushBatch ); + return CCSP_OK; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_push_input_adapter_push_double( CCspPushInputAdapterHandle adapter, double value, CCspPushBatchHandle batch ) +{ + if( !adapter ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null adapter" ); + return CCSP_ERROR_NULL_POINTER; + } + + try + { + auto * pushAdapter = reinterpret_cast( adapter ); + auto * pushBatch = reinterpret_cast( batch ); + double val = value; + pushAdapter -> pushTick( std::move( val ), pushBatch ); + return CCSP_OK; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_push_input_adapter_push_datetime( CCspPushInputAdapterHandle adapter, CCspDateTime value, CCspPushBatchHandle batch ) +{ + if( !adapter ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null adapter" ); + return CCSP_ERROR_NULL_POINTER; + } + + try + { + auto * pushAdapter = reinterpret_cast( adapter ); + auto * pushBatch = reinterpret_cast( batch ); + csp::DateTime dt = csp::DateTime::fromNanoseconds( value ); + pushAdapter -> pushTick( std::move( dt ), pushBatch ); + return CCSP_OK; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_push_input_adapter_push_timedelta( CCspPushInputAdapterHandle adapter, CCspTimeDelta value, CCspPushBatchHandle batch ) +{ + if( !adapter ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null adapter" ); + return CCSP_ERROR_NULL_POINTER; + } + + try + { + auto * pushAdapter = reinterpret_cast( adapter ); + auto * pushBatch = reinterpret_cast( batch ); + csp::TimeDelta td = csp::TimeDelta::fromNanoseconds( value ); + pushAdapter -> pushTick( std::move( td ), pushBatch ); + return CCSP_OK; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_push_input_adapter_push_string( CCspPushInputAdapterHandle adapter, const char* data, size_t length, CCspPushBatchHandle batch ) +{ + if( !adapter ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null adapter" ); + return CCSP_ERROR_NULL_POINTER; + } + + if( !data && length > 0 ) + { + ccsp_set_error( CCSP_ERROR_INVALID_ARGUMENT, "null data with non-zero length" ); + return CCSP_ERROR_INVALID_ARGUMENT; + } + + try + { + auto * pushAdapter = reinterpret_cast( adapter ); + auto * pushBatch = reinterpret_cast( batch ); + std::string str( data ? data : "", length ); + pushAdapter -> pushTick( std::move( str ), pushBatch ); + return CCSP_OK; + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return CCSP_ERROR_RUNTIME; + } +} + +CCspErrorCode ccsp_push_input_adapter_push_struct( CCspPushInputAdapterHandle adapter, CCspStructHandle value, CCspPushBatchHandle batch ) +{ + // TODO: Implement struct push when struct support is complete + (void)adapter; + (void)value; + (void)batch; + ccsp_set_error( CCSP_ERROR_NOT_IMPLEMENTED, "struct push not yet implemented" ); + return CCSP_ERROR_NOT_IMPLEMENTED; +} + +CCspErrorCode ccsp_push_input_adapter_push_value( + CCspPushInputAdapterHandle adapter, + const CCspValue* value, + CCspPushBatchHandle batch ) +{ + // TODO: Implement generic value push + (void)adapter; + (void)value; + (void)batch; + ccsp_set_error( CCSP_ERROR_NOT_IMPLEMENTED, "generic value push not yet implemented" ); + return CCSP_ERROR_NOT_IMPLEMENTED; +} + +// ============================================================================ +// Push Batch Management +// ============================================================================ + +CCspPushBatchHandle ccsp_push_batch_create( CCspEngineHandle engine ) +{ + if( !engine ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null engine" ); + return nullptr; + } + + try + { + auto * eng = reinterpret_cast( engine ); + auto * batch = new csp::PushBatch( eng -> rootEngine() ); + return reinterpret_cast( batch ); + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return nullptr; + } +} + +void ccsp_push_batch_flush( CCspPushBatchHandle batch ) +{ + if( !batch ) return; + + try + { + auto * pushBatch = reinterpret_cast( batch ); + pushBatch -> flush(); + } + catch( ... ) + { + // Ignore errors during flush + } +} + +void ccsp_push_batch_destroy( CCspPushBatchHandle batch ) +{ + if( !batch ) return; + + try + { + auto * pushBatch = reinterpret_cast( batch ); + delete pushBatch; + } + catch( ... ) + { + // Ignore errors during destroy + } +} + +// ============================================================================ +// Push Group Management +// ============================================================================ + +CCspPushGroupHandle ccsp_push_group_create( void ) +{ + try + { + auto * group = new csp::PushGroup(); + return reinterpret_cast( group ); + } + catch( const std::exception & e ) + { + ccsp_set_error( CCSP_ERROR_RUNTIME, e.what() ); + return nullptr; + } +} + +void ccsp_push_group_destroy( CCspPushGroupHandle group ) +{ + if( !group ) return; + + try + { + auto * pushGroup = reinterpret_cast( group ); + delete pushGroup; + } + catch( ... ) + { + // Ignore errors during destroy + } +} + +} // extern "C" diff --git a/cpp/csp/engine/PushInputAdapterExtern.h b/cpp/csp/engine/PushInputAdapterExtern.h new file mode 100644 index 000000000..ce108ad2c --- /dev/null +++ b/cpp/csp/engine/PushInputAdapterExtern.h @@ -0,0 +1,40 @@ +#ifndef _IN_CSP_ENGINE_PUSH_INPUT_ADAPTER_EXTERN_H +#define _IN_CSP_ENGINE_PUSH_INPUT_ADAPTER_EXTERN_H + +/* + * C++ wrapper for external Push Input Adapters using the C ABI interface. + * + * This class wraps an adapter implemented in C (or any language with C FFI) + * and integrates it with the CSP engine's PushInputAdapter interface. + */ + +#include +#include + +namespace csp +{ + +class PushInputAdapterExtern final : public PushInputAdapter +{ +public: + PushInputAdapterExtern( Engine * engine, CspTypePtr & type, PushMode pushMode, + PushGroup * group, const CCspPushInputAdapterVTable & vtable ); + ~PushInputAdapterExtern() override; + + const char* name() const override { return "PushInputAdapterExtern"; } + + void start( DateTime startTime, DateTime endTime ) override; + void stop() override; + + // Get the vtable for access to user_data + const CCspPushInputAdapterVTable & vtable() const { return m_vtable; } + +private: + CCspPushInputAdapterVTable m_vtable; + DateTime m_startTime; + DateTime m_endTime; +}; + +} + +#endif /* _IN_CSP_ENGINE_PUSH_INPUT_ADAPTER_EXTERN_H */ diff --git a/cpp/csp/engine/StructExtern.cpp b/cpp/csp/engine/StructExtern.cpp new file mode 100644 index 000000000..3bc3496c2 --- /dev/null +++ b/cpp/csp/engine/StructExtern.cpp @@ -0,0 +1,1138 @@ +/* + * Implementation of C API for CSP Struct Access + */ + +#include +#include +#include +#include + +// ============================================================================ +// Helper Functions +// ============================================================================ + +static CCspType cspTypeToC( csp::CspType::Type t ) +{ + switch( t ) + { + case csp::CspType::Type::BOOL: return CCSP_TYPE_BOOL; + case csp::CspType::Type::INT8: return CCSP_TYPE_INT8; + case csp::CspType::Type::UINT8: return CCSP_TYPE_UINT8; + case csp::CspType::Type::INT16: return CCSP_TYPE_INT16; + case csp::CspType::Type::UINT16: return CCSP_TYPE_UINT16; + case csp::CspType::Type::INT32: return CCSP_TYPE_INT32; + case csp::CspType::Type::UINT32: return CCSP_TYPE_UINT32; + case csp::CspType::Type::INT64: return CCSP_TYPE_INT64; + case csp::CspType::Type::UINT64: return CCSP_TYPE_UINT64; + case csp::CspType::Type::DOUBLE: return CCSP_TYPE_DOUBLE; + case csp::CspType::Type::STRING: return CCSP_TYPE_STRING; + case csp::CspType::Type::DATETIME: return CCSP_TYPE_DATETIME; + case csp::CspType::Type::TIMEDELTA: return CCSP_TYPE_TIMEDELTA; + case csp::CspType::Type::DATE: return CCSP_TYPE_DATE; + case csp::CspType::Type::TIME: return CCSP_TYPE_TIME; + case csp::CspType::Type::ENUM: return CCSP_TYPE_ENUM; + case csp::CspType::Type::STRUCT: return CCSP_TYPE_STRUCT; + case csp::CspType::Type::ARRAY: return CCSP_TYPE_ARRAY; + case csp::CspType::Type::DIALECT_GENERIC: return CCSP_TYPE_DIALECT_GENERIC; + default: return CCSP_TYPE_UNKNOWN; + } +} + +// CCspStructHandle is actually a csp::StructPtr* (heap-allocated smart pointer) +// This helper extracts the raw Struct* for read operations +static inline const csp::Struct * getStructConst( CCspStructHandle s ) +{ + auto * ptr = reinterpret_cast( s ); + return ptr -> get(); +} + +// For write operations +static inline csp::Struct * getStruct( CCspStructHandle s ) +{ + auto * ptr = reinterpret_cast( s ); + return ptr -> get(); +} + +extern "C" { + +// ============================================================================ +// StructMeta Functions +// ============================================================================ + +const char * ccsp_struct_meta_name( CCspStructMetaHandle meta ) +{ + if( !meta ) return nullptr; + + auto * m = reinterpret_cast( meta ); + return m -> name().c_str(); +} + +size_t ccsp_struct_meta_field_count( CCspStructMetaHandle meta ) +{ + if( !meta ) return 0; + + auto * m = reinterpret_cast( meta ); + return m -> fieldNames().size(); +} + +CCspStructFieldHandle ccsp_struct_meta_field_by_index( CCspStructMetaHandle meta, size_t index ) +{ + if( !meta ) return nullptr; + + auto * m = reinterpret_cast( meta ); + const auto & fieldNames = m -> fieldNames(); + + if( index >= fieldNames.size() ) + return nullptr; + + const auto & fieldPtr = m -> field( fieldNames[index] ); + if( !fieldPtr ) + return nullptr; + + return reinterpret_cast( fieldPtr.get() ); +} + +CCspStructFieldHandle ccsp_struct_meta_field_by_name( CCspStructMetaHandle meta, const char * name ) +{ + if( !meta || !name ) return nullptr; + + auto * m = reinterpret_cast( meta ); + const auto & fieldPtr = m -> field( name ); + + if( !fieldPtr ) + return nullptr; + + return reinterpret_cast( fieldPtr.get() ); +} + +const char * ccsp_struct_meta_field_name_by_index( CCspStructMetaHandle meta, size_t index ) +{ + if( !meta ) return nullptr; + + auto * m = reinterpret_cast( meta ); + const auto & fieldNames = m -> fieldNames(); + + if( index >= fieldNames.size() ) + return nullptr; + + return fieldNames[index].c_str(); +} + +int ccsp_struct_meta_is_strict( CCspStructMetaHandle meta ) +{ + if( !meta ) return 0; + + auto * m = reinterpret_cast( meta ); + return m -> isStrict() ? 1 : 0; +} + +// ============================================================================ +// StructField Functions +// ============================================================================ + +const char * ccsp_struct_field_name( CCspStructFieldHandle field ) +{ + if( !field ) return nullptr; + + auto * f = reinterpret_cast( field ); + return f -> fieldname().c_str(); +} + +CCspType ccsp_struct_field_type( CCspStructFieldHandle field ) +{ + if( !field ) return CCSP_TYPE_UNKNOWN; + + auto * f = reinterpret_cast( field ); + return cspTypeToC( f -> type() -> type() ); +} + +int ccsp_struct_field_is_optional( CCspStructFieldHandle field ) +{ + if( !field ) return 0; + + auto * f = reinterpret_cast( field ); + return f -> isOptional() ? 1 : 0; +} + +// ============================================================================ +// Struct Instance Functions +// ============================================================================ + +CCspStructMetaHandle ccsp_struct_meta( CCspStructHandle s ) +{ + if( !s ) return nullptr; + + const csp::Struct * st = getStructConst( s ); + return reinterpret_cast( const_cast( st -> meta() ) ); +} + +int ccsp_struct_field_is_set( CCspStructHandle s, CCspStructFieldHandle field ) +{ + if( !s || !field ) return 0; + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + return f -> isSet( st ) ? 1 : 0; +} + +int ccsp_struct_field_is_none( CCspStructHandle s, CCspStructFieldHandle field ) +{ + if( !s || !field ) return 0; + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + return f -> isNone( st ) ? 1 : 0; +} + +// ============================================================================ +// Field Value Getters +// ============================================================================ + +// Helper macro for type-safe getters +#define IMPLEMENT_GETTER( c_type, csp_field_type, type_enum ) \ + CCspErrorCode ccsp_struct_get_##c_type( CCspStructHandle s, CCspStructFieldHandle field,\ + c_type##_t * out_value ) \ + { \ + if( !s || !field || !out_value ) \ + { \ + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); \ + return CCSP_ERROR_NULL_POINTER; \ + } \ + \ + const csp::Struct * st = getStructConst( s ); \ + auto * f = reinterpret_cast( field ); \ + \ + if( !f -> isSet( st ) ) \ + { \ + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); \ + return CCSP_ERROR_KEY_NOT_FOUND; \ + } \ + \ + if( f -> type() -> type() != csp::CspType::Type::type_enum ) \ + { \ + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); \ + return CCSP_ERROR_TYPE_MISMATCH; \ + } \ + \ + auto * tf = static_cast( f ); \ + *out_value = tf -> value( st ); \ + return CCSP_OK; \ + } + +CCspErrorCode ccsp_struct_get_bool( CCspStructHandle s, CCspStructFieldHandle field, int8_t * out_value ) +{ + if( !s || !field || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::BOOL ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + *out_value = tf -> value( st ) ? 1 : 0; + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_get_int8( CCspStructHandle s, CCspStructFieldHandle field, int8_t * out_value ) +{ + if( !s || !field || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::INT8 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + *out_value = tf -> value( st ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_get_uint8( CCspStructHandle s, CCspStructFieldHandle field, uint8_t * out_value ) +{ + if( !s || !field || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::UINT8 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + *out_value = tf -> value( st ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_get_int16( CCspStructHandle s, CCspStructFieldHandle field, int16_t * out_value ) +{ + if( !s || !field || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::INT16 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + *out_value = tf -> value( st ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_get_uint16( CCspStructHandle s, CCspStructFieldHandle field, uint16_t * out_value ) +{ + if( !s || !field || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::UINT16 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + *out_value = tf -> value( st ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_get_int32( CCspStructHandle s, CCspStructFieldHandle field, int32_t * out_value ) +{ + if( !s || !field || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::INT32 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + *out_value = tf -> value( st ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_get_uint32( CCspStructHandle s, CCspStructFieldHandle field, uint32_t * out_value ) +{ + if( !s || !field || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::UINT32 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + *out_value = tf -> value( st ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_get_int64( CCspStructHandle s, CCspStructFieldHandle field, int64_t * out_value ) +{ + if( !s || !field || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::INT64 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + *out_value = tf -> value( st ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_get_uint64( CCspStructHandle s, CCspStructFieldHandle field, uint64_t * out_value ) +{ + if( !s || !field || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::UINT64 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + *out_value = tf -> value( st ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_get_double( CCspStructHandle s, CCspStructFieldHandle field, double * out_value ) +{ + if( !s || !field || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::DOUBLE ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + *out_value = tf -> value( st ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_get_datetime( CCspStructHandle s, CCspStructFieldHandle field, CCspDateTime * out_value ) +{ + if( !s || !field || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::DATETIME ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + *out_value = tf -> value( st ).asNanoseconds(); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_get_timedelta( CCspStructHandle s, CCspStructFieldHandle field, CCspTimeDelta * out_value ) +{ + if( !s || !field || !out_value ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::TIMEDELTA ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + *out_value = tf -> value( st ).asNanoseconds(); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_get_string( CCspStructHandle s, CCspStructFieldHandle field, const char ** out_data, size_t * out_length ) +{ + if( !s || !field || !out_data || !out_length ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::STRING ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + const std::string & str = tf -> value( st ); + *out_data = str.data(); + *out_length = str.size(); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_get_enum( CCspStructHandle s, CCspStructFieldHandle field, int32_t * out_ordinal ) +{ + if( !s || !field || !out_ordinal ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::ENUM ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + *out_ordinal = static_cast( tf -> value( st ).value() ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_get_struct( CCspStructHandle s, CCspStructFieldHandle field, CCspStructHandle * out_struct ) +{ + if( !s || !field || !out_struct ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + const csp::Struct * st = getStructConst( s ); + auto * f = reinterpret_cast( field ); + + if( !f -> isSet( st ) ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not set" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + if( f -> type() -> type() != csp::CspType::Type::STRUCT ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + // StructStructField stores StructPtr, need to get raw pointer + // This is a bit tricky - we need to access the nested struct + ccsp_set_error( CCSP_ERROR_NOT_IMPLEMENTED, "nested struct access not implemented yet" ); + return CCSP_ERROR_NOT_IMPLEMENTED; +} + +// ============================================================================ +// Field Value Getters by Name +// ============================================================================ + +CCspErrorCode ccsp_struct_get_bool_by_name( CCspStructHandle s, const char * name, int8_t * out_value ) +{ + if( !s || !name ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + CCspStructMetaHandle meta = ccsp_struct_meta( s ); + CCspStructFieldHandle field = ccsp_struct_meta_field_by_name( meta, name ); + if( !field ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + return ccsp_struct_get_bool( s, field, out_value ); +} + +CCspErrorCode ccsp_struct_get_int32_by_name( CCspStructHandle s, const char * name, int32_t * out_value ) +{ + if( !s || !name ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + CCspStructMetaHandle meta = ccsp_struct_meta( s ); + CCspStructFieldHandle field = ccsp_struct_meta_field_by_name( meta, name ); + if( !field ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + return ccsp_struct_get_int32( s, field, out_value ); +} + +CCspErrorCode ccsp_struct_get_int64_by_name( CCspStructHandle s, const char * name, int64_t * out_value ) +{ + if( !s || !name ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + CCspStructMetaHandle meta = ccsp_struct_meta( s ); + CCspStructFieldHandle field = ccsp_struct_meta_field_by_name( meta, name ); + if( !field ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + return ccsp_struct_get_int64( s, field, out_value ); +} + +CCspErrorCode ccsp_struct_get_double_by_name( CCspStructHandle s, const char * name, double * out_value ) +{ + if( !s || !name ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + CCspStructMetaHandle meta = ccsp_struct_meta( s ); + CCspStructFieldHandle field = ccsp_struct_meta_field_by_name( meta, name ); + if( !field ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + return ccsp_struct_get_double( s, field, out_value ); +} + +CCspErrorCode ccsp_struct_get_datetime_by_name( CCspStructHandle s, const char * name, CCspDateTime * out_value ) +{ + if( !s || !name ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + CCspStructMetaHandle meta = ccsp_struct_meta( s ); + CCspStructFieldHandle field = ccsp_struct_meta_field_by_name( meta, name ); + if( !field ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + return ccsp_struct_get_datetime( s, field, out_value ); +} + +CCspErrorCode ccsp_struct_get_string_by_name( CCspStructHandle s, const char * name, const char ** out_data, size_t * out_length ) +{ + if( !s || !name ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + CCspStructMetaHandle meta = ccsp_struct_meta( s ); + CCspStructFieldHandle field = ccsp_struct_meta_field_by_name( meta, name ); + if( !field ) + { + ccsp_set_error( CCSP_ERROR_KEY_NOT_FOUND, "field not found" ); + return CCSP_ERROR_KEY_NOT_FOUND; + } + + return ccsp_struct_get_string( s, field, out_data, out_length ); +} + +// ============================================================================ +// Field Value Setters +// ============================================================================ + +CCspErrorCode ccsp_struct_set_bool( CCspStructHandle s, CCspStructFieldHandle field, int8_t value ) +{ + if( !s || !field ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + csp::Struct * st = getStruct( s ); + auto * f = reinterpret_cast( field ); + + if( f -> type() -> type() != csp::CspType::Type::BOOL ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + tf -> setValue( st, value != 0 ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_set_int8( CCspStructHandle s, CCspStructFieldHandle field, int8_t value ) +{ + if( !s || !field ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + csp::Struct * st = getStruct( s ); + auto * f = reinterpret_cast( field ); + + if( f -> type() -> type() != csp::CspType::Type::INT8 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + tf -> setValue( st, value ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_set_uint8( CCspStructHandle s, CCspStructFieldHandle field, uint8_t value ) +{ + if( !s || !field ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + csp::Struct * st = getStruct( s ); + auto * f = reinterpret_cast( field ); + + if( f -> type() -> type() != csp::CspType::Type::UINT8 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + tf -> setValue( st, value ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_set_int16( CCspStructHandle s, CCspStructFieldHandle field, int16_t value ) +{ + if( !s || !field ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + csp::Struct * st = getStruct( s ); + auto * f = reinterpret_cast( field ); + + if( f -> type() -> type() != csp::CspType::Type::INT16 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + tf -> setValue( st, value ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_set_uint16( CCspStructHandle s, CCspStructFieldHandle field, uint16_t value ) +{ + if( !s || !field ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + csp::Struct * st = getStruct( s ); + auto * f = reinterpret_cast( field ); + + if( f -> type() -> type() != csp::CspType::Type::UINT16 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + tf -> setValue( st, value ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_set_int32( CCspStructHandle s, CCspStructFieldHandle field, int32_t value ) +{ + if( !s || !field ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + csp::Struct * st = getStruct( s ); + auto * f = reinterpret_cast( field ); + + if( f -> type() -> type() != csp::CspType::Type::INT32 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + tf -> setValue( st, value ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_set_uint32( CCspStructHandle s, CCspStructFieldHandle field, uint32_t value ) +{ + if( !s || !field ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + csp::Struct * st = getStruct( s ); + auto * f = reinterpret_cast( field ); + + if( f -> type() -> type() != csp::CspType::Type::UINT32 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + tf -> setValue( st, value ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_set_int64( CCspStructHandle s, CCspStructFieldHandle field, int64_t value ) +{ + if( !s || !field ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + csp::Struct * st = getStruct( s ); + auto * f = reinterpret_cast( field ); + + if( f -> type() -> type() != csp::CspType::Type::INT64 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + tf -> setValue( st, value ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_set_uint64( CCspStructHandle s, CCspStructFieldHandle field, uint64_t value ) +{ + if( !s || !field ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + csp::Struct * st = getStruct( s ); + auto * f = reinterpret_cast( field ); + + if( f -> type() -> type() != csp::CspType::Type::UINT64 ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + tf -> setValue( st, value ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_set_double( CCspStructHandle s, CCspStructFieldHandle field, double value ) +{ + if( !s || !field ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + csp::Struct * st = getStruct( s ); + auto * f = reinterpret_cast( field ); + + if( f -> type() -> type() != csp::CspType::Type::DOUBLE ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + tf -> setValue( st, value ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_set_datetime( CCspStructHandle s, CCspStructFieldHandle field, CCspDateTime value ) +{ + if( !s || !field ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + csp::Struct * st = getStruct( s ); + auto * f = reinterpret_cast( field ); + + if( f -> type() -> type() != csp::CspType::Type::DATETIME ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + tf -> setValue( st, csp::DateTime::fromNanoseconds( value ) ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_set_timedelta( CCspStructHandle s, CCspStructFieldHandle field, CCspTimeDelta value ) +{ + if( !s || !field ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + csp::Struct * st = getStruct( s ); + auto * f = reinterpret_cast( field ); + + if( f -> type() -> type() != csp::CspType::Type::TIMEDELTA ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + tf -> setValue( st, csp::TimeDelta::fromNanoseconds( value ) ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_set_string( CCspStructHandle s, CCspStructFieldHandle field, + const char * data, size_t length ) +{ + if( !s || !field ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + csp::Struct * st = getStruct( s ); + auto * f = reinterpret_cast( field ); + + if( f -> type() -> type() != csp::CspType::Type::STRING ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + auto * tf = static_cast( f ); + tf -> setValue( st, std::string( data, length ) ); + return CCSP_OK; +} + +CCspErrorCode ccsp_struct_set_enum( CCspStructHandle s, CCspStructFieldHandle field, int32_t ordinal ) +{ + if( !s || !field ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); + return CCSP_ERROR_NULL_POINTER; + } + + auto * f = reinterpret_cast( field ); + + if( f -> type() -> type() != csp::CspType::Type::ENUM ) + { + ccsp_set_error( CCSP_ERROR_TYPE_MISMATCH, "field type mismatch" ); + return CCSP_ERROR_TYPE_MISMATCH; + } + + // Setting enum requires creating a CspEnum with the correct meta + // This is more complex - for now mark as not implemented + ccsp_set_error( CCSP_ERROR_NOT_IMPLEMENTED, "enum set not implemented" ); + return CCSP_ERROR_NOT_IMPLEMENTED; +} + +// ============================================================================ +// Struct Creation +// ============================================================================ + +// Note: CCspStructHandle is actually a csp::StructPtr* (pointer to smart pointer) +// This allows proper reference counting without needing access to private incref/decref + +CCspStructHandle ccsp_struct_create( CCspStructMetaHandle meta ) +{ + if( !meta ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null meta" ); + return nullptr; + } + + auto * m = reinterpret_cast( meta ); + + try + { + csp::StructPtr * ptr = new csp::StructPtr( m -> create() ); + return reinterpret_cast( ptr ); + } + catch( ... ) + { + ccsp_set_error( CCSP_ERROR_OUT_OF_MEMORY, "failed to create struct" ); + return nullptr; + } +} + +void ccsp_struct_destroy( CCspStructHandle s ) +{ + if( !s ) return; + + auto * ptr = reinterpret_cast( s ); + delete ptr; +} + +CCspStructHandle ccsp_struct_copy( CCspStructHandle s ) +{ + if( !s ) + { + ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null struct" ); + return nullptr; + } + + auto * srcPtr = reinterpret_cast( s ); + const csp::Struct * st = srcPtr -> get(); + + try + { + csp::StructPtr * copy = new csp::StructPtr( st -> meta() -> create() ); + csp::StructMeta::deepcopyFrom( st, copy -> get() ); + + return reinterpret_cast( copy ); + } + catch( ... ) + { + ccsp_set_error( CCSP_ERROR_OUT_OF_MEMORY, "failed to copy struct" ); + return nullptr; + } +} + +} // extern "C" diff --git a/cpp/csp/engine/c/AdapterManager.h b/cpp/csp/engine/c/AdapterManager.h new file mode 100644 index 000000000..c9f035796 --- /dev/null +++ b/cpp/csp/engine/c/AdapterManager.h @@ -0,0 +1,319 @@ +/* + * C API for CSP Adapter Manager + * + * Adapter managers coordinate the lifecycle of a group of related adapters. + * They handle: + * - Starting and stopping all managed adapters together + * - Simulation time slicing for sim input adapters + * - Status reporting + * - Push group coordination + * + * This header provides the C ABI for external adapter managers. + */ + +#ifndef _IN_CSP_ENGINE_C_ADAPTER_MANAGER_H +#define _IN_CSP_ENGINE_C_ADAPTER_MANAGER_H + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ============================================================================ + * Opaque Handles + * ============================================================================ */ + +typedef struct CCspAdapterManagerImpl * CCspAdapterManagerHandle; +typedef struct CCspStatusAdapterImpl * CCspStatusAdapterHandle; +typedef struct CCspManagedSimInputAdapterImpl * CCspManagedSimInputAdapterHandle; + +/* ============================================================================ + * Adapter Manager VTable + * ============================================================================ + * + * This struct defines the callbacks that external adapter managers must + * implement. The CSP engine will call these functions at appropriate times. + * + * Lifecycle: + * 1. Manager is created with ccsp_adapter_manager_extern_create() + * 2. CSP calls start() when the graph starts + * 3. For sim mode: CSP calls process_next_sim_time_slice() repeatedly + * 4. CSP calls stop() when the graph stops + * 5. CSP calls destroy() to clean up + */ + +typedef struct CCspAdapterManagerVTable { + /* User-defined data pointer passed to all callbacks */ + void * user_data; + + /* ======================================================================== + * Required Callbacks + * ======================================================================== */ + + /* + * name - Return the name of this adapter manager + * + * Used for logging and debugging. Must return a valid C string that + * remains valid for the lifetime of the adapter manager. + * + * Parameters: + * user_data - The user_data pointer from this vtable + * + * Returns: + * A null-terminated string naming this adapter manager + */ + const char * ( * name ) ( void * user_data ); + + /* + * process_next_sim_time_slice - Process simulation data for a time slice + * + * Called repeatedly during simulation mode to process data. Should: + * 1. Process all data with timestamp equal to 'time' + * 2. Return the next available timestamp, or 0 if no more data + * + * The first call is made with start_time. Subsequent calls use the + * previously returned timestamp. + * + * For realtime adapters that don't support simulation, return 0. + * + * Parameters: + * user_data - The user_data pointer from this vtable + * time - The current simulation time to process + * + * Returns: + * Next timestamp with available data, or 0 if no more data + */ + CCspDateTime ( * process_next_sim_time_slice )( void * user_data, CCspDateTime time ); + + /* + * destroy - Clean up adapter manager resources + * + * Called when the adapter manager is being destroyed. Must free all + * resources allocated in user_data. + * + * Parameters: + * user_data - The user_data pointer from this vtable + */ + void ( * destroy )( void * user_data ); + + /* ======================================================================== + * Optional Callbacks (can be NULL) + * ======================================================================== */ + + /* + * start - Called when the graph starts + * + * Initialize connections, open files, start threads, etc. + * + * Parameters: + * user_data - The user_data pointer from this vtable + * manager - Handle to this adapter manager (for creating adapters) + * start_time - Graph start time + * end_time - Graph end time + */ + void ( * start )( void * user_data, CCspAdapterManagerHandle manager, CCspDateTime start_time, CCspDateTime end_time ); + + /* + * stop - Called when the graph stops + * + * Close connections, flush buffers, stop threads, etc. + * + * Parameters: + * user_data - The user_data pointer from this vtable + */ + void ( * stop )( void * user_data ); + +} CCspAdapterManagerVTable; + +/* ============================================================================ + * Adapter Manager Creation and Lifecycle + * ============================================================================ */ + +/* + * ccsp_adapter_manager_extern_create - Create an external adapter manager + * + * Creates a new adapter manager that wraps C callbacks. + * + * Parameters: + * engine - Engine handle (from Python capsule or parent manager) + * vtable - Pointer to vtable with callbacks (copied internally) + * + * Returns: + * Handle to the new adapter manager + */ +CSP_C_API_EXPORT CCspAdapterManagerHandle ccsp_adapter_manager_extern_create( CCspEngineHandle engine, const CCspAdapterManagerVTable * vtable ); + +/* + * ccsp_adapter_manager_extern_destroy - Destroy an external adapter manager + * + * Calls the destroy callback and frees internal resources. + * + * Parameters: + * manager - Handle to the adapter manager + */ +CSP_C_API_EXPORT void ccsp_adapter_manager_extern_destroy( CCspAdapterManagerHandle manager ); + +/* ============================================================================ + * Engine and Time Access + * ============================================================================ */ + +/* + * ccsp_adapter_manager_engine - Get the engine handle + * + * Parameters: + * manager - Handle to the adapter manager + * + * Returns: + * Engine handle for use with other C API functions + */ +CSP_C_API_EXPORT CCspEngineHandle ccsp_adapter_manager_engine( CCspAdapterManagerHandle manager ); + +/* + * ccsp_adapter_manager_start_time - Get graph start time + * + * Only valid after start() has been called. + * + * Parameters: + * manager - Handle to the adapter manager + * + * Returns: + * Start time in nanoseconds since epoch + */ +CSP_C_API_EXPORT CCspDateTime ccsp_adapter_manager_start_time( CCspAdapterManagerHandle manager ); + +/* + * ccsp_adapter_manager_end_time - Get graph end time + * + * Only valid after start() has been called. + * + * Parameters: + * manager - Handle to the adapter manager + * + * Returns: + * End time in nanoseconds since epoch + */ +CSP_C_API_EXPORT CCspDateTime ccsp_adapter_manager_end_time( CCspAdapterManagerHandle manager ); + +/* ============================================================================ + * Adapter Creation from Manager + * ============================================================================ + * + * These functions create adapters that are managed by the adapter manager. + * The manager handles their lifecycle automatically. + */ + +/* + * ccsp_adapter_manager_create_output_adapter - Create a managed output adapter + * + * Creates an output adapter that will be started/stopped with the manager. + * + * Parameters: + * manager - Handle to the adapter manager + * input_type - Type of input data the adapter will receive + * vtable - Pointer to output adapter vtable (copied internally) + * + * Returns: + * Handle to the new output adapter + */ +CSP_C_API_EXPORT CCspOutputAdapterHandle ccsp_adapter_manager_create_output_adapter( CCspAdapterManagerHandle manager, CCspType input_type, const CCspOutputAdapterVTable * vtable ); + +/* + * ccsp_adapter_manager_create_push_input_adapter - Create a managed push input adapter + * + * Creates a push input adapter that will be started/stopped with the manager. + * + * Parameters: + * manager - Handle to the adapter manager + * type - Type of data the adapter will push + * push_mode - Push mode (LAST_VALUE, NON_COLLAPSING, or BURST) + * vtable - Pointer to input adapter vtable (copied internally) + * + * Returns: + * Handle to the new push input adapter + */ +CSP_C_API_EXPORT CCspPushInputAdapterHandle ccsp_adapter_manager_create_push_input_adapter( CCspAdapterManagerHandle manager, CCspType type, CCspPushMode push_mode, const CCspPushInputAdapterVTable * vtable ); + +/* ============================================================================ + * Status Reporting + * ============================================================================ + * + * Adapter managers can report status to the graph via a status adapter. + */ + +/* Status levels (matching csp.StatusLevel) */ +typedef enum { + CCSP_STATUS_LEVEL_CRITICAL = 0, + CCSP_STATUS_LEVEL_ERROR = 1, + CCSP_STATUS_LEVEL_WARNING = 2, + CCSP_STATUS_LEVEL_INFO = 3, + CCSP_STATUS_LEVEL_DEBUG = 4 +} CCspStatusLevel; + +/* + * ccsp_adapter_manager_push_status - Report status to the graph + * + * Pushes a status message that can be consumed by nodes in the graph. + * + * Parameters: + * manager - Handle to the adapter manager + * level - Status level (CRITICAL, ERROR, WARNING, INFO, DEBUG) + * err_code - Application-specific error code + * message - Status message (copied internally) + * + * Returns: + * CCSP_OK on success, error code on failure + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_adapter_manager_push_status( CCspAdapterManagerHandle manager, CCspStatusLevel level, int64_t err_code, const char * message ); + +/* ============================================================================ + * Managed Simulation Input Adapter + * ============================================================================ + * + * For adapters that need to provide data in simulation mode, use managed + * simulation input adapters. The adapter manager coordinates time slicing. + */ + +/* + * ccsp_adapter_manager_create_managed_sim_input_adapter - Create a sim input adapter + * + * Creates an input adapter for simulation mode. Use this for adapters that + * read from static data sources (files, databases). + * + * Parameters: + * manager - Handle to the adapter manager + * type - Type of data the adapter will provide + * push_mode - Push mode for handling multiple ticks + * + * Returns: + * Handle to the managed sim input adapter + */ +CSP_C_API_EXPORT CCspManagedSimInputAdapterHandle ccsp_adapter_manager_create_managed_sim_input_adapter( CCspAdapterManagerHandle manager, CCspType type, CCspPushMode push_mode ); + +/* + * ccsp_managed_sim_input_adapter_push_* - Push data from simulation source + * + * These functions push typed data into the simulation input adapter. + * Call these from process_next_sim_time_slice to provide data. + * + * Parameters: + * adapter - Handle to the managed sim input adapter + * value - Value to push + * + * Returns: + * CCSP_OK on success, error code on failure + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_managed_sim_input_adapter_push_bool( CCspManagedSimInputAdapterHandle adapter, int8_t value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_managed_sim_input_adapter_push_int64( CCspManagedSimInputAdapterHandle adapter, int64_t value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_managed_sim_input_adapter_push_double( CCspManagedSimInputAdapterHandle adapter, double value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_managed_sim_input_adapter_push_string( CCspManagedSimInputAdapterHandle adapter, const char* data, size_t length ); +CSP_C_API_EXPORT CCspErrorCode ccsp_managed_sim_input_adapter_push_datetime( CCspManagedSimInputAdapterHandle adapter, CCspDateTime value ); +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_ENGINE_C_ADAPTER_MANAGER_H */ diff --git a/cpp/csp/engine/c/CspDictionary.h b/cpp/csp/engine/c/CspDictionary.h new file mode 100644 index 000000000..3c298b42a --- /dev/null +++ b/cpp/csp/engine/c/CspDictionary.h @@ -0,0 +1,263 @@ +/* + * ABI-stable C Dictionary interface for CSP Engine + * + * Dictionary is used to pass configuration from Python to C adapters. + * This API provides read-only access to dictionary values. + * + * The Dictionary is passed to adapters via their creation functions. + * All pointers returned from getters are borrowed and valid only while + * the dictionary exists. + */ +#ifndef _IN_CSP_ENGINE_C_CSPDICTIONARY_H +#define _IN_CSP_ENGINE_C_CSPDICTIONARY_H + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ============================================================================ + * Opaque Handles + * ============================================================================ */ + +/* Handle to a CSP Dictionary (opaque) */ +typedef struct CCspDictionaryImpl * CCspDictionaryHandle; + +/* Handle to a Dictionary iterator (opaque) */ +typedef struct CCspDictIteratorImpl * CCspDictIteratorHandle; + +/* ============================================================================ + * Dictionary Type Enumeration + * ============================================================================ + * + * These correspond to the types that can be stored in a Dictionary. + */ + +typedef enum { + CCSP_DICT_TYPE_NONE = 0, /* monostate / not found */ + CCSP_DICT_TYPE_BOOL, + CCSP_DICT_TYPE_INT32, + CCSP_DICT_TYPE_UINT32, + CCSP_DICT_TYPE_INT64, + CCSP_DICT_TYPE_UINT64, + CCSP_DICT_TYPE_DOUBLE, + CCSP_DICT_TYPE_STRING, + CCSP_DICT_TYPE_DATETIME, + CCSP_DICT_TYPE_TIMEDELTA, + CCSP_DICT_TYPE_STRUCT_META, /* StructMetaPtr - opaque */ + CCSP_DICT_TYPE_DIALECT, /* DialectGenericType - opaque */ + CCSP_DICT_TYPE_DICTIONARY, /* Nested dictionary */ + CCSP_DICT_TYPE_VECTOR, /* Vector of values */ + CCSP_DICT_TYPE_DATA /* Raw data pointer */ +} CCspDictValueType; + +/* ============================================================================ + * Dictionary Basic Operations + * ============================================================================ */ + +/* + * ccsp_dictionary_exists - Check if a key exists in the dictionary + * + * Parameters: + * dict - Dictionary handle + * key - Key to look up (null-terminated string) + * + * Returns: + * 1 if the key exists, 0 if not (or if dict is NULL) + */ +CSP_C_API_EXPORT int ccsp_dictionary_exists( CCspDictionaryHandle dict, const char * key ); + +/* + * ccsp_dictionary_size - Get the number of entries in the dictionary + * + * Parameters: + * dict - Dictionary handle + * + * Returns: + * Number of entries, or 0 if dict is NULL + */ +CSP_C_API_EXPORT size_t ccsp_dictionary_size( CCspDictionaryHandle dict ); + +/* + * ccsp_dictionary_is_empty - Check if the dictionary is empty + * + * Parameters: + * dict - Dictionary handle + * + * Returns: + * 1 if empty (or NULL), 0 if has entries + */ +CSP_C_API_EXPORT int ccsp_dictionary_is_empty( CCspDictionaryHandle dict ); + +/* + * ccsp_dictionary_get_type - Get the type of a value in the dictionary + * + * Parameters: + * dict - Dictionary handle + * key - Key to look up + * + * Returns: + * CCspDictValueType indicating the type, or CCSP_DICT_TYPE_NONE if not found + */ +CSP_C_API_EXPORT CCspDictValueType ccsp_dictionary_get_type( CCspDictionaryHandle dict, const char * key ); + +/* ============================================================================ + * Type-Safe Getters (return error if type mismatch or key not found) + * ============================================================================ */ + +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_get_bool( CCspDictionaryHandle dict, const char * key, int8_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_get_int32( CCspDictionaryHandle dict, const char * key, int32_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_get_uint32( CCspDictionaryHandle dict, const char * key, uint32_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_get_int64( CCspDictionaryHandle dict, const char * key, int64_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_get_uint64( CCspDictionaryHandle dict, const char * key, uint64_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_get_double( CCspDictionaryHandle dict, const char * key, double * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_get_datetime( CCspDictionaryHandle dict, const char * key, CCspDateTime * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_get_timedelta( CCspDictionaryHandle dict, const char * key, CCspTimeDelta * out_value ); + +/* + * ccsp_dictionary_get_string - Get a string value + * + * The returned pointer is borrowed and valid only while the dictionary exists. + * + * Parameters: + * dict - Dictionary handle + * key - Key to look up + * out_data - Output: pointer to string data (NOT null-terminated in general) + * out_length - Output: length of the string + * + * Returns: + * CCSP_OK on success, error code on failure + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_get_string( CCspDictionaryHandle dict, const char * key, const char ** out_data, size_t * out_length ); + +/* + * ccsp_dictionary_get_dict - Get a nested dictionary + * + * The returned handle is borrowed and valid only while the parent dictionary exists. + * + * Parameters: + * dict - Dictionary handle + * key - Key to look up + * out_dict - Output: handle to the nested dictionary + * + * Returns: + * CCSP_OK on success, error code on failure + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_get_dict( CCspDictionaryHandle dict, const char * key, CCspDictionaryHandle * out_dict ); + +/* ============================================================================ + * Getters with Default Values (do not error on missing keys) + * ============================================================================ + * + * These return the default value if the key doesn't exist or has the wrong type. + */ + +CSP_C_API_EXPORT int8_t ccsp_dictionary_get_bool_or( CCspDictionaryHandle dict, const char * key, int8_t default_value ); +CSP_C_API_EXPORT int32_t ccsp_dictionary_get_int32_or( CCspDictionaryHandle dict, const char * key, int32_t default_value ); +CSP_C_API_EXPORT uint32_t ccsp_dictionary_get_uint32_or( CCspDictionaryHandle dict, const char * key, uint32_t default_value ); +CSP_C_API_EXPORT int64_t ccsp_dictionary_get_int64_or( CCspDictionaryHandle dict, const char * key, int64_t default_value ); +CSP_C_API_EXPORT uint64_t ccsp_dictionary_get_uint64_or( CCspDictionaryHandle dict, const char * key, uint64_t default_value ); +CSP_C_API_EXPORT double ccsp_dictionary_get_double_or( CCspDictionaryHandle dict, const char * key, double default_value ); +CSP_C_API_EXPORT CCspDateTime ccsp_dictionary_get_datetime_or( CCspDictionaryHandle dict, const char * key, CCspDateTime default_value ); +CSP_C_API_EXPORT CCspTimeDelta ccsp_dictionary_get_timedelta_or( CCspDictionaryHandle dict, const char * key, CCspTimeDelta default_value ); + +/* + * ccsp_dictionary_get_string_or - Get a string with default + * + * Note: Returns pointer to the default_value if key not found, so the + * default_value must remain valid if you use the returned pointer. + * + * Parameters: + * dict - Dictionary handle + * key - Key to look up + * default_value - Default value (null-terminated C string) + * out_length - Output: length of the returned string (can be NULL if not needed) + * + * Returns: + * Pointer to string data (borrowed from dict or default_value) + */ +CSP_C_API_EXPORT const char * ccsp_dictionary_get_string_or( CCspDictionaryHandle dict, const char * key, const char * default_value, size_t * out_length ); + +/* ============================================================================ + * Dictionary Iteration + * ============================================================================ + * + * Iterate over all key-value pairs in the dictionary. + * + * Example: + * CCspDictIteratorHandle iter = ccsp_dictionary_iter_create( dict ); + * const char * key; + * while( ccsp_dictionary_iter_next( iter, &key ) ) + * { + * CCspDictValueType type = ccsp_dictionary_iter_value_type( iter ); + * // ... use key and type ... + * } + * ccsp_dictionary_iter_destroy( iter ); + */ + +/* + * ccsp_dictionary_iter_create - Create an iterator for the dictionary + * + * Parameters: + * dict - Dictionary handle + * + * Returns: + * Iterator handle, or NULL on error + */ +CSP_C_API_EXPORT CCspDictIteratorHandle ccsp_dictionary_iter_create( CCspDictionaryHandle dict ); + +/* + * ccsp_dictionary_iter_destroy - Destroy an iterator + * + * Parameters: + * iter - Iterator handle + */ +CSP_C_API_EXPORT void ccsp_dictionary_iter_destroy( CCspDictIteratorHandle iter ); + +/* + * ccsp_dictionary_iter_next - Advance to the next entry + * + * Parameters: + * iter - Iterator handle + * out_key - Output: pointer to the key string (borrowed, valid until next call) + * + * Returns: + * 1 if there is a next entry, 0 if iteration is complete + */ +CSP_C_API_EXPORT int ccsp_dictionary_iter_next( CCspDictIteratorHandle iter, const char ** out_key ); + +/* + * ccsp_dictionary_iter_value_type - Get the type of the current value + * + * Must be called after ccsp_dictionary_iter_next returns 1. + * + * Parameters: + * iter - Iterator handle + * + * Returns: + * Type of the current value + */ +CSP_C_API_EXPORT CCspDictValueType ccsp_dictionary_iter_value_type( CCspDictIteratorHandle iter ); + +/* Type-safe value getters for current iterator position */ +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_iter_get_bool( CCspDictIteratorHandle iter, int8_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_iter_get_int32( CCspDictIteratorHandle iter, int32_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_iter_get_uint32( CCspDictIteratorHandle iter, uint32_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_iter_get_int64( CCspDictIteratorHandle iter, int64_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_iter_get_uint64( CCspDictIteratorHandle iter, uint64_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_iter_get_double( CCspDictIteratorHandle iter, double * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_iter_get_datetime( CCspDictIteratorHandle iter, CCspDateTime * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_iter_get_timedelta( CCspDictIteratorHandle iter, CCspTimeDelta * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_iter_get_string( CCspDictIteratorHandle iter, const char ** out_data, size_t * out_length ); +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_iter_get_dict( CCspDictIteratorHandle iter, CCspDictionaryHandle * out_dict ); + +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_ENGINE_C_CSPDICTIONARY_H */ diff --git a/cpp/csp/engine/c/CspError.h b/cpp/csp/engine/c/CspError.h new file mode 100644 index 000000000..c5bfb2823 --- /dev/null +++ b/cpp/csp/engine/c/CspError.h @@ -0,0 +1,66 @@ +/* + * ABI-stable C Error Handling for CSP Engine + * + * This provides consistent error reporting across the C API boundary. + * Errors are stored in thread-local storage for retrieval. + */ +#ifndef _IN_CSP_ENGINE_C_CSPERROR_H +#define _IN_CSP_ENGINE_C_CSPERROR_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Error codes */ +typedef enum { + CCSP_OK = 0, + CCSP_ERROR_NULL_POINTER, + CCSP_ERROR_TYPE_MISMATCH, + CCSP_ERROR_KEY_NOT_FOUND, + CCSP_ERROR_INVALID_ARGUMENT, + CCSP_ERROR_OUT_OF_MEMORY, + CCSP_ERROR_OUT_OF_RANGE, + CCSP_ERROR_RUNTIME, + CCSP_ERROR_VALUE, + CCSP_ERROR_NOT_IMPLEMENTED, + CCSP_ERROR_UNKNOWN +} CCspErrorCode; + +/* Get the last error code for the current thread */ +CSP_C_API_EXPORT CCspErrorCode ccsp_get_last_error( void ); + +/* Get the last error message for the current thread (may be NULL) */ +CSP_C_API_EXPORT const char * ccsp_get_last_error_message( void ); + +/* Clear the last error for the current thread */ +CSP_C_API_EXPORT void ccsp_clear_error( void ); + +/* + * Set an error (for adapter implementations). + * The message is copied internally. + */ +CSP_C_API_EXPORT void ccsp_set_error( CCspErrorCode code, const char * message ); + +/* + * Helper macro for checking and returning on error + */ +#define CCSP_RETURN_IF_ERROR( expr ) \ + do { \ + CCspErrorCode _err = ( expr ); \ + if ( _err != CCSP_OK ) return _err; \ + } while(0) + +#define CCSP_RETURN_NULL_IF_ERROR( expr ) \ + do { \ + CCspErrorCode _err = ( expr ); \ + if ( _err != CCSP_OK ) return NULL; \ + } while(0) + +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_ENGINE_C_CSPERROR_H */ diff --git a/cpp/csp/engine/c/CspExport.h b/cpp/csp/engine/c/CspExport.h new file mode 100644 index 000000000..640be067a --- /dev/null +++ b/cpp/csp/engine/c/CspExport.h @@ -0,0 +1,31 @@ +/* + * ABI-stable C API Export Macros for CSP Engine + * + * This header provides platform-independent macros for exporting C API symbols + * from shared libraries. All C API functions should be declared with + * CSP_C_API_EXPORT to ensure they are available for external adapters. + */ +#ifndef _IN_CSP_ENGINE_C_CSPEXPORT_H +#define _IN_CSP_ENGINE_C_CSPEXPORT_H + +/* + * CSP_C_API_EXPORT - Marks a function for export from the shared library + * + * On Windows: Uses __declspec(dllexport/dllimport) + * On Unix: Uses __attribute__((visibility("default"))) + * + * This ensures C API symbols are available for runtime linking by external + * adapters implemented in C, Rust, or other languages. + */ +#if defined( _WIN32 ) || defined( _WIN64 ) + #ifdef CSPIMPL_EXPORTS + #define CSP_C_API_EXPORT __declspec( dllexport ) + #else + #define CSP_C_API_EXPORT __declspec( dllimport ) + #endif +#else + /* Unix/Linux/macOS - ensure default visibility */ + #define CSP_C_API_EXPORT __attribute__( ( visibility( "default" ) ) ) +#endif + +#endif /* _IN_CSP_ENGINE_C_CSPEXPORT_H */ diff --git a/cpp/csp/engine/c/CspString.h b/cpp/csp/engine/c/CspString.h new file mode 100644 index 000000000..a13e11ebc --- /dev/null +++ b/cpp/csp/engine/c/CspString.h @@ -0,0 +1,99 @@ +/* + * ABI-stable C String Types for CSP Engine + * + * Strings crossing the ABI boundary use a length-prefixed format + * to avoid issues with null-terminated strings containing embedded nulls + * (important for binary data / bytes type). + */ +#ifndef _IN_CSP_ENGINE_C_CSPSTRING_H +#define _IN_CSP_ENGINE_C_CSPSTRING_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * String view (non-owning reference to string data) + * Use this for passing strings into CSP functions. + * The data pointer must remain valid for the duration of the call. + */ +typedef struct { + const char * data; /* Pointer to string data (may contain embedded nulls) */ + size_t length; /* Length in bytes (not including any null terminator) */ +} CCspStringView; + +/* + * Owned string (CSP owns the memory) + * Use this for strings returned from CSP functions. + * Must be freed with ccsp_string_free(). + */ +typedef struct { + char * data; /* Pointer to string data */ + size_t length; /* Length in bytes */ + size_t capacity; /* Allocated capacity (internal use) */ +} CCspString; + +/* + * Create a string view from a null-terminated C string. + * The original string must outlive the view. + */ +CCspStringView ccsp_string_view_from_cstr( const char * cstr ); + +/* + * Create a string view from data and length. + * The original data must outlive the view. + */ +CCspStringView ccsp_string_view_from_data( const char * data, size_t length ); + +/* + * Create an owned string by copying the given data. + * Returns empty string on allocation failure. + */ +CCspString ccsp_string_create( const char * data, size_t length ); + +/* + * Create an owned string from a null-terminated C string. + * Returns empty string on allocation failure. + */ +CCspString ccsp_string_create_from_cstr( const char * cstr ); + +/* + * Create an empty owned string with the given capacity. + * Useful when you need to build a string incrementally. + */ +CCspString ccsp_string_create_with_capacity( size_t capacity ); + +/* + * Free an owned string's memory. + * Safe to call on an already-freed or zero-initialized string. + */ +void ccsp_string_free( CCspString * str ); + +/* + * Get a view of an owned string. + * The view is only valid while the owned string is not modified or freed. + */ +CCspStringView ccsp_string_as_view( const CCspString * str ); + +/* + * Check if a string view is empty. + */ +static inline int ccsp_string_view_is_empty( CCspStringView view ) { + return view.length == 0 || view.data == NULL; +} + +/* + * Check if an owned string is empty. + */ +static inline int ccsp_string_is_empty( const CCspString * str ) { + return str == NULL || str -> length == 0 || str -> data == NULL; +} + +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_ENGINE_C_CSPSTRING_H */ diff --git a/cpp/csp/engine/c/CspStruct.h b/cpp/csp/engine/c/CspStruct.h new file mode 100644 index 000000000..57f2fe9c7 --- /dev/null +++ b/cpp/csp/engine/c/CspStruct.h @@ -0,0 +1,366 @@ +/* + * C API for CSP Struct Access + * + * This header provides C-compatible access to CSP Structs and StructMeta. + * Structs are CSP's structured data type with named, typed fields. + * + * Key concepts: + * - StructMeta: Type information (field names, types, offsets) + * - Struct: Instance data (actual field values) + * - StructField: Individual field access (get/set values) + * + * All pointers returned are borrowed references owned by the parent object. + * Do not free them unless explicitly documented. + */ + +#ifndef _IN_CSP_ENGINE_C_CSPSTRUCT_H +#define _IN_CSP_ENGINE_C_CSPSTRUCT_H + +#include +#include +#include +#include +#include /* For CCspStructHandle */ +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ============================================================================ + * Opaque Handle Types + * ============================================================================ */ + +/* Handle to csp::StructMeta (type information) */ +typedef void * CCspStructMetaHandle; + +/* CCspStructHandle is defined in CspValue.h as it's used for struct values */ + +/* Handle to csp::StructField (field metadata) */ +typedef void * CCspStructFieldHandle; + +/* ============================================================================ + * StructMeta Functions (Type Information) + * ============================================================================ */ + +/* + * ccsp_struct_meta_name - Get the name of a struct type + * + * Parameters: + * meta - Handle to StructMeta + * + * Returns: + * Pointer to null-terminated string (borrowed, do not free) + * NULL if meta is NULL + */ +CSP_C_API_EXPORT const char * ccsp_struct_meta_name( CCspStructMetaHandle meta ); + +/* + * ccsp_struct_meta_field_count - Get the number of fields in a struct type + * + * Parameters: + * meta - Handle to StructMeta + * + * Returns: + * Number of fields, or 0 if meta is NULL + */ +CSP_C_API_EXPORT size_t ccsp_struct_meta_field_count( CCspStructMetaHandle meta ); + +/* + * ccsp_struct_meta_field_by_index - Get field handle by index + * + * Parameters: + * meta - Handle to StructMeta + * index - Field index (0-based) + * + * Returns: + * Handle to StructField, or NULL if index out of range + */ +CSP_C_API_EXPORT CCspStructFieldHandle ccsp_struct_meta_field_by_index( CCspStructMetaHandle meta, size_t index ); + +/* + * ccsp_struct_meta_field_by_name - Get field handle by name + * + * Parameters: + * meta - Handle to StructMeta + * name - Field name (null-terminated) + * + * Returns: + * Handle to StructField, or NULL if not found + */ +CSP_C_API_EXPORT CCspStructFieldHandle ccsp_struct_meta_field_by_name( CCspStructMetaHandle meta, const char * name ); + +/* + * ccsp_struct_meta_field_name_by_index - Get field name by index + * + * Parameters: + * meta - Handle to StructMeta + * index - Field index (0-based) + * + * Returns: + * Pointer to null-terminated string (borrowed, do not free) + * NULL if index out of range + */ +CSP_C_API_EXPORT const char * ccsp_struct_meta_field_name_by_index( CCspStructMetaHandle meta, size_t index ); + +/* + * ccsp_struct_meta_is_strict - Check if struct type is strict + * + * Strict structs require all fields to be set. + * + * Parameters: + * meta - Handle to StructMeta + * + * Returns: + * 1 if strict, 0 otherwise + */ +CSP_C_API_EXPORT int ccsp_struct_meta_is_strict( CCspStructMetaHandle meta ); + +/* ============================================================================ + * StructField Functions (Field Metadata) + * ============================================================================ */ + +/* + * ccsp_struct_field_name - Get the name of a field + * + * Parameters: + * field - Handle to StructField + * + * Returns: + * Pointer to null-terminated string (borrowed, do not free) + * NULL if field is NULL + */ +CSP_C_API_EXPORT const char * ccsp_struct_field_name( CCspStructFieldHandle field ); + +/* + * ccsp_struct_field_type - Get the CSP type of a field + * + * Parameters: + * field - Handle to StructField + * + * Returns: + * CCspType enum value, or CCSP_TYPE_UNKNOWN if field is NULL + */ +CSP_C_API_EXPORT CCspType ccsp_struct_field_type( CCspStructFieldHandle field ); + +/* + * ccsp_struct_field_is_optional - Check if a field is optional + * + * Parameters: + * field - Handle to StructField + * + * Returns: + * 1 if optional, 0 otherwise + */ +CSP_C_API_EXPORT int ccsp_struct_field_is_optional( CCspStructFieldHandle field ); + +/* ============================================================================ + * Struct Instance Functions + * ============================================================================ */ + +/* + * ccsp_struct_meta - Get the StructMeta for a struct instance + * + * Parameters: + * s - Handle to Struct + * + * Returns: + * Handle to StructMeta, or NULL if s is NULL + */ +CSP_C_API_EXPORT CCspStructMetaHandle ccsp_struct_meta( CCspStructHandle s ); + +/* + * ccsp_struct_field_is_set - Check if a field is set on a struct instance + * + * Parameters: + * s - Handle to Struct + * field - Handle to StructField + * + * Returns: + * 1 if set, 0 if not set or on error + */ +CSP_C_API_EXPORT int ccsp_struct_field_is_set( CCspStructHandle s, CCspStructFieldHandle field ); + +/* + * ccsp_struct_field_is_none - Check if an optional field is explicitly None + * + * Parameters: + * s - Handle to Struct + * field - Handle to StructField + * + * Returns: + * 1 if None, 0 otherwise + */ +CSP_C_API_EXPORT int ccsp_struct_field_is_none( CCspStructHandle s, CCspStructFieldHandle field ); + +/* ============================================================================ + * Field Value Getters + * + * All getters return CCSP_OK on success or an error code. + * CCSP_ERROR_KEY_NOT_FOUND is returned if the field is not set. + * CCSP_ERROR_TYPE_MISMATCH is returned if the field type doesn't match. + * ============================================================================ */ + +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_bool( CCspStructHandle s, CCspStructFieldHandle field, int8_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_int8( CCspStructHandle s, CCspStructFieldHandle field, int8_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_uint8( CCspStructHandle s, CCspStructFieldHandle field, uint8_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_int16( CCspStructHandle s, CCspStructFieldHandle field, int16_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_uint16( CCspStructHandle s, CCspStructFieldHandle field, uint16_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_int32( CCspStructHandle s, CCspStructFieldHandle field, int32_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_uint32( CCspStructHandle s, CCspStructFieldHandle field, uint32_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_int64( CCspStructHandle s, CCspStructFieldHandle field, int64_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_uint64( CCspStructHandle s, CCspStructFieldHandle field, uint64_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_double( CCspStructHandle s, CCspStructFieldHandle field, double * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_datetime( CCspStructHandle s, CCspStructFieldHandle field, CCspDateTime * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_timedelta( CCspStructHandle s, CCspStructFieldHandle field, CCspTimeDelta * out_value ); + +/* + * ccsp_struct_get_string - Get a string field value + * + * Parameters: + * s - Handle to Struct + * field - Handle to StructField + * out_data - Output pointer to string data (borrowed, valid while struct exists) + * out_length - Output string length in bytes + * + * Returns: + * CCSP_OK on success, error code on failure + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_string( CCspStructHandle s, CCspStructFieldHandle field, const char ** out_data, size_t * out_length ); + +/* + * ccsp_struct_get_enum - Get an enum field value (as ordinal) + * + * Parameters: + * s - Handle to Struct + * field - Handle to StructField + * out_ordinal - Output enum ordinal value + * + * Returns: + * CCSP_OK on success, error code on failure + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_enum( CCspStructHandle s, CCspStructFieldHandle field, int32_t * out_ordinal ); + +/* + * ccsp_struct_get_struct - Get a nested struct field value + * + * Parameters: + * s - Handle to Struct + * field - Handle to StructField + * out_struct - Output handle to nested struct (borrowed, do not free) + * + * Returns: + * CCSP_OK on success, error code on failure + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_struct( CCspStructHandle s, CCspStructFieldHandle field, CCspStructHandle * out_struct ); + +/* ============================================================================ + * Field Value Getters by Name (Convenience) + * + * These combine field lookup and value access in one call. + * Less efficient than caching the field handle for repeated access. + * ============================================================================ */ + +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_bool_by_name( CCspStructHandle s, const char * name, int8_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_int32_by_name( CCspStructHandle s, const char * name, int32_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_int64_by_name( CCspStructHandle s, const char * name, int64_t * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_double_by_name( CCspStructHandle s, const char * name, double * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_datetime_by_name( CCspStructHandle s, const char * name, CCspDateTime * out_value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_string_by_name( CCspStructHandle s, const char * name, const char ** out_data, size_t * out_length ); + +/* ============================================================================ + * Field Value Setters + * + * All setters return CCSP_OK on success or an error code. + * CCSP_ERROR_TYPE_MISMATCH is returned if the field type doesn't match. + * ============================================================================ */ + +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_bool( CCspStructHandle s, CCspStructFieldHandle field, int8_t value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_int8( CCspStructHandle s, CCspStructFieldHandle field, int8_t value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_uint8( CCspStructHandle s, CCspStructFieldHandle field, uint8_t value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_int16( CCspStructHandle s, CCspStructFieldHandle field, int16_t value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_uint16( CCspStructHandle s, CCspStructFieldHandle field, uint16_t value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_int32( CCspStructHandle s, CCspStructFieldHandle field, int32_t value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_uint32( CCspStructHandle s, CCspStructFieldHandle field, uint32_t value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_int64( CCspStructHandle s, CCspStructFieldHandle field, int64_t value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_uint64( CCspStructHandle s, CCspStructFieldHandle field, uint64_t value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_double( CCspStructHandle s, CCspStructFieldHandle field, double value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_datetime( CCspStructHandle s, CCspStructFieldHandle field, CCspDateTime value ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_timedelta( CCspStructHandle s, CCspStructFieldHandle field, CCspTimeDelta value ); + +/* + * ccsp_struct_set_string - Set a string field value + * + * The string data is copied into the struct. + * + * Parameters: + * s - Handle to Struct + * field - Handle to StructField + * data - String data + * length - String length in bytes + * + * Returns: + * CCSP_OK on success, error code on failure + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_string( CCspStructHandle s, CCspStructFieldHandle field, const char * data, size_t length ); + +/* + * ccsp_struct_set_enum - Set an enum field value (by ordinal) + * + * Parameters: + * s - Handle to Struct + * field - Handle to StructField + * ordinal - Enum ordinal value + * + * Returns: + * CCSP_OK on success, error code on failure + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_enum( CCspStructHandle s, CCspStructFieldHandle field, int32_t ordinal ); + +/* ============================================================================ + * Struct Creation (if needed by adapters) + * ============================================================================ */ + +/* + * ccsp_struct_create - Create a new struct instance from StructMeta + * + * The returned struct must be freed with ccsp_struct_destroy when done. + * + * Parameters: + * meta - Handle to StructMeta + * + * Returns: + * Handle to new Struct, or NULL on error + */ +CSP_C_API_EXPORT CCspStructHandle ccsp_struct_create( CCspStructMetaHandle meta ); + +/* + * ccsp_struct_destroy - Destroy a struct created with ccsp_struct_create + * + * Do NOT use this on structs obtained from other sources (e.g., from + * ccsp_struct_get_struct or from input values). + * + * Parameters: + * s - Handle to Struct to destroy + */ +CSP_C_API_EXPORT void ccsp_struct_destroy( CCspStructHandle s ); + +/* + * ccsp_struct_copy - Create a deep copy of a struct + * + * The returned struct must be freed with ccsp_struct_destroy. + * + * Parameters: + * s - Handle to Struct to copy + * + * Returns: + * Handle to new Struct copy, or NULL on error + */ +CSP_C_API_EXPORT CCspStructHandle ccsp_struct_copy( CCspStructHandle s ); + +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_ENGINE_C_CSPSTRUCT_H */ diff --git a/cpp/csp/engine/c/CspTime.h b/cpp/csp/engine/c/CspTime.h new file mode 100644 index 000000000..4c5a4e3c1 --- /dev/null +++ b/cpp/csp/engine/c/CspTime.h @@ -0,0 +1,109 @@ +/* + * ABI-stable C Time Types for CSP Engine + * + * CSP uses nanosecond precision for all time types internally. + * These types map directly to the C++ csp::DateTime, csp::TimeDelta, etc. + */ +#ifndef _IN_CSP_ENGINE_C_CSPTIME_H +#define _IN_CSP_ENGINE_C_CSPTIME_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * DateTime: nanoseconds since Unix epoch (1970-01-01 00:00:00 UTC) + * Maps to csp::DateTime + */ +typedef int64_t CCspDateTime; + +/* + * TimeDelta: duration in nanoseconds (can be negative) + * Maps to csp::TimeDelta + */ +typedef int64_t CCspTimeDelta; + +/* + * Date: days since Unix epoch (1970-01-01) + * Maps to csp::Date + */ +typedef int32_t CCspDate; + +/* + * Time: nanoseconds since midnight + * Maps to csp::Time + */ +typedef int64_t CCspTime; + +/* Constants */ +#define CCSP_NANOSECONDS_PER_SECOND 1000000000LL +#define CCSP_NANOSECONDS_PER_MILLISECOND 1000000LL +#define CCSP_NANOSECONDS_PER_MICROSECOND 1000LL +#define CCSP_SECONDS_PER_DAY 86400LL + +/* Special values */ +#define CCSP_DATETIME_MIN INT64_MIN +#define CCSP_DATETIME_MAX INT64_MAX +#define CCSP_TIMEDELTA_ZERO 0LL + +/* DateTime construction */ +CCspDateTime ccsp_datetime_from_nanoseconds( int64_t nanoseconds ); +CCspDateTime ccsp_datetime_from_seconds( int64_t seconds ); +CCspDateTime ccsp_datetime_from_milliseconds( int64_t milliseconds ); +CCspDateTime ccsp_datetime_from_parts( + int year, int month, int day, + int hour, int minute, int second, + int nanosecond +); + +/* DateTime extraction */ +int64_t ccsp_datetime_to_nanoseconds( CCspDateTime dt ); +int64_t ccsp_datetime_to_seconds( CCspDateTime dt ); +int64_t ccsp_datetime_to_milliseconds( CCspDateTime dt ); +void ccsp_datetime_to_parts( + CCspDateTime dt, + int* out_year, int* out_month, int* out_day, + int* out_hour, int* out_minute, int* out_second, + int* out_nanosecond +); + +/* DateTime arithmetic */ +CCspDateTime ccsp_datetime_add( CCspDateTime dt, CCspTimeDelta delta ); +CCspTimeDelta ccsp_datetime_diff( CCspDateTime a, CCspDateTime b ); + +/* TimeDelta construction */ +CCspTimeDelta ccsp_timedelta_from_nanoseconds( int64_t nanoseconds ); +CCspTimeDelta ccsp_timedelta_from_microseconds( int64_t microseconds ); +CCspTimeDelta ccsp_timedelta_from_milliseconds( int64_t milliseconds ); +CCspTimeDelta ccsp_timedelta_from_seconds( double seconds ); +CCspTimeDelta ccsp_timedelta_from_minutes( double minutes ); +CCspTimeDelta ccsp_timedelta_from_hours( double hours ); +CCspTimeDelta ccsp_timedelta_from_days( double days ); + +/* TimeDelta extraction */ +double ccsp_timedelta_to_seconds( CCspTimeDelta td ); +int64_t ccsp_timedelta_to_nanoseconds( CCspTimeDelta td ); + +/* Date construction */ +CCspDate ccsp_date_from_days( int32_t days_since_epoch ); +CCspDate ccsp_date_from_parts( int year, int month, int day ); + +/* Date extraction */ +int32_t ccsp_date_to_days( CCspDate date ); +void ccsp_date_to_parts( CCspDate date, int * out_year, int * out_month, int * out_day ); + +/* Time (time of day) construction */ +CCspTime ccsp_time_from_nanoseconds( int64_t nanoseconds_since_midnight ); +CCspTime ccsp_time_from_parts( int hour, int minute, int second, int nanosecond ); + +/* Time extraction */ +int64_t ccsp_time_to_nanoseconds( CCspTime time ); +void ccsp_time_to_parts( CCspTime time, int * out_hour, int * out_minute, int * out_second, int * out_nanosecond ); + +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_ENGINE_C_CSPTIME_H */ diff --git a/cpp/csp/engine/c/CspType.h b/cpp/csp/engine/c/CspType.h new file mode 100644 index 000000000..110709b59 --- /dev/null +++ b/cpp/csp/engine/c/CspType.h @@ -0,0 +1,75 @@ +/* + * CSP exposes functionality through a list of types. + * These are defined in CspType.h. + * + * Basic types need to be mapped to C types in an ABI-stable manner. + * - Unknown + * - Bool + * - Int/Unit 8/16/32/64 + * - Double + * + * Complex types should be mapped into basic types where possible. + * - DateTime -> int64_t + * - TimeDelta -> int64_t + * - Date -> int32_t + * - Time -> int32_t + * - Enum -> int32_t (plus string mapping, if needed) + * - String -> char* + length + * - Struct -> opaque pointer + metadata + * + * Array types should be mapped to basic types if possible, otherwise to opaque pointer + metadata. + * - Array of Bool -> uint8_t* + length + * - Array of Int/Uint8/16/32/64 -> corresponding pointer + length + * - Array of Double -> double* + length + * - Array of DateTime/TimeDelta/Date/Time -> int64_t* / int32_t* + length + * - Array of Enum -> int32_t* + length (plus string mapping, if needed) + * - Array of String -> char** + length + * - Array of Struct -> opaque pointer* + length + metadata + * - Array of Array -> opaque pointer* + length + metadata + * + * DialectGenericType is a bit weird as we probably don't care about its internal structure. + * For example, if its a PyObject*, we can just pass it as an opaque pointer as the other side + * of the ABI boundary will need/know how to handle it based on the stability of the outer + * dialect itself. + * + * DialectGenericType -> opaque pointer + metadata + */ + + +#ifndef _IN_CSP_ENGINE_CCSPTYPE_H +#define _IN_CSP_ENGINE_CCSPTYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + + // Basic types + typedef enum { + CCSP_TYPE_UNKNOWN = 0, + CCSP_TYPE_BOOL, + CCSP_TYPE_INT8, + CCSP_TYPE_UINT8, + CCSP_TYPE_INT16, + CCSP_TYPE_UINT16, + CCSP_TYPE_INT32, + CCSP_TYPE_UINT32, + CCSP_TYPE_INT64, + CCSP_TYPE_UINT64, + CCSP_TYPE_DOUBLE, + CCSP_TYPE_STRING, + CCSP_TYPE_DATETIME, + CCSP_TYPE_TIMEDELTA, + CCSP_TYPE_DATE, + CCSP_TYPE_TIME, + CCSP_TYPE_ENUM, + CCSP_TYPE_STRUCT, + CCSP_TYPE_ARRAY, + CCSP_TYPE_DIALECT_GENERIC + } CCspType; + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cpp/csp/engine/c/CspValue.h b/cpp/csp/engine/c/CspValue.h new file mode 100644 index 000000000..f365be6a1 --- /dev/null +++ b/cpp/csp/engine/c/CspValue.h @@ -0,0 +1,220 @@ +/* + * ABI-stable C Value Container for CSP Engine + * + * CCspValue is a tagged union that can hold any CSP type. + * It is used for passing values across the C API boundary. + * + * Memory ownership: + * - For primitive types (bool, int, double, datetime, etc.), values are copied. + * - For strings, the CCspValue may own or borrow the data (check is_owned). + * - For structs and arrays, values are opaque pointers managed by CSP. + */ +#ifndef _IN_CSP_ENGINE_C_CSPVALUE_H +#define _IN_CSP_ENGINE_C_CSPVALUE_H + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declarations for opaque types */ +typedef struct CCspStructImpl* CCspStructHandle; +typedef struct CCspArrayImpl* CCspArrayHandle; +typedef struct CCspEnumMetaImpl* CCspEnumMetaHandle; +typedef struct CCspTypeInfoImpl* CCspTypeHandle; + +/* + * String value with ownership flag + */ +typedef struct { + const char * data; + size_t length; + int is_owned; /* 1 if CCspValue owns the memory, 0 if borrowed */ +} CCspStringValue; + +/* + * Array value (for CCSP_TYPE_ARRAY) + */ +typedef struct { + void * data; /* Pointer to array data */ + size_t length; /* Number of elements */ + CCspType elem_type; /* Element type */ + int is_owned; /* 1 if CCspValue owns the memory */ +} CCspArrayValue; + +/* + * Enum value (for CCSP_TYPE_ENUM) + */ +typedef struct { + int32_t ordinal; /* Enum ordinal value */ + CCspEnumMetaHandle meta; /* Enum metadata (for string conversion) */ +} CCspEnumValue; + +/* + * Dialect generic value (for CCSP_TYPE_DIALECT_GENERIC) + * This is an opaque pointer that the dialect (e.g., Python) knows how to handle. + */ +typedef struct { + void * ptr; /* Opaque pointer to dialect-specific object */ + int type_id; /* Dialect-specific type identifier */ +} CCspDialectValue; + +/* + * Main value container - a tagged union for any CSP type + */ +typedef struct { + CCspType type; /* Type tag */ + union { + int8_t bool_val; /* CCSP_TYPE_BOOL */ + int8_t int8_val; /* CCSP_TYPE_INT8 */ + uint8_t uint8_val; /* CCSP_TYPE_UINT8 */ + int16_t int16_val; /* CCSP_TYPE_INT16 */ + uint16_t uint16_val; /* CCSP_TYPE_UINT16 */ + int32_t int32_val; /* CCSP_TYPE_INT32 */ + uint32_t uint32_val; /* CCSP_TYPE_UINT32 */ + int64_t int64_val; /* CCSP_TYPE_INT64 */ + uint64_t uint64_val; /* CCSP_TYPE_UINT64 */ + double double_val; /* CCSP_TYPE_DOUBLE */ + CCspStringValue string_val; /* CCSP_TYPE_STRING */ + CCspDateTime datetime_val; /* CCSP_TYPE_DATETIME */ + CCspTimeDelta timedelta_val; /* CCSP_TYPE_TIMEDELTA */ + CCspDate date_val; /* CCSP_TYPE_DATE */ + CCspTime time_val; /* CCSP_TYPE_TIME */ + CCspEnumValue enum_val; /* CCSP_TYPE_ENUM */ + CCspStructHandle struct_val; /* CCSP_TYPE_STRUCT (opaque handle) */ + CCspArrayValue array_val; /* CCSP_TYPE_ARRAY */ + CCspDialectValue dialect_val; /* CCSP_TYPE_DIALECT_GENERIC */ + }; +} CCspValue; + +/* + * Initialize a CCspValue to unknown/invalid state + */ +void ccsp_value_init( CCspValue * value ); + +/* + * Free any owned memory in a CCspValue. + * Safe to call multiple times or on uninitialized values. + */ +void ccsp_value_free( CCspValue * value ); + +/* + * Copy a CCspValue. + * For owned strings/arrays, this creates a deep copy. + */ +CCspErrorCode ccsp_value_copy( CCspValue * dest, const CCspValue * src ); + +/* + * Move a CCspValue (transfers ownership, source becomes invalid) + */ +void ccsp_value_move( CCspValue * dest, CCspValue * src ); + +/* ============================================================================ + * Type-safe setters + * ============================================================================ */ + +void ccsp_value_set_bool( CCspValue * value, int8_t v ); +void ccsp_value_set_int8( CCspValue * value, int8_t v ); +void ccsp_value_set_uint8( CCspValue * value, uint8_t v ); +void ccsp_value_set_int16( CCspValue * value, int16_t v ); +void ccsp_value_set_uint16( CCspValue * value, uint16_t v ); +void ccsp_value_set_int32( CCspValue * value, int32_t v ); +void ccsp_value_set_uint32( CCspValue * value, uint32_t v ); +void ccsp_value_set_int64( CCspValue * value, int64_t v ); +void ccsp_value_set_uint64( CCspValue * value, uint64_t v ); +void ccsp_value_set_double( CCspValue * value, double v ); +void ccsp_value_set_datetime( CCspValue * value, CCspDateTime v ); +void ccsp_value_set_timedelta( CCspValue * value, CCspTimeDelta v ); +void ccsp_value_set_date( CCspValue * value, CCspDate v ); +void ccsp_value_set_time( CCspValue * value, CCspTime v ); + +/* + * Set string value (copies the data, CCspValue owns the copy) + */ +CCspErrorCode ccsp_value_set_string( CCspValue * value, const char * data, size_t length ); + +/* + * Set string value from null-terminated C string (copies the data) + */ +CCspErrorCode ccsp_value_set_string_cstr( CCspValue * value, const char * cstr ); + +/* + * Set string value as a view (does NOT copy, caller must ensure data outlives value) + */ +void ccsp_value_set_string_view( CCspValue * value, const char * data, size_t length ); + +/* + * Set struct value (opaque handle, CSP manages the struct) + */ +void ccsp_value_set_struct( CCspValue * value, CCspStructHandle s ); + +/* + * Set enum value + */ +void ccsp_value_set_enum( CCspValue * value, int32_t ordinal, CCspEnumMetaHandle meta ); + +/* ============================================================================ + * Type-safe getters (return error if type mismatch) + * ============================================================================ */ + +CCspErrorCode ccsp_value_get_bool( const CCspValue * value, int8_t * out ); +CCspErrorCode ccsp_value_get_int8( const CCspValue * value, int8_t * out ); +CCspErrorCode ccsp_value_get_uint8( const CCspValue * value, uint8_t * out ); +CCspErrorCode ccsp_value_get_int16( const CCspValue * value, int16_t * out ); +CCspErrorCode ccsp_value_get_uint16( const CCspValue * value, uint16_t * out ); +CCspErrorCode ccsp_value_get_int32( const CCspValue * value, int32_t * out ); +CCspErrorCode ccsp_value_get_uint32( const CCspValue * value, uint32_t * out ); +CCspErrorCode ccsp_value_get_int64( const CCspValue * value, int64_t * out ); +CCspErrorCode ccsp_value_get_uint64( const CCspValue * value, uint64_t * out ); +CCspErrorCode ccsp_value_get_double( const CCspValue * value, double * out ); +CCspErrorCode ccsp_value_get_datetime( const CCspValue * value, CCspDateTime * out ); +CCspErrorCode ccsp_value_get_timedelta( const CCspValue * value, CCspTimeDelta * out ); +CCspErrorCode ccsp_value_get_date( const CCspValue * value, CCspDate * out ); +CCspErrorCode ccsp_value_get_time( const CCspValue * value, CCspTime * out ); + +/* + * Get string value (returns pointer to internal data, do not free) + */ +CCspErrorCode ccsp_value_get_string( const CCspValue * value, const char ** out_data, size_t * out_length ); + +/* + * Get struct handle + */ +CCspErrorCode ccsp_value_get_struct( const CCspValue * value, CCspStructHandle * out ); + +/* + * Get enum value + */ +CCspErrorCode ccsp_value_get_enum( const CCspValue * value, int32_t * out_ordinal, CCspEnumMetaHandle * out_meta ); + +/* ============================================================================ + * Type checking + * ============================================================================ */ + +/* Check if value is of a specific type */ +static inline int ccsp_value_is_type( const CCspValue * value, CCspType type ) { + return value != NULL && value -> type == type; +} + +/* Check if value is valid (not UNKNOWN) */ +static inline int ccsp_value_is_valid( const CCspValue * value ) { + return value != NULL && value -> type != CCSP_TYPE_UNKNOWN; +} + +/* Check if value is a numeric type */ +int ccsp_value_is_numeric( const CCspValue * value ); + +/* Check if value is an integer type (signed or unsigned) */ +int ccsp_value_is_integer( const CCspValue * value ); + +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_ENGINE_C_CSPVALUE_H */ diff --git a/cpp/csp/engine/c/InputAdapter.h b/cpp/csp/engine/c/InputAdapter.h new file mode 100644 index 000000000..e7981a685 --- /dev/null +++ b/cpp/csp/engine/c/InputAdapter.h @@ -0,0 +1,254 @@ +/* + * ABI-stable C Input Adapter interface for CSP Engine + * + * Input adapters push data into the CSP graph from external sources. + * External adapters implement the CCspPushInputAdapterVTable callbacks. + * + * Lifecycle: + * 1. Adapter created via ccsp_push_input_adapter_extern_create() + * 2. start() called when graph starts - adapter can start its data source + * 3. Adapter calls ccsp_push_input_adapter_push_*() to push data (thread-safe) + * 4. stop() called when graph stops - adapter should stop its data source + * 5. destroy() called to clean up + * + * Thread Safety: + * The push functions are thread-safe and can be called from any thread. + * All other functions must be called from the engine thread. + */ +#ifndef _IN_CSP_ENGINE_C_INPUTADAPTER_H +#define _IN_CSP_ENGINE_C_INPUTADAPTER_H + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ============================================================================ + * Opaque handle types + * ============================================================================ */ + +/* Handle to the CSP engine (opaque) - same as in OutputAdapter.h */ +typedef struct CCspEngineImpl * CCspEngineHandle; + +/* Handle to the internal C++ PushInputAdapter wrapper (opaque) */ +typedef struct CCspPushInputAdapterImpl * CCspPushInputAdapterHandle; + +/* Handle to a push batch for grouping events (opaque) */ +typedef struct CCspPushBatchImpl * CCspPushBatchHandle; + +/* Handle to a push group for synchronizing adapters (opaque) */ +typedef struct CCspPushGroupImpl * CCspPushGroupHandle; + +/* ============================================================================ + * Push Mode + * ============================================================================ */ + +typedef enum { + /* Only the last value per engine cycle is kept (values collapse) */ + CCSP_PUSH_MODE_LAST_VALUE = 0, + + /* Every value creates a separate engine cycle (no collapsing) */ + CCSP_PUSH_MODE_NON_COLLAPSING = 1, + + /* Values are batched into a vector per engine cycle */ + CCSP_PUSH_MODE_BURST = 2 +} CCspPushMode; + +/* ============================================================================ + * Push Input Adapter Callbacks (VTable) + * + * External adapters must implement these callbacks. + * ============================================================================ */ + +typedef struct CCspPushInputAdapterVTable { + /* + * User data pointer passed to all callbacks. + * This is typically a pointer to your adapter's state structure. + */ + void * user_data; + + /* + * Called when the graph starts. + * Use this to start your data source (threads, connections, etc.) + * Optional - set to NULL if not needed. + * + * @param user_data Your adapter's state + * @param engine Handle to the engine + * @param adapter Handle to this adapter (for pushing data) + * @param start_time Graph start time + * @param end_time Graph end time + */ + void ( * start ) ( void * user_data, CCspEngineHandle engine, + CCspPushInputAdapterHandle adapter, + CCspDateTime start_time, CCspDateTime end_time ); + + /* + * Called when the graph stops. + * Use this to stop your data source. + * Optional - set to NULL if not needed. + * + * @param user_data Your adapter's state + */ + void ( * stop ) ( void * user_data ); + + /* + * Called to destroy the adapter and free resources. + * REQUIRED - must not be NULL (even if it does nothing). + * + * @param user_data Your adapter's state + */ + void ( * destroy ) ( void * user_data ); + +} CCspPushInputAdapterVTable; + +/* ============================================================================ + * Push Input Adapter Creation + * ============================================================================ */ + +/* + * Create an external push input adapter. + * + * @param engine Engine handle (from adapter manager) + * @param type Type of data this adapter will push + * @param push_mode How to handle multiple values per cycle + * @param group Optional push group for synchronization (can be NULL) + * @param vtable Callback table (copied, caller can free after this returns) + * @return Handle to the adapter, or NULL on error + */ +CSP_C_API_EXPORT CCspPushInputAdapterHandle ccsp_push_input_adapter_extern_create( CCspEngineHandle engine, CCspType type, + CCspPushMode push_mode, CCspPushGroupHandle group, + const CCspPushInputAdapterVTable * vtable ); + +/* + * Destroy an external push input adapter. + * This is typically called by CSP when the graph is destroyed. + */ +CSP_C_API_EXPORT void ccsp_push_input_adapter_extern_destroy( CCspPushInputAdapterHandle adapter ); + +/* ============================================================================ + * Push Functions (Thread-Safe) + * + * Call these from your data source thread to push data into the graph. + * ============================================================================ */ + +/* + * Push a generic value. + * The value is copied; caller retains ownership of the CCspValue. + * + * @param adapter Handle to the adapter + * @param value Value to push + * @param batch Optional batch handle (can be NULL for unbatched push) + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_value( CCspPushInputAdapterHandle adapter, const CCspValue * value, + CCspPushBatchHandle batch ); + +/* Type-specific push functions (more efficient, avoid CCspValue overhead) */ + +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_bool( CCspPushInputAdapterHandle adapter, int8_t value, + CCspPushBatchHandle batch ); + +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_int8( CCspPushInputAdapterHandle adapter, int8_t value, + CCspPushBatchHandle batch ); + +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_uint8( CCspPushInputAdapterHandle adapter, uint8_t value, + CCspPushBatchHandle batch ); + +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_int16( CCspPushInputAdapterHandle adapter, int16_t value, + CCspPushBatchHandle batch ); + +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_uint16( CCspPushInputAdapterHandle adapter, uint16_t value, + CCspPushBatchHandle batch ); + +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_int32( CCspPushInputAdapterHandle adapter, int32_t value, + CCspPushBatchHandle batch ); + +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_uint32( CCspPushInputAdapterHandle adapter, uint32_t value, + CCspPushBatchHandle batch ); + +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_int64( CCspPushInputAdapterHandle adapter, int64_t value, + CCspPushBatchHandle batch ); + +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_uint64( CCspPushInputAdapterHandle adapter, uint64_t value, + CCspPushBatchHandle batch ); + +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_double( CCspPushInputAdapterHandle adapter, double value, + CCspPushBatchHandle batch ); + +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_datetime( CCspPushInputAdapterHandle adapter, CCspDateTime value, + CCspPushBatchHandle batch ); + +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_timedelta( CCspPushInputAdapterHandle adapter, CCspTimeDelta value, + CCspPushBatchHandle batch ); + +/* + * Push a string value. + * The string data is copied internally. + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_string( CCspPushInputAdapterHandle adapter, const char * data, + size_t length, CCspPushBatchHandle batch ); + +/* + * Push a struct value. + * The struct is copied internally. + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_struct( CCspPushInputAdapterHandle adapter, + CCspStructHandle value, + CCspPushBatchHandle batch ); + +/* ============================================================================ + * Push Batch Management + * + * Batches group multiple push events to be processed atomically. + * ============================================================================ */ + +/* + * Create a push batch. + * Events added to a batch are held until the batch is flushed. + * + * @param engine Engine handle + * @return Handle to the batch, or NULL on error + */ +CSP_C_API_EXPORT CCspPushBatchHandle ccsp_push_batch_create( CCspEngineHandle engine ); + +/* + * Flush a push batch, releasing all pending events to the engine. + * The batch can be reused after flushing. + */ +CSP_C_API_EXPORT void ccsp_push_batch_flush( CCspPushBatchHandle batch ); + +/* + * Destroy a push batch. + * Any unflushed events are flushed before destruction. + */ +CSP_C_API_EXPORT void ccsp_push_batch_destroy( CCspPushBatchHandle batch ); + +/* ============================================================================ + * Push Group Management + * + * Groups synchronize multiple input adapters so they don't get out of sync. + * ============================================================================ */ + +/* + * Create a push group. + * + * @return Handle to the group, or NULL on error + */ +CSP_C_API_EXPORT CCspPushGroupHandle ccsp_push_group_create( void ); + +/* + * Destroy a push group. + */ +CSP_C_API_EXPORT void ccsp_push_group_destroy( CCspPushGroupHandle group ); + +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_ENGINE_C_INPUTADAPTER_H */ diff --git a/cpp/csp/engine/c/OutputAdapter.h b/cpp/csp/engine/c/OutputAdapter.h new file mode 100644 index 000000000..ac7b04b89 --- /dev/null +++ b/cpp/csp/engine/c/OutputAdapter.h @@ -0,0 +1,211 @@ +/* + * ABI-stable C Output Adapter interface for CSP Engine + * + * Output adapters receive data from the CSP graph and send it to external systems. + * External adapters implement the CCspOutputAdapterVTable callbacks. + * + * Lifecycle: + * 1. Adapter created via ccsp_output_adapter_extern_create() + * 2. start() called when graph starts + * 3. execute() called each time input has new value + * 4. stop() called when graph stops + * 5. destroy() called to clean up + */ +#ifndef _IN_CSP_ENGINE_C_OUTPUTADAPTER_H +#define _IN_CSP_ENGINE_C_OUTPUTADAPTER_H + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ============================================================================ + * Opaque handle types + * ============================================================================ */ + +/* Handle to the CSP engine (opaque) */ +typedef struct CCspEngineImpl * CCspEngineHandle; + +/* Handle to a time series input (opaque) */ +typedef struct CCspInputImpl * CCspInputHandle; + +/* Handle to the internal C++ OutputAdapter wrapper (opaque) */ +typedef struct CCspOutputAdapterImpl * CCspOutputAdapterHandle; + +/* ============================================================================ + * Input access functions (for use in execute callback) + * ============================================================================ */ + +/* + * Check if the input is valid (has ticked at least once) + */ +CSP_C_API_EXPORT int ccsp_input_is_valid( CCspInputHandle input ); + +/* + * Get the number of ticks available in the input buffer + */ +CSP_C_API_EXPORT int32_t ccsp_input_num_ticks( CCspInputHandle input ); + +/* + * Get the type of the input + */ +CSP_C_API_EXPORT CCspType ccsp_input_get_type( CCspInputHandle input ); + +/* + * Get the last value from the input. + * The value is borrowed - do not free it, and do not use after execute() returns. + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_input_get_last_value( CCspInputHandle input, CCspValue* out_value ); + +/* + * Get value at a specific index in the buffer. + * Index 0 is the most recent, negative indices go back in history. + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_input_get_value_at( CCspInputHandle input, int32_t index, CCspValue * out_value ); + +/* + * Get the timestamp of the value at a specific index. + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_input_get_time_at( CCspInputHandle input, int32_t index, CCspDateTime * out_time ); + +/* + * Get the timestamp of the last value. + */ +CSP_C_API_EXPORT CCspDateTime ccsp_input_get_last_time( CCspInputHandle input ); + +/* ============================================================================ + * Engine access functions + * ============================================================================ */ + +/* + * Get current engine time + */ +CSP_C_API_EXPORT CCspDateTime ccsp_engine_now( CCspEngineHandle engine ); + +/* + * Get current cycle count + */ +CSP_C_API_EXPORT uint64_t ccsp_engine_cycle_count( CCspEngineHandle engine ); + +/* ============================================================================ + * Output Adapter Callbacks (VTable) + * + * External adapters must implement these callbacks. + * ============================================================================ */ + +typedef struct CCspOutputAdapterVTable { + /* + * User data pointer passed to all callbacks. + * This is typically a pointer to your adapter's state structure. + */ + void * user_data; + + /* + * Called when the graph starts. + * Optional - set to NULL if not needed. + * + * @param user_data Your adapter's state + * @param engine Handle to the engine (for accessing time, etc.) + * @param start_time Graph start time + * @param end_time Graph end time + */ + void ( * start ) ( void * user_data, CCspEngineHandle engine, + CCspDateTime start_time, CCspDateTime end_time ); + + /* + * Called when the graph stops. + * Optional - set to NULL if not needed. + * Use this to flush buffers, close connections, etc. + * + * @param user_data Your adapter's state + */ + void ( * stop ) ( void * user_data ); + + /* + * Called each time the input has a new value. + * REQUIRED - must not be NULL. + * + * @param user_data Your adapter's state + * @param engine Handle to the engine + * @param input Handle to the input time series + */ + void ( * execute ) ( void * user_data, CCspEngineHandle engine, CCspInputHandle input ); + + /* + * Called to destroy the adapter and free resources. + * REQUIRED - must not be NULL (even if it does nothing). + * + * @param user_data Your adapter's state + */ + void ( * destroy ) ( void * user_data ); + +} CCspOutputAdapterVTable; + +/* ============================================================================ + * Output Adapter Creation + * ============================================================================ */ + +/* + * Create an external output adapter. + * + * @param engine Engine handle (from adapter manager) + * @param input_type Type of the input time series (CCSP_TYPE_*) + * @param vtable Callback table (copied, caller can free after this returns) + * @return Handle to the adapter, or NULL on error + * + * Note: The returned handle should be returned to Python via capsule, + * which will then be registered with the CSP graph. + */ +CSP_C_API_EXPORT CCspOutputAdapterHandle ccsp_output_adapter_extern_create( CCspEngineHandle engine, CCspType input_type, + const CCspOutputAdapterVTable * vtable ); + +/* + * Destroy an external output adapter. + * This is typically called by CSP when the graph is destroyed. + * The destroy callback in the vtable will be invoked. + */ +CSP_C_API_EXPORT void ccsp_output_adapter_extern_destroy( CCspOutputAdapterHandle adapter ); + +/* ============================================================================ + * Convenience functions for common output patterns + * ============================================================================ */ + +/* + * Get last value as string (convenience for string outputs). + * Returns borrowed pointer valid until next execute() call. + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_input_get_last_string( CCspInputHandle input, const char ** out_data, + size_t * out_length ); + +/* + * Get last value as int64 (convenience for int outputs) + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_input_get_last_int64( CCspInputHandle input, int64_t * out_value ); + +/* + * Get last value as double (convenience for double outputs) + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_input_get_last_double( CCspInputHandle input, double * out_value ); + +/* + * Get last value as bool (convenience for bool outputs) + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_input_get_last_bool( CCspInputHandle input, int8_t * out_value ); + +/* + * Get last value as datetime (convenience for datetime outputs) + */ +CSP_C_API_EXPORT CCspErrorCode ccsp_input_get_last_datetime( CCspInputHandle input, CCspDateTime * out_value ); + +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_ENGINE_C_OUTPUTADAPTER_H */ diff --git a/cpp/csp/python/CMakeLists.txt b/cpp/csp/python/CMakeLists.txt index 195b99332..df334e9d2 100644 --- a/cpp/csp/python/CMakeLists.txt +++ b/cpp/csp/python/CMakeLists.txt @@ -50,6 +50,13 @@ set(CSPIMPL_PUBLIC_HEADERS PyStructToJson.h PyStructToDict.h) +# C API headers (ABI-stable interface) +set(CSPIMPL_C_API_HEADERS + c/PyInputAdapter.h + c/PyOutputAdapter.h + c/PyAdapterManager.h +) + add_library(cspimpl SHARED cspimpl.cpp Conversions.cpp @@ -76,6 +83,7 @@ add_library(cspimpl SHARED PyPullInputAdapter.cpp PyPushInputAdapter.cpp PyPushPullInputAdapter.cpp + PyCApiAdapters.cpp PyManagedSimInputAdapter.cpp PyTimerAdapter.cpp PyConstants.cpp @@ -83,7 +91,18 @@ add_library(cspimpl SHARED set_target_properties(cspimpl PROPERTIES PUBLIC_HEADER "${CSPIMPL_PUBLIC_HEADERS}") -target_link_libraries(cspimpl csptypesimpl csp_core csp_engine ) +# Link with csp_engine static library, forcing all C API symbols to be included +# so they can be used by external adapters at runtime +if(APPLE) + # macOS: Use -force_load to include all symbols from csp_engine + target_link_libraries(cspimpl csptypesimpl csp_core "-Wl,-force_load,$") +elseif(UNIX) + # Linux: Use --whole-archive to include all symbols from csp_engine + target_link_libraries(cspimpl csptypesimpl csp_core -Wl,--whole-archive csp_engine -Wl,--no-whole-archive) +else() + # Windows: Standard linking + target_link_libraries(cspimpl csptypesimpl csp_core csp_engine) +endif() target_compile_definitions(cspimpl PUBLIC NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION) target_compile_definitions(cspimpl PRIVATE CSPIMPL_EXPORTS=1) @@ -118,8 +137,16 @@ target_link_libraries(cspnpstatsimpl cspimpl npstatsimpl) target_include_directories(npstatsimpl PRIVATE ${NUMPY_INCLUDE_DIRS}) target_include_directories(cspnpstatsimpl PRIVATE ${NUMPY_INCLUDE_DIRS}) +## C API Meta Library +add_library(cspcapiimpl INTERFACE) +set_target_properties(cspcapiimpl PROPERTIES PUBLIC_HEADER "${CSPIMPL_C_API_HEADERS}") + install(TARGETS csptypesimpl cspimpl cspbaselibimpl cspbasketlibimpl cspmathimpl cspstatsimpl csptestlibimpl cspnpstatsimpl PUBLIC_HEADER DESTINATION include/csp/python RUNTIME DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR} LIBRARY DESTINATION lib/ ) + +install(TARGETS cspcapiimpl + PUBLIC_HEADER DESTINATION include/csp/python/c +) diff --git a/cpp/csp/python/PyCApiAdapters.cpp b/cpp/csp/python/PyCApiAdapters.cpp new file mode 100644 index 000000000..372db833d --- /dev/null +++ b/cpp/csp/python/PyCApiAdapters.cpp @@ -0,0 +1,172 @@ +/* + * Bridge for C API capsule-based adapters + * + * This module provides the glue between C API capsules (containing VTables) + * and CSP's wiring layer (which expects InputAdapter * / OutputAdapter *). + * + * Usage from Python: + * - _c_api_push_input_adapter: Takes a capsule containing CCspPushInputAdapterVTable + * - _c_api_output_adapter: Takes a capsule containing CCspOutputAdapterVTable + * - _c_api_adapter_manager_bridge: Takes a capsule containing CCspAdapterManagerVTable + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace csp::python +{ + +/* Capsule names - must match the C API headers */ +static const char * const CSP_C_INPUT_ADAPTER_CAPSULE_NAME = "csp.c.InputAdapterCapsule"; +static const char * const CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME = "csp.c.OutputAdapterCapsule"; +static const char * const CSP_C_ADAPTER_MANAGER_CAPSULE_NAME = "csp.c.AdapterManagerCapsule"; + +/* + * Create a PushInputAdapterExtern from a C API capsule. + * + * Expected args tuple: (capsule, push_group_or_none, additional_args...) + * The capsule contains a CCspPushInputAdapterVTable. + */ +static InputAdapter * c_api_push_input_adapter_creator( csp::AdapterManager * manager, PyEngine * pyengine, PyObject * pyType, + PushMode pushMode, PyObject * args ) +{ + PyObject * capsule = nullptr; + PyObject * pyPushGroup = nullptr; + + /* Parse args - expect (capsule, push_group_or_none) */ + if( !PyArg_ParseTuple( args, "OO", &capsule, &pyPushGroup ) ) + CSP_THROW( PythonPassthrough, "" ); + + /* Validate and extract the VTable from the capsule */ + if( !PyCapsule_IsValid( capsule, CSP_C_INPUT_ADAPTER_CAPSULE_NAME ) ) + CSP_THROW( TypeError, "Expected input adapter capsule (csp.c.InputAdapterCapsule)" ); + + CCspPushInputAdapterVTable * vtable = static_cast( PyCapsule_GetPointer( capsule, CSP_C_INPUT_ADAPTER_CAPSULE_NAME ) ); + + if( !vtable ) + CSP_THROW( ValueError, "Failed to extract VTable from capsule" ); + + /* Get push group if provided */ + csp::PushGroup * pushGroup = nullptr; + if( pyPushGroup != Py_None ) + { + pushGroup = static_cast( PyCapsule_GetPointer( pyPushGroup, nullptr ) ); + if( !pushGroup ) + { + PyErr_Clear(); + CSP_THROW( TypeError, "Expected PushGroup instance for push group" ); + } + } + + /* Get the CSP type from Python type */ + auto & cspType = pyTypeAsCspType( pyType ); + + /* Create the adapter using createOwnedObject */ + auto * adapter = pyengine->engine()->createOwnedObject( cspType, pushMode, pushGroup, *vtable ); + + /* Transfer ownership of the VTable to the adapter by clearing the capsule's destructor. + * This prevents double-free since PushInputAdapterExtern will call destroy in its destructor. */ + PyCapsule_SetDestructor( capsule, nullptr ); + + return adapter; +} + +/* + * Create an OutputAdapterExtern from a C API capsule. + * + * Expected args tuple: (capsule, additional_args...) + * The capsule contains a CCspOutputAdapterVTable. + */ +static OutputAdapter * c_api_output_adapter_creator( + csp::AdapterManager * manager, + PyEngine * pyengine, + PyObject * args ) +{ + PyObject * capsule = nullptr; + PyObject * pyInputType = nullptr; + + /* Parse args - expect (input_type, capsule) */ + if( !PyArg_ParseTuple( args, "OO", &pyInputType, &capsule ) ) + CSP_THROW( PythonPassthrough, "" ); + + /* Validate and extract the VTable from the capsule */ + if( !PyCapsule_IsValid( capsule, CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME ) ) + CSP_THROW( TypeError, "Expected output adapter capsule (csp.c.OutputAdapterCapsule)" ); + + CCspOutputAdapterVTable * vtable = static_cast( PyCapsule_GetPointer( capsule, CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME ) ); + + if( !vtable ) + CSP_THROW( ValueError, "Failed to extract VTable from capsule" ); + + /* Get the CSP type from Python type */ + auto & cspType = pyTypeAsCspType( pyInputType ); + + /* Create the adapter using createOwnedObject */ + auto * adapter = pyengine->engine()->createOwnedObject( cspType, *vtable ); + + /* Transfer ownership of the VTable to the adapter by clearing the capsule's destructor. + * This prevents double-free since OutputAdapterExtern will call destroy in its destructor. */ + PyCapsule_SetDestructor( capsule, nullptr ); + + return adapter; +} + +REGISTER_INPUT_ADAPTER( _c_api_push_input_adapter, c_api_push_input_adapter_creator ); +REGISTER_OUTPUT_ADAPTER( _c_api_output_adapter, c_api_output_adapter_creator ); + +/* + * Bridge a C API adapter manager capsule to CSP's internal format. + * + * Python signature: _c_api_adapter_manager_bridge(engine, c_api_capsule) -> csp_capsule + * + * This takes a capsule containing CCspAdapterManagerVTable (from C API) and + * returns a capsule containing AdapterManagerExtern* that CSP's wiring layer expects. + */ +static PyObject * c_api_adapter_manager_bridge( PyObject * /* self */, PyObject * args ) +{ + CSP_BEGIN_METHOD; + + PyEngine * pyEngine = nullptr; + PyObject * cApiCapsule = nullptr; + + if( !PyArg_ParseTuple( args, "O!O", + &PyEngine::PyType, &pyEngine, + &cApiCapsule ) ) + CSP_THROW( PythonPassthrough, "" ); + + /* Validate the C API capsule */ + if( !PyCapsule_IsValid( cApiCapsule, CSP_C_ADAPTER_MANAGER_CAPSULE_NAME ) ) + CSP_THROW( TypeError, "Expected C API adapter manager capsule (csp.c.AdapterManagerCapsule)" ); + + CCspAdapterManagerVTable * vtable = static_cast( PyCapsule_GetPointer( cApiCapsule, CSP_C_ADAPTER_MANAGER_CAPSULE_NAME ) ); + + if( !vtable ) + CSP_THROW( ValueError, "Failed to extract VTable from C API capsule" ); + + /* Create the AdapterManagerExtern owned by the engine */ + auto * adapterMgr = pyEngine->engine()->createOwnedObject( *vtable ); + + /* Transfer ownership of the VTable to the adapter manager by clearing the capsule's destructor. + * This prevents double-free since AdapterManagerExtern will call destroy in its destructor. */ + PyCapsule_SetDestructor( cApiCapsule, nullptr ); + + /* Return a capsule with the name CSP's wiring layer expects */ + return PyCapsule_New( adapterMgr, "adapterMgr", nullptr ); + + CSP_RETURN_NULL; +} + +REGISTER_MODULE_METHOD( "_c_api_adapter_manager_bridge", c_api_adapter_manager_bridge, METH_VARARGS, + "Bridge a C API adapter manager capsule to CSP format" ); + +} diff --git a/cpp/csp/python/PyInputAdapterWrapper.h b/cpp/csp/python/PyInputAdapterWrapper.h index 686cc75d9..bdfb44e45 100644 --- a/cpp/csp/python/PyInputAdapterWrapper.h +++ b/cpp/csp/python/PyInputAdapterWrapper.h @@ -36,7 +36,6 @@ class CSPIMPL_EXPORT PyInputAdapterWrapper : public PyObject #define REGISTER_INPUT_ADAPTER( METHOD_NAME, CREATOR_FUNC ) \ static PyObject * create_##METHOD_NAME( PyObject *, PyObject * args ) { return PyInputAdapterWrapper::createAdapter( CREATOR_FUNC, args ); } \ REGISTER_MODULE_METHOD( #METHOD_NAME, create_##METHOD_NAME, METH_VARARGS, #METHOD_NAME ); - } #endif diff --git a/cpp/csp/python/PyStruct.cpp b/cpp/csp/python/PyStruct.cpp index 0f2d09eb1..5ce67f297 100644 --- a/cpp/csp/python/PyStruct.cpp +++ b/cpp/csp/python/PyStruct.cpp @@ -107,7 +107,7 @@ static PyObject * PyStructMeta_new( PyTypeObject *subtype, PyObject *args, PyObj CSP_THROW( KeyError, "StructMeta missing __metadata__" ); PyObject * optFields = PyDict_GetItemString( dict, "__optional_fields__" ); - if( !optFields ) + if( !optFields ) CSP_THROW( TypeError, "StructMeta missing __optional_fields__" ); StructMeta::Fields fields; @@ -126,7 +126,7 @@ static PyObject * PyStructMeta_new( PyTypeObject *subtype, PyObject *args, PyObj if( rv == -1 ) CSP_THROW( PythonPassthrough, "" ); optional = rv; - + if( !PyType_Check( type ) && !PyList_Check( type ) ) CSP_THROW( TypeError, "Struct metadata for key " << keystr << " expected a type, got " << PyObjectPtr::incref( type ) ); @@ -200,7 +200,7 @@ static PyObject * PyStructMeta_new( PyTypeObject *subtype, PyObject *args, PyObj | | PyStruct -------------------------- */ - + PyObject * strict_enabled = PyDict_GetItemString( dict, "__strict_enabled__" ); if( !strict_enabled ) CSP_THROW( KeyError, "StructMeta missing __strict_enabled__" ); @@ -369,6 +369,25 @@ static PyMethodDef PyStructMeta_methods[] = { {NULL} }; +// Getter for _struct_meta_capsule - exposes StructMeta* as a capsule for C API usage +static PyObject * PyStructMeta_get_struct_meta_capsule( PyStructMeta * m, void * /* closure */ ) +{ + if( !m -> structMeta ) + { + Py_RETURN_NONE; + } + + // Return a capsule containing the raw StructMeta pointer + // The capsule does NOT own the pointer - the PyStructMeta owns it via shared_ptr + return PyCapsule_New( m -> structMeta.get(), nullptr, nullptr ); +} + +static PyGetSetDef PyStructMeta_getset[] = { + { "_struct_meta_capsule", (getter)PyStructMeta_get_struct_meta_capsule, nullptr, + "Get a capsule containing the StructMeta pointer for C API usage", nullptr }, + { NULL } +}; + PyTypeObject PyStructMeta::PyType = { PyVarObject_HEAD_INIT(nullptr, 0) "_cspimpl.PyStructMeta", /* tp_name */ @@ -400,7 +419,7 @@ PyTypeObject PyStructMeta::PyType = { 0, /* tp_iternext */ PyStructMeta_methods, /* tp_methods */ 0, /* tp_members */ - 0, /* tp_getset */ + PyStructMeta_getset, /* tp_getset */ &PyType_Type, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ @@ -431,7 +450,7 @@ PyObject * getattr_( const StructField * field, const Struct * struct_ ) PyObject * getarrayattr_( const StructField * field, const PyStruct * pystruct ) { assert( field -> type() -> type() == CspType::Type::ARRAY ); - + const CspArrayType * arrayType = static_cast( field -> type().get() ); PyObject *v = ArraySubTypeSwitch::invoke( arrayType -> elemType(), [ field, pystruct ]( auto tag ) { @@ -454,7 +473,7 @@ PyObject * PyStruct::getattr( PyObject * attr ) { if( field -> isNone( struct_.get() ) ) return Py_None; - + //For efficiency reasons we set err here rather than rely on exceptions, since this //can get called pretty regularly, ie getattr( s, "f", default ) or hasattr checks //we also pass the attribute as the exception for efficiency, expensive to format a nice error here @@ -502,7 +521,7 @@ void PyStruct::setattr( Struct * s, PyObject * attr, PyObject * value ) typedField -> clearValue( struct_ ); } } ); - + } catch( const TypeError & err ) { diff --git a/cpp/csp/python/c/PyAdapterManager.h b/cpp/csp/python/c/PyAdapterManager.h new file mode 100644 index 000000000..0647a668e --- /dev/null +++ b/cpp/csp/python/c/PyAdapterManager.h @@ -0,0 +1,115 @@ +/* + * Python integration helpers for C ABI adapter managers + * + * This provides utilities for creating Python capsules that wrap C adapter managers. + */ +#ifndef _IN_CSP_PYTHON_C_PYADAPTERMANAGER_H +#define _IN_CSP_PYTHON_C_PYADAPTERMANAGER_H + +#include "Python.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Capsule name for C adapter managers */ +static const char * const CSP_C_ADAPTER_MANAGER_CAPSULE_NAME = "csp.c.AdapterManagerCapsule"; + +/* + * Create a Python capsule wrapping an adapter manager VTable. + * The VTable is copied internally. + * + * @param vtable Pointer to the VTable structure + * @return Python capsule object, or NULL on error + */ +static inline PyObject * ccsp_py_create_adapter_manager_capsule( const CCspAdapterManagerVTable * vtable ) +{ + if( !vtable ) + { + PyErr_SetString( PyExc_ValueError, "vtable cannot be NULL" ); + return NULL; + } + + /* Allocate a copy of the vtable */ + CCspAdapterManagerVTable * vtable_copy = ( CCspAdapterManagerVTable * ) malloc( sizeof( CCspAdapterManagerVTable ) ); + + if( !vtable_copy ) + { + PyErr_NoMemory(); + return NULL; + } + + *vtable_copy = *vtable; + + return PyCapsule_New( vtable_copy, CSP_C_ADAPTER_MANAGER_CAPSULE_NAME, NULL ); +} + +/* + * Extract a VTable from a Python capsule. + * + * @param capsule Python capsule object + * @return Pointer to VTable, or NULL on error + */ +static inline CCspAdapterManagerVTable * ccsp_py_get_adapter_manager_vtable( PyObject * capsule ) +{ + if( !PyCapsule_IsValid( capsule, CSP_C_ADAPTER_MANAGER_CAPSULE_NAME ) ) + { + PyErr_SetString( PyExc_TypeError, "expected adapter manager capsule" ); + return NULL; + } + return ( CCspAdapterManagerVTable * ) PyCapsule_GetPointer( capsule, CSP_C_ADAPTER_MANAGER_CAPSULE_NAME ); +} + +/* + * Destructor for adapter manager capsules that cleans up the VTable copy + * and calls the destroy callback. + */ +static inline void ccsp_py_adapter_manager_capsule_destructor( PyObject * capsule ) +{ + CCspAdapterManagerVTable * vtable = ( CCspAdapterManagerVTable * ) PyCapsule_GetPointer( capsule, CSP_C_ADAPTER_MANAGER_CAPSULE_NAME ); + if( vtable ) + { + if( vtable -> destroy ) + { + vtable -> destroy( vtable -> user_data ); + } + free( vtable ); + } +} + +/* + * Create a Python capsule wrapping an adapter manager VTable with automatic cleanup. + * The VTable is copied internally and the destroy callback will be invoked + * when the capsule is garbage collected. + * + * @param vtable Pointer to the VTable structure + * @return Python capsule object, or NULL on error + */ +static inline PyObject * ccsp_py_create_adapter_manager_capsule_owned( const CCspAdapterManagerVTable * vtable ) +{ + if( !vtable ) + { + PyErr_SetString( PyExc_ValueError, "vtable cannot be NULL" ); + return NULL; + } + + /* Allocate a copy of the vtable */ + CCspAdapterManagerVTable * vtable_copy = ( CCspAdapterManagerVTable * ) malloc( sizeof( CCspAdapterManagerVTable ) ); + if( !vtable_copy ) + { + PyErr_NoMemory(); + return NULL; + } + *vtable_copy = *vtable; + + return PyCapsule_New( vtable_copy, CSP_C_ADAPTER_MANAGER_CAPSULE_NAME, ccsp_py_adapter_manager_capsule_destructor ); +} + +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_PYTHON_C_PYADAPTERMANAGER_H */ diff --git a/cpp/csp/python/c/PyInputAdapter.h b/cpp/csp/python/c/PyInputAdapter.h new file mode 100644 index 000000000..5227e655b --- /dev/null +++ b/cpp/csp/python/c/PyInputAdapter.h @@ -0,0 +1,113 @@ +/* + * Python integration helpers for C ABI push input adapters + * + * This provides utilities for creating Python capsules that wrap C input adapters. + */ +#ifndef _IN_CSP_PYTHON_C_PYINPUTADAPTER_H +#define _IN_CSP_PYTHON_C_PYINPUTADAPTER_H + +#include "Python.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Capsule name for C push input adapters */ +static const char * const CSP_C_INPUT_ADAPTER_CAPSULE_NAME = "csp.c.InputAdapterCapsule"; + +/* + * Create a Python capsule wrapping a push input adapter VTable. + * The VTable is copied internally. + * + * @param vtable Pointer to the VTable structure + * @return Python capsule object, or NULL on error + */ +static inline PyObject * ccsp_py_create_input_adapter_capsule( const CCspPushInputAdapterVTable * vtable ) +{ + if( !vtable ) + { + PyErr_SetString( PyExc_ValueError, "vtable cannot be NULL" ); + return NULL; + } + + /* Allocate a copy of the vtable */ + CCspPushInputAdapterVTable * vtable_copy = ( CCspPushInputAdapterVTable * ) malloc( sizeof( CCspPushInputAdapterVTable ) ); + if( !vtable_copy ) + { + PyErr_NoMemory(); + return NULL; + } + *vtable_copy = *vtable; + + return PyCapsule_New( vtable_copy, CSP_C_INPUT_ADAPTER_CAPSULE_NAME, NULL ); +} + +/* + * Extract a VTable from a Python capsule. + * + * @param capsule Python capsule object + * @return Pointer to VTable, or NULL on error + */ +static inline CCspPushInputAdapterVTable * ccsp_py_get_input_adapter_vtable( PyObject * capsule ) +{ + if( !PyCapsule_IsValid( capsule, CSP_C_INPUT_ADAPTER_CAPSULE_NAME ) ) + { + PyErr_SetString( PyExc_TypeError, "expected input adapter capsule" ); + return NULL; + } + return ( CCspPushInputAdapterVTable * ) PyCapsule_GetPointer( capsule, CSP_C_INPUT_ADAPTER_CAPSULE_NAME ); +} + +/* + * Destructor for input adapter capsules that cleans up the VTable copy + * and calls the destroy callback. + */ +static inline void ccsp_py_input_adapter_capsule_destructor( PyObject * capsule ) +{ + CCspPushInputAdapterVTable * vtable = ( CCspPushInputAdapterVTable * ) PyCapsule_GetPointer( capsule, CSP_C_INPUT_ADAPTER_CAPSULE_NAME ); + if( vtable ) + { + if( vtable -> destroy ) + { + vtable -> destroy( vtable -> user_data ); + } + free( vtable ); + } +} + +/* + * Create a Python capsule wrapping a push input adapter VTable with automatic cleanup. + * The VTable is copied internally and the destroy callback will be invoked + * when the capsule is garbage collected. + * + * @param vtable Pointer to the VTable structure + * @return Python capsule object, or NULL on error + */ +static inline PyObject * ccsp_py_create_input_adapter_capsule_owned( const CCspPushInputAdapterVTable * vtable ) +{ + if( !vtable ) + { + PyErr_SetString( PyExc_ValueError, "vtable cannot be NULL" ); + return NULL; + } + + /* Allocate a copy of the vtable */ + CCspPushInputAdapterVTable * vtable_copy = ( CCspPushInputAdapterVTable * ) malloc( sizeof( CCspPushInputAdapterVTable ) ); + if( !vtable_copy ) + { + PyErr_NoMemory(); + return NULL; + } + *vtable_copy = *vtable; + + return PyCapsule_New( vtable_copy, CSP_C_INPUT_ADAPTER_CAPSULE_NAME, ccsp_py_input_adapter_capsule_destructor ); +} + +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_PYTHON_C_PYINPUTADAPTER_H */ diff --git a/cpp/csp/python/c/PyOutputAdapter.h b/cpp/csp/python/c/PyOutputAdapter.h new file mode 100644 index 000000000..b339a396e --- /dev/null +++ b/cpp/csp/python/c/PyOutputAdapter.h @@ -0,0 +1,115 @@ +/* + * Python integration helpers for C ABI output adapters + * + * This provides utilities for creating Python capsules that wrap C output adapters. + */ +#ifndef _IN_CSP_PYTHON_C_PYOUTPUTADAPTER_H +#define _IN_CSP_PYTHON_C_PYOUTPUTADAPTER_H + +#include "Python.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Capsule name for C output adapters */ +static const char * const CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME = "csp.c.OutputAdapterCapsule"; + +/* + * Create a Python capsule wrapping an output adapter VTable. + * The VTable is copied internally. + * + * @param vtable Pointer to the VTable structure + * @return Python capsule object, or NULL on error + */ +static inline PyObject * ccsp_py_create_output_adapter_capsule( const CCspOutputAdapterVTable * vtable ) +{ + if( !vtable ) + { + PyErr_SetString( PyExc_ValueError, "vtable cannot be NULL" ); + return NULL; + } + + /* Allocate a copy of the vtable */ + CCspOutputAdapterVTable * vtable_copy = ( CCspOutputAdapterVTable * ) malloc( sizeof( CCspOutputAdapterVTable ) ); + if( !vtable_copy ) + { + PyErr_NoMemory(); + return NULL; + } + *vtable_copy = *vtable; + + return PyCapsule_New( vtable_copy, CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME, NULL ); +} + +/* + * Extract a VTable from a Python capsule. + * + * @param capsule Python capsule object + * @return Pointer to VTable, or NULL on error + */ +static inline CCspOutputAdapterVTable * ccsp_py_get_output_adapter_vtable( PyObject * capsule ) +{ + if( !PyCapsule_IsValid( capsule, CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME ) ) + { + PyErr_SetString( PyExc_TypeError, "expected output adapter capsule" ); + return NULL; + } + return ( CCspOutputAdapterVTable * ) PyCapsule_GetPointer( capsule, CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME ); +} + +/* + * Destructor for output adapter capsules that cleans up the VTable copy + * and calls the destroy callback. + */ +static inline void ccsp_py_output_adapter_capsule_destructor( PyObject * capsule ) +{ + CCspOutputAdapterVTable * vtable = ( CCspOutputAdapterVTable * ) PyCapsule_GetPointer( capsule, CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME ); + if( vtable ) + { + if( vtable -> destroy ) + { + vtable -> destroy( vtable -> user_data ); + } + free( vtable ); + } +} + +/* + * Create a Python capsule wrapping an output adapter VTable with automatic cleanup. + * The VTable is copied internally and the destroy callback will be invoked + * when the capsule is garbage collected. + * + * @param vtable Pointer to the VTable structure + * @return Python capsule object, or NULL on error + */ +static inline PyObject * ccsp_py_create_output_adapter_capsule_owned( const CCspOutputAdapterVTable * vtable ) +{ + if( !vtable ) + { + PyErr_SetString( PyExc_ValueError, "vtable cannot be NULL" ); + return NULL; + } + + /* Allocate a copy of the vtable */ + CCspOutputAdapterVTable * vtable_copy = ( CCspOutputAdapterVTable * ) malloc( sizeof( CCspOutputAdapterVTable ) ); + if( !vtable_copy ) + { + PyErr_NoMemory(); + return NULL; + } + *vtable_copy = *vtable; + + return PyCapsule_New( vtable_copy, CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME, ccsp_py_output_adapter_capsule_destructor ); +} + +#ifdef __cplusplus +} +} +#endif + +#endif /* _IN_CSP_PYTHON_C_PYOUTPUTADAPTER_H */ + diff --git a/docs/wiki/_Sidebar.md b/docs/wiki/_Sidebar.md index 821b24bc1..a252efa17 100644 --- a/docs/wiki/_Sidebar.md +++ b/docs/wiki/_Sidebar.md @@ -34,6 +34,7 @@ Notes for editors: - [Write Historical Input Adapters](Write-Historical-Input-Adapters) - [Write Realtime Input Adapters](Write-Realtime-Input-Adapters) - [Write Output Adapters](Write-Output-Adapters) + - [Write C API Adapters](Write-C-API-Adapters) - [Profile CSP Code](Profile-CSP-Code) **References** @@ -51,6 +52,7 @@ Notes for editors: - [`csp.Struct` API](csp.Struct-API) - [`csp.dynamic` API](csp.dynamic-API) - [`csp.profiler` API](csp.profiler-API) + - [C APIs](C-APIs) - [Glossary of Terms](Glossary) - [Examples](https://github.com/Point72/csp/tree/main/examples) diff --git a/docs/wiki/api-references/C-APIs.md b/docs/wiki/api-references/C-APIs.md new file mode 100644 index 000000000..251fcb2b0 --- /dev/null +++ b/docs/wiki/api-references/C-APIs.md @@ -0,0 +1,1320 @@ +# CSP C API Reference + +This document provides a complete reference for the CSP C API, which allows adapters to be written in C (or any language with C FFI) and compiled separately from CSP. + +## Table of Contents + +- [Overview](#overview) +- [Header Files](#header-files) +- [Error Handling](#error-handling) +- [Type System](#type-system) +- [Time Types](#time-types) +- [String Types](#string-types) +- [Value Container](#value-container) +- [Dictionary Access](#dictionary-access) +- [Output Adapters](#output-adapters) +- [Push Input Adapters](#push-input-adapters) +- [Engine Access](#engine-access) +- [Input Access](#input-access) +- [Adapter Managers](#adapter-managers) +- [Python Bridge Functions](#python-bridge-functions) + +______________________________________________________________________ + +## Overview + +The C API provides ABI-stable interfaces for: + +- **Output Adapters**: Receive data from the CSP graph and send to external systems +- **Push Input Adapters**: Push data from external sources into the CSP graph +- **Adapter Managers**: Coordinate multiple adapters sharing resources and lifecycle + +All types are designed to be stable across CSP versions, using: + +- Fixed-size integer types (`int32_t`, `int64_t`, etc.) +- Opaque handle pointers for internal CSP objects +- VTable pattern for polymorphism + +### Including the Headers + +```c +#include // Error handling +#include // Type enumeration +#include // Time types +#include // String types +#include // Value container +#include // Dictionary access +#include // Output adapter API +#include // Input adapter API +#include // Adapter manager API +``` + +______________________________________________________________________ + +## Error Handling + +**Header:** `` + +### Error Codes + +```c +typedef enum { + CCSP_OK = 0, // Success + CCSP_ERROR_NULL_POINTER, // NULL argument provided + CCSP_ERROR_TYPE_MISMATCH, // Type does not match expected + CCSP_ERROR_KEY_NOT_FOUND, // Dictionary key not found + CCSP_ERROR_INVALID_ARGUMENT, // Invalid argument value + CCSP_ERROR_OUT_OF_MEMORY, // Memory allocation failed + CCSP_ERROR_OUT_OF_RANGE, // Index out of range + CCSP_ERROR_RUNTIME, // Runtime error + CCSP_ERROR_VALUE, // Value error + CCSP_ERROR_NOT_IMPLEMENTED, // Feature not implemented + CCSP_ERROR_UNKNOWN // Unknown error +} CCspErrorCode; +``` + +### Functions + +#### `ccsp_get_last_error` + +```c +CCspErrorCode ccsp_get_last_error(void); +``` + +Returns the last error code for the current thread. + +#### `ccsp_get_last_error_message` + +```c +const char* ccsp_get_last_error_message(void); +``` + +Returns the last error message for the current thread, or `NULL` if no message is set. + +#### `ccsp_clear_error` + +```c +void ccsp_clear_error(void); +``` + +Clears the last error for the current thread. + +#### `ccsp_set_error` + +```c +void ccsp_set_error(CCspErrorCode code, const char* message); +``` + +Sets an error code and message. The message is copied internally. + +### Macros + +```c +// Return from function if expression returns error +#define CCSP_RETURN_IF_ERROR(expr) + +// Return NULL from function if expression returns error +#define CCSP_RETURN_NULL_IF_ERROR(expr) +``` + +______________________________________________________________________ + +## Type System + +**Header:** `` + +### Type Enumeration + +```c +typedef enum { + CCSP_TYPE_UNKNOWN = 0, + CCSP_TYPE_BOOL, + CCSP_TYPE_INT8, + CCSP_TYPE_UINT8, + CCSP_TYPE_INT16, + CCSP_TYPE_UINT16, + CCSP_TYPE_INT32, + CCSP_TYPE_UINT32, + CCSP_TYPE_INT64, + CCSP_TYPE_UINT64, + CCSP_TYPE_DOUBLE, + CCSP_TYPE_STRING, + CCSP_TYPE_DATETIME, + CCSP_TYPE_TIMEDELTA, + CCSP_TYPE_DATE, + CCSP_TYPE_TIME, + CCSP_TYPE_ENUM, + CCSP_TYPE_STRUCT, + CCSP_TYPE_ARRAY, + CCSP_TYPE_DIALECT_GENERIC +} CCspType; +``` + +### Type Mapping + +| CCspType | C Type | CSP C++ Type | +| --------------------------- | ------------------------ | -------------------- | +| `CCSP_TYPE_BOOL` | `int8_t` | `bool` | +| `CCSP_TYPE_INT8` | `int8_t` | `int8_t` | +| `CCSP_TYPE_UINT8` | `uint8_t` | `uint8_t` | +| `CCSP_TYPE_INT16` | `int16_t` | `int16_t` | +| `CCSP_TYPE_UINT16` | `uint16_t` | `uint16_t` | +| `CCSP_TYPE_INT32` | `int32_t` | `int32_t` | +| `CCSP_TYPE_UINT32` | `uint32_t` | `uint32_t` | +| `CCSP_TYPE_INT64` | `int64_t` | `int64_t` | +| `CCSP_TYPE_UINT64` | `uint64_t` | `uint64_t` | +| `CCSP_TYPE_DOUBLE` | `double` | `double` | +| `CCSP_TYPE_STRING` | `const char*` + `size_t` | `std::string` | +| `CCSP_TYPE_DATETIME` | `int64_t` (nanoseconds) | `csp::DateTime` | +| `CCSP_TYPE_TIMEDELTA` | `int64_t` (nanoseconds) | `csp::TimeDelta` | +| `CCSP_TYPE_DATE` | `int32_t` (days) | `csp::Date` | +| `CCSP_TYPE_TIME` | `int64_t` (nanoseconds) | `csp::Time` | +| `CCSP_TYPE_ENUM` | `int32_t` | `csp::CspEnum` | +| `CCSP_TYPE_STRUCT` | opaque handle | `csp::StructPtr` | +| `CCSP_TYPE_ARRAY` | opaque handle | `std::vector` | +| `CCSP_TYPE_DIALECT_GENERIC` | opaque pointer | `PyObject*` (Python) | + +______________________________________________________________________ + +## Time Types + +**Header:** `` + +### Type Definitions + +```c +typedef int64_t CCspDateTime; // Nanoseconds since Unix epoch +typedef int64_t CCspTimeDelta; // Duration in nanoseconds +typedef int32_t CCspDate; // Days since Unix epoch +typedef int64_t CCspTime; // Nanoseconds since midnight +``` + +### Constants + +```c +#define CCSP_NANOSECONDS_PER_SECOND 1000000000LL +#define CCSP_NANOSECONDS_PER_MILLISECOND 1000000LL +#define CCSP_NANOSECONDS_PER_MICROSECOND 1000LL +#define CCSP_SECONDS_PER_DAY 86400LL + +#define CCSP_DATETIME_MIN INT64_MIN +#define CCSP_DATETIME_MAX INT64_MAX +#define CCSP_TIMEDELTA_ZERO 0LL +``` + +### DateTime Functions + +```c +// Construction +CCspDateTime ccsp_datetime_from_nanoseconds(int64_t nanoseconds); +CCspDateTime ccsp_datetime_from_seconds(int64_t seconds); +CCspDateTime ccsp_datetime_from_milliseconds(int64_t milliseconds); +CCspDateTime ccsp_datetime_from_parts( + int year, int month, int day, + int hour, int minute, int second, int nanosecond); + +// Extraction +int64_t ccsp_datetime_to_nanoseconds(CCspDateTime dt); +int64_t ccsp_datetime_to_seconds(CCspDateTime dt); +int64_t ccsp_datetime_to_milliseconds(CCspDateTime dt); +void ccsp_datetime_to_parts(CCspDateTime dt, + int* year, int* month, int* day, + int* hour, int* minute, int* second, int* nanosecond); + +// Arithmetic +CCspDateTime ccsp_datetime_add(CCspDateTime dt, CCspTimeDelta delta); +CCspTimeDelta ccsp_datetime_diff(CCspDateTime a, CCspDateTime b); +``` + +### TimeDelta Functions + +```c +// Construction +CCspTimeDelta ccsp_timedelta_from_nanoseconds(int64_t nanoseconds); +CCspTimeDelta ccsp_timedelta_from_microseconds(int64_t microseconds); +CCspTimeDelta ccsp_timedelta_from_milliseconds(int64_t milliseconds); +CCspTimeDelta ccsp_timedelta_from_seconds(double seconds); +CCspTimeDelta ccsp_timedelta_from_minutes(double minutes); +CCspTimeDelta ccsp_timedelta_from_hours(double hours); +CCspTimeDelta ccsp_timedelta_from_days(double days); + +// Extraction +double ccsp_timedelta_to_seconds(CCspTimeDelta td); +int64_t ccsp_timedelta_to_nanoseconds(CCspTimeDelta td); +``` + +### Date Functions + +```c +CCspDate ccsp_date_from_days(int32_t days_since_epoch); +CCspDate ccsp_date_from_parts(int year, int month, int day); +int32_t ccsp_date_to_days(CCspDate date); +void ccsp_date_to_parts(CCspDate date, int* year, int* month, int* day); +``` + +### Time Functions + +```c +CCspTime ccsp_time_from_nanoseconds(int64_t nanoseconds_since_midnight); +CCspTime ccsp_time_from_parts(int hour, int minute, int second, int nanosecond); +int64_t ccsp_time_to_nanoseconds(CCspTime time); +void ccsp_time_to_parts(CCspTime time, int* hour, int* minute, int* second, int* nanosecond); +``` + +______________________________________________________________________ + +## String Types + +**Header:** `` + +### String View (Non-owning) + +```c +typedef struct { + const char* data; // Pointer to string data + size_t length; // Length in bytes +} CCspStringView; +``` + +Use string views for passing strings into CSP functions. The data must remain valid for the duration of the call. + +### Owned String + +```c +typedef struct { + char* data; // Pointer to string data + size_t length; // Length in bytes + size_t capacity; // Allocated capacity +} CCspString; +``` + +Owned strings are returned from CSP functions and must be freed with `ccsp_string_free()`. + +### Functions + +```c +// Create views +CCspStringView ccsp_string_view_from_cstr(const char* cstr); +CCspStringView ccsp_string_view_from_data(const char* data, size_t length); + +// Create owned strings +CCspString ccsp_string_create(const char* data, size_t length); +CCspString ccsp_string_create_from_cstr(const char* cstr); +CCspString ccsp_string_create_with_capacity(size_t capacity); + +// Free owned string +void ccsp_string_free(CCspString* str); + +// Convert owned to view +CCspStringView ccsp_string_as_view(const CCspString* str); + +// Check empty +static inline int ccsp_string_view_is_empty(CCspStringView view); +static inline int ccsp_string_is_empty(const CCspString* str); +``` + +______________________________________________________________________ + +## Value Container + +**Header:** `` + +### CCspValue Structure + +`CCspValue` is a tagged union that can hold any CSP type: + +```c +typedef struct { + CCspType type; // Type tag + union { + int8_t bool_val; + int8_t int8_val; + uint8_t uint8_val; + int16_t int16_val; + uint16_t uint16_val; + int32_t int32_val; + uint32_t uint32_val; + int64_t int64_val; + uint64_t uint64_val; + double double_val; + CCspStringValue string_val; + CCspDateTime datetime_val; + CCspTimeDelta timedelta_val; + CCspDate date_val; + CCspTime time_val; + CCspEnumValue enum_val; + CCspStructHandle struct_val; + CCspArrayValue array_val; + CCspDialectValue dialect_val; + }; +} CCspValue; +``` + +### Lifecycle Functions + +```c +void ccsp_value_init(CCspValue* value); // Initialize to unknown +void ccsp_value_free(CCspValue* value); // Free owned memory +CCspErrorCode ccsp_value_copy(CCspValue* dest, const CCspValue* src); +void ccsp_value_move(CCspValue* dest, CCspValue* src); +``` + +### Setters + +```c +void ccsp_value_set_bool(CCspValue* value, int8_t v); +void ccsp_value_set_int8(CCspValue* value, int8_t v); +void ccsp_value_set_uint8(CCspValue* value, uint8_t v); +void ccsp_value_set_int16(CCspValue* value, int16_t v); +void ccsp_value_set_uint16(CCspValue* value, uint16_t v); +void ccsp_value_set_int32(CCspValue* value, int32_t v); +void ccsp_value_set_uint32(CCspValue* value, uint32_t v); +void ccsp_value_set_int64(CCspValue* value, int64_t v); +void ccsp_value_set_uint64(CCspValue* value, uint64_t v); +void ccsp_value_set_double(CCspValue* value, double v); +void ccsp_value_set_datetime(CCspValue* value, CCspDateTime v); +void ccsp_value_set_timedelta(CCspValue* value, CCspTimeDelta v); +void ccsp_value_set_date(CCspValue* value, CCspDate v); +void ccsp_value_set_time(CCspValue* value, CCspTime v); + +// String (copies data) +CCspErrorCode ccsp_value_set_string(CCspValue* value, const char* data, size_t length); +CCspErrorCode ccsp_value_set_string_cstr(CCspValue* value, const char* cstr); + +// String view (does not copy) +void ccsp_value_set_string_view(CCspValue* value, const char* data, size_t length); + +// Struct and enum +void ccsp_value_set_struct(CCspValue* value, CCspStructHandle s); +void ccsp_value_set_enum(CCspValue* value, int32_t ordinal, CCspEnumMetaHandle meta); +``` + +### Getters + +All getters return `CCspErrorCode` and write to an output parameter: + +```c +CCspErrorCode ccsp_value_get_bool(const CCspValue* value, int8_t* out); +CCspErrorCode ccsp_value_get_int8(const CCspValue* value, int8_t* out); +CCspErrorCode ccsp_value_get_uint8(const CCspValue* value, uint8_t* out); +CCspErrorCode ccsp_value_get_int16(const CCspValue* value, int16_t* out); +CCspErrorCode ccsp_value_get_uint16(const CCspValue* value, uint16_t* out); +CCspErrorCode ccsp_value_get_int32(const CCspValue* value, int32_t* out); +CCspErrorCode ccsp_value_get_uint32(const CCspValue* value, uint32_t* out); +CCspErrorCode ccsp_value_get_int64(const CCspValue* value, int64_t* out); +CCspErrorCode ccsp_value_get_uint64(const CCspValue* value, uint64_t* out); +CCspErrorCode ccsp_value_get_double(const CCspValue* value, double* out); +CCspErrorCode ccsp_value_get_datetime(const CCspValue* value, CCspDateTime* out); +CCspErrorCode ccsp_value_get_timedelta(const CCspValue* value, CCspTimeDelta* out); +CCspErrorCode ccsp_value_get_date(const CCspValue* value, CCspDate* out); +CCspErrorCode ccsp_value_get_time(const CCspValue* value, CCspTime* out); +CCspErrorCode ccsp_value_get_string(const CCspValue* value, const char** out_data, size_t* out_length); +CCspErrorCode ccsp_value_get_struct(const CCspValue* value, CCspStructHandle* out); +CCspErrorCode ccsp_value_get_enum(const CCspValue* value, int32_t* out_ordinal, CCspEnumMetaHandle* out_meta); +``` + +### Type Checking + +```c +static inline int ccsp_value_is_type(const CCspValue* value, CCspType type); +static inline int ccsp_value_is_valid(const CCspValue* value); +int ccsp_value_is_numeric(const CCspValue* value); +int ccsp_value_is_integer(const CCspValue* value); +``` + +______________________________________________________________________ + +## Dictionary Access + +**Header:** `` + +The Dictionary API provides read-only access to CSP dictionaries, which are used to pass configuration from Python to C adapters. + +### Handles + +```c +typedef void* CCspDictionaryHandle; // Opaque handle to csp::Dictionary +typedef void* CCspDictIteratorHandle; // Opaque iterator handle +``` + +### Value Types + +```c +typedef enum { + CCSP_DICT_TYPE_NONE = 0, // std::monostate + CCSP_DICT_TYPE_BOOL, // bool + CCSP_DICT_TYPE_INT32, // int32_t + CCSP_DICT_TYPE_UINT32, // uint32_t + CCSP_DICT_TYPE_INT64, // int64_t + CCSP_DICT_TYPE_UINT64, // uint64_t + CCSP_DICT_TYPE_DOUBLE, // double + CCSP_DICT_TYPE_STRING, // std::string + CCSP_DICT_TYPE_DATETIME, // csp::DateTime + CCSP_DICT_TYPE_TIMEDELTA, // csp::TimeDelta + CCSP_DICT_TYPE_STRUCT_META, // StructMetaPtr + CCSP_DICT_TYPE_DIALECT, // DialectGenericType + CCSP_DICT_TYPE_DICTIONARY, // nested Dictionary + CCSP_DICT_TYPE_VECTOR, // Vector + CCSP_DICT_TYPE_DATA // shared_ptr +} CCspDictValueType; +``` + +### Basic Operations + +```c +// Get number of entries +size_t ccsp_dictionary_size(CCspDictionaryHandle dict); + +// Check if key exists +int ccsp_dictionary_has_key(CCspDictionaryHandle dict, const char* key); + +// Get value type for key +CCspDictValueType ccsp_dictionary_get_type(CCspDictionaryHandle dict, const char* key); +``` + +### Type-Safe Getters + +These functions return `CCSP_OK` on success or an error code on failure: + +```c +CCspErrorCode ccsp_dictionary_get_bool(CCspDictionaryHandle dict, const char* key, int8_t* out_value); +CCspErrorCode ccsp_dictionary_get_int32(CCspDictionaryHandle dict, const char* key, int32_t* out_value); +CCspErrorCode ccsp_dictionary_get_uint32(CCspDictionaryHandle dict, const char* key, uint32_t* out_value); +CCspErrorCode ccsp_dictionary_get_int64(CCspDictionaryHandle dict, const char* key, int64_t* out_value); +CCspErrorCode ccsp_dictionary_get_uint64(CCspDictionaryHandle dict, const char* key, uint64_t* out_value); +CCspErrorCode ccsp_dictionary_get_double(CCspDictionaryHandle dict, const char* key, double* out_value); +CCspErrorCode ccsp_dictionary_get_datetime(CCspDictionaryHandle dict, const char* key, CCspDateTime* out_value); +CCspErrorCode ccsp_dictionary_get_timedelta(CCspDictionaryHandle dict, const char* key, CCspTimeDelta* out_value); + +// Returns pointer to internal string data (valid while dictionary exists) +CCspErrorCode ccsp_dictionary_get_string(CCspDictionaryHandle dict, const char* key, + const char** out_data, size_t* out_length); + +// Returns handle to nested dictionary (must NOT be freed - owned by parent) +CCspErrorCode ccsp_dictionary_get_dict(CCspDictionaryHandle dict, const char* key, + CCspDictionaryHandle* out_dict); +``` + +### Getters with Default Values + +These functions return the value directly, or the provided default if key is missing: + +```c +int8_t ccsp_dictionary_get_bool_or(CCspDictionaryHandle dict, const char* key, int8_t default_value); +int32_t ccsp_dictionary_get_int32_or(CCspDictionaryHandle dict, const char* key, int32_t default_value); +uint32_t ccsp_dictionary_get_uint32_or(CCspDictionaryHandle dict, const char* key, uint32_t default_value); +int64_t ccsp_dictionary_get_int64_or(CCspDictionaryHandle dict, const char* key, int64_t default_value); +uint64_t ccsp_dictionary_get_uint64_or(CCspDictionaryHandle dict, const char* key, uint64_t default_value); +double ccsp_dictionary_get_double_or(CCspDictionaryHandle dict, const char* key, double default_value); +CCspDateTime ccsp_dictionary_get_datetime_or(CCspDictionaryHandle dict, const char* key, CCspDateTime default_value); +CCspTimeDelta ccsp_dictionary_get_timedelta_or(CCspDictionaryHandle dict, const char* key, CCspTimeDelta default_value); + +// Returns pointer to string or default_value if key missing (NULL-safe) +const char* ccsp_dictionary_get_string_or(CCspDictionaryHandle dict, const char* key, const char* default_value); +``` + +### Iteration + +Iterate over all key-value pairs in a dictionary: + +```c +// Create iterator +CCspDictIteratorHandle ccsp_dictionary_iter_create(CCspDictionaryHandle dict); + +// Destroy iterator +void ccsp_dictionary_iter_destroy(CCspDictIteratorHandle iter); + +// Advance and get next key (returns 0 when exhausted) +int ccsp_dictionary_iter_next(CCspDictIteratorHandle iter, const char** out_key); + +// Get type of current value +CCspDictValueType ccsp_dictionary_iter_value_type(CCspDictIteratorHandle iter); + +// Get current value (type-specific) +CCspErrorCode ccsp_dictionary_iter_get_bool(CCspDictIteratorHandle iter, int8_t* out_value); +CCspErrorCode ccsp_dictionary_iter_get_int32(CCspDictIteratorHandle iter, int32_t* out_value); +// ... similar for other types +CCspErrorCode ccsp_dictionary_iter_get_dict(CCspDictIteratorHandle iter, CCspDictionaryHandle* out_dict); +``` + +### Example Usage + +```c +void process_config(CCspDictionaryHandle config) +{ + // Direct access with defaults + int32_t port = ccsp_dictionary_get_int32_or(config, "port", 9092); + const char* host = ccsp_dictionary_get_string_or(config, "host", "localhost"); + + // Type-safe access with error handling + const char* topic_data = NULL; + size_t topic_len = 0; + if (ccsp_dictionary_get_string(config, "topic", &topic_data, &topic_len) != CCSP_OK) { + // Handle missing required field + } + + // Iteration + CCspDictIteratorHandle iter = ccsp_dictionary_iter_create(config); + const char* key; + while (ccsp_dictionary_iter_next(iter, &key)) { + CCspDictValueType type = ccsp_dictionary_iter_value_type(iter); + printf("Key: %s, Type: %d\n", key, type); + } + ccsp_dictionary_iter_destroy(iter); +} +``` + +______________________________________________________________________ + +## Struct Access + +**Header:** `` + +The Struct API provides access to CSP Structs, which are structured data types with named, typed fields. This API allows C code to: + +- Inspect struct type metadata (fields, types) +- Read field values from struct instances +- Write field values to struct instances +- Create and copy struct instances + +### Opaque Handles + +```c +typedef void* CCspStructMetaHandle; // Handle to csp::StructMeta (type info) +typedef CCspStructImpl* CCspStructHandle; // Handle to csp::Struct (instance) +typedef void* CCspStructFieldHandle; // Handle to csp::StructField (field info) +``` + +### StructMeta Functions (Type Information) + +```c +// Get struct type name +const char* ccsp_struct_meta_name(CCspStructMetaHandle meta); + +// Get number of fields +size_t ccsp_struct_meta_field_count(CCspStructMetaHandle meta); + +// Get field by index (0-based) +CCspStructFieldHandle ccsp_struct_meta_field_by_index(CCspStructMetaHandle meta, size_t index); + +// Get field by name +CCspStructFieldHandle ccsp_struct_meta_field_by_name(CCspStructMetaHandle meta, const char* name); + +// Get field name by index +const char* ccsp_struct_meta_field_name_by_index(CCspStructMetaHandle meta, size_t index); + +// Check if struct type is strict +int ccsp_struct_meta_is_strict(CCspStructMetaHandle meta); +``` + +### StructField Functions (Field Metadata) + +```c +// Get field name +const char* ccsp_struct_field_name(CCspStructFieldHandle field); + +// Get field type (returns CCspType enum) +CCspType ccsp_struct_field_type(CCspStructFieldHandle field); + +// Check if field is optional +int ccsp_struct_field_is_optional(CCspStructFieldHandle field); +``` + +### Struct Instance Functions + +```c +// Get struct's meta (type info) +CCspStructMetaHandle ccsp_struct_meta(CCspStructHandle s); + +// Check if field is set +int ccsp_struct_field_is_set(CCspStructHandle s, CCspStructFieldHandle field); + +// Check if field is None +int ccsp_struct_field_is_none(CCspStructHandle s, CCspStructFieldHandle field); +``` + +### Field Value Getters + +All getters return `CCSP_OK` on success or an error code on failure: + +```c +// Primitive types +CCspErrorCode ccsp_struct_get_bool(CCspStructHandle s, CCspStructFieldHandle field, int8_t* out_value); +CCspErrorCode ccsp_struct_get_int8(CCspStructHandle s, CCspStructFieldHandle field, int8_t* out_value); +CCspErrorCode ccsp_struct_get_uint8(CCspStructHandle s, CCspStructFieldHandle field, uint8_t* out_value); +CCspErrorCode ccsp_struct_get_int16(CCspStructHandle s, CCspStructFieldHandle field, int16_t* out_value); +CCspErrorCode ccsp_struct_get_uint16(CCspStructHandle s, CCspStructFieldHandle field, uint16_t* out_value); +CCspErrorCode ccsp_struct_get_int32(CCspStructHandle s, CCspStructFieldHandle field, int32_t* out_value); +CCspErrorCode ccsp_struct_get_uint32(CCspStructHandle s, CCspStructFieldHandle field, uint32_t* out_value); +CCspErrorCode ccsp_struct_get_int64(CCspStructHandle s, CCspStructFieldHandle field, int64_t* out_value); +CCspErrorCode ccsp_struct_get_uint64(CCspStructHandle s, CCspStructFieldHandle field, uint64_t* out_value); +CCspErrorCode ccsp_struct_get_double(CCspStructHandle s, CCspStructFieldHandle field, double* out_value); + +// Time types +CCspErrorCode ccsp_struct_get_datetime(CCspStructHandle s, CCspStructFieldHandle field, CCspDateTime* out_value); +CCspErrorCode ccsp_struct_get_timedelta(CCspStructHandle s, CCspStructFieldHandle field, CCspTimeDelta* out_value); + +// String (returns pointer to internal data, valid while struct exists) +CCspErrorCode ccsp_struct_get_string(CCspStructHandle s, CCspStructFieldHandle field, + const char** out_data, size_t* out_length); + +// Enum (returns ordinal value) +CCspErrorCode ccsp_struct_get_enum(CCspStructHandle s, CCspStructFieldHandle field, int32_t* out_ordinal); +``` + +### Convenience Getters (by Name) + +Access fields directly by name: + +```c +CCspErrorCode ccsp_struct_get_bool_by_name(CCspStructHandle s, const char* name, int8_t* out_value); +CCspErrorCode ccsp_struct_get_int32_by_name(CCspStructHandle s, const char* name, int32_t* out_value); +CCspErrorCode ccsp_struct_get_int64_by_name(CCspStructHandle s, const char* name, int64_t* out_value); +CCspErrorCode ccsp_struct_get_double_by_name(CCspStructHandle s, const char* name, double* out_value); +CCspErrorCode ccsp_struct_get_datetime_by_name(CCspStructHandle s, const char* name, CCspDateTime* out_value); +CCspErrorCode ccsp_struct_get_string_by_name(CCspStructHandle s, const char* name, + const char** out_data, size_t* out_length); +``` + +### Field Value Setters + +All setters return `CCSP_OK` on success or an error code on failure: + +```c +// Primitive types +CCspErrorCode ccsp_struct_set_bool(CCspStructHandle s, CCspStructFieldHandle field, int8_t value); +CCspErrorCode ccsp_struct_set_int8(CCspStructHandle s, CCspStructFieldHandle field, int8_t value); +CCspErrorCode ccsp_struct_set_uint8(CCspStructHandle s, CCspStructFieldHandle field, uint8_t value); +CCspErrorCode ccsp_struct_set_int16(CCspStructHandle s, CCspStructFieldHandle field, int16_t value); +CCspErrorCode ccsp_struct_set_uint16(CCspStructHandle s, CCspStructFieldHandle field, uint16_t value); +CCspErrorCode ccsp_struct_set_int32(CCspStructHandle s, CCspStructFieldHandle field, int32_t value); +CCspErrorCode ccsp_struct_set_uint32(CCspStructHandle s, CCspStructFieldHandle field, uint32_t value); +CCspErrorCode ccsp_struct_set_int64(CCspStructHandle s, CCspStructFieldHandle field, int64_t value); +CCspErrorCode ccsp_struct_set_uint64(CCspStructHandle s, CCspStructFieldHandle field, uint64_t value); +CCspErrorCode ccsp_struct_set_double(CCspStructHandle s, CCspStructFieldHandle field, double value); + +// Time types +CCspErrorCode ccsp_struct_set_datetime(CCspStructHandle s, CCspStructFieldHandle field, CCspDateTime value); +CCspErrorCode ccsp_struct_set_timedelta(CCspStructHandle s, CCspStructFieldHandle field, CCspTimeDelta value); + +// String (copies the data) +CCspErrorCode ccsp_struct_set_string(CCspStructHandle s, CCspStructFieldHandle field, + const char* data, size_t length); +``` + +### Struct Lifecycle + +```c +// Create a new struct instance (must call ccsp_struct_destroy when done) +CCspStructHandle ccsp_struct_create(CCspStructMetaHandle meta); + +// Destroy a struct instance +void ccsp_struct_destroy(CCspStructHandle s); + +// Create a deep copy of a struct +CCspStructHandle ccsp_struct_copy(CCspStructHandle s); +``` + +### Example Usage + +```c +void process_struct(CCspStructHandle s) +{ + // Get struct meta (type info) + CCspStructMetaHandle meta = ccsp_struct_meta(s); + printf("Struct type: %s\n", ccsp_struct_meta_name(meta)); + + // Iterate over fields + size_t field_count = ccsp_struct_meta_field_count(meta); + for (size_t i = 0; i < field_count; i++) { + CCspStructFieldHandle field = ccsp_struct_meta_field_by_index(meta, i); + const char* name = ccsp_struct_field_name(field); + CCspType type = ccsp_struct_field_type(field); + + // Check if field is set + if (!ccsp_struct_field_is_set(s, field)) { + printf(" %s: \n", name); + continue; + } + + // Print based on type + switch (type) { + case CCSP_TYPE_INT64: { + int64_t val; + ccsp_struct_get_int64(s, field, &val); + printf(" %s: %lld\n", name, (long long)val); + break; + } + case CCSP_TYPE_STRING: { + const char* data; + size_t len; + ccsp_struct_get_string(s, field, &data, &len); + printf(" %s: %.*s\n", name, (int)len, data); + break; + } + // ... handle other types + } + } + + // Direct access by name + double price; + if (ccsp_struct_get_double_by_name(s, "price", &price) == CCSP_OK) { + printf("Price: %f\n", price); + } +} + +// Creating a new struct +void create_order(CCspStructMetaHandle order_meta) +{ + CCspStructHandle order = ccsp_struct_create(order_meta); + if (!order) { + // Handle error + return; + } + + // Set fields + CCspStructFieldHandle symbol_field = ccsp_struct_meta_field_by_name(order_meta, "symbol"); + ccsp_struct_set_string(order, symbol_field, "AAPL", 4); + + CCspStructFieldHandle qty_field = ccsp_struct_meta_field_by_name(order_meta, "quantity"); + ccsp_struct_set_int64(order, qty_field, 100); + + // Use struct... + + // Cleanup + ccsp_struct_destroy(order); +} +``` + +______________________________________________________________________ + +## Output Adapters + +**Header:** `` + +Output adapters receive data from the CSP graph and send it to external systems. + +### Opaque Handles + +```c +typedef struct CCspEngineImpl* CCspEngineHandle; +typedef struct CCspInputImpl* CCspInputHandle; +typedef struct CCspOutputAdapterImpl* CCspOutputAdapterHandle; +``` + +### VTable Structure + +```c +typedef struct CCspOutputAdapterVTable { + void* user_data; + + void (*start)(void* user_data, CCspEngineHandle engine, + CCspDateTime start_time, CCspDateTime end_time); + + void (*stop)(void* user_data); + + void (*execute)(void* user_data, CCspEngineHandle engine, + CCspInputHandle input); + + void (*destroy)(void* user_data); + +} CCspOutputAdapterVTable; +``` + +#### Callbacks + +| Callback | Required | Description | +| --------- | ------------ | ---------------------------------------------------------- | +| `start` | Optional | Called when graph starts. Initialize resources here. | +| `stop` | Optional | Called when graph stops. Flush buffers, close connections. | +| `execute` | **Required** | Called when input has new value. Process the data here. | +| `destroy` | **Required** | Called to clean up. Free all allocated memory. | + +### Creation + +```c +CCspOutputAdapterHandle ccsp_output_adapter_extern_create( + CCspEngineHandle engine, + CCspType input_type, + const CCspOutputAdapterVTable* vtable +); + +void ccsp_output_adapter_extern_destroy(CCspOutputAdapterHandle adapter); +``` + +______________________________________________________________________ + +## Push Input Adapters + +**Header:** `` + +Push input adapters push data from external sources into the CSP graph. + +### Push Mode + +```c +typedef enum { + CCSP_PUSH_MODE_LAST_VALUE = 0, // Values collapse per cycle + CCSP_PUSH_MODE_NON_COLLAPSING = 1, // Each value = separate cycle + CCSP_PUSH_MODE_BURST = 2 // Values batched into vector +} CCspPushMode; +``` + +### VTable Structure + +```c +typedef struct CCspPushInputAdapterVTable { + void* user_data; + + void (*start)(void* user_data, CCspEngineHandle engine, + CCspPushInputAdapterHandle adapter, + CCspDateTime start_time, CCspDateTime end_time); + + void (*stop)(void* user_data); + + void (*destroy)(void* user_data); + +} CCspPushInputAdapterVTable; +``` + +**Note:** The `start` callback receives the adapter handle, which is needed for pushing data. + +### Creation + +```c +CCspPushInputAdapterHandle ccsp_push_input_adapter_extern_create( + CCspEngineHandle engine, + CCspType type, + CCspPushMode push_mode, + CCspPushGroupHandle group, // Can be NULL + const CCspPushInputAdapterVTable* vtable +); + +void ccsp_push_input_adapter_extern_destroy(CCspPushInputAdapterHandle adapter); +``` + +### Push Functions (Thread-Safe) + +These functions can be called from any thread: + +```c +// Generic push +CCspErrorCode ccsp_push_input_adapter_push_value( + CCspPushInputAdapterHandle adapter, + const CCspValue* value, + CCspPushBatchHandle batch); + +// Type-specific push (more efficient) +CCspErrorCode ccsp_push_input_adapter_push_bool(CCspPushInputAdapterHandle adapter, int8_t value, CCspPushBatchHandle batch); +CCspErrorCode ccsp_push_input_adapter_push_int8(CCspPushInputAdapterHandle adapter, int8_t value, CCspPushBatchHandle batch); +CCspErrorCode ccsp_push_input_adapter_push_uint8(CCspPushInputAdapterHandle adapter, uint8_t value, CCspPushBatchHandle batch); +CCspErrorCode ccsp_push_input_adapter_push_int16(CCspPushInputAdapterHandle adapter, int16_t value, CCspPushBatchHandle batch); +CCspErrorCode ccsp_push_input_adapter_push_uint16(CCspPushInputAdapterHandle adapter, uint16_t value, CCspPushBatchHandle batch); +CCspErrorCode ccsp_push_input_adapter_push_int32(CCspPushInputAdapterHandle adapter, int32_t value, CCspPushBatchHandle batch); +CCspErrorCode ccsp_push_input_adapter_push_uint32(CCspPushInputAdapterHandle adapter, uint32_t value, CCspPushBatchHandle batch); +CCspErrorCode ccsp_push_input_adapter_push_int64(CCspPushInputAdapterHandle adapter, int64_t value, CCspPushBatchHandle batch); +CCspErrorCode ccsp_push_input_adapter_push_uint64(CCspPushInputAdapterHandle adapter, uint64_t value, CCspPushBatchHandle batch); +CCspErrorCode ccsp_push_input_adapter_push_double(CCspPushInputAdapterHandle adapter, double value, CCspPushBatchHandle batch); +CCspErrorCode ccsp_push_input_adapter_push_datetime(CCspPushInputAdapterHandle adapter, CCspDateTime value, CCspPushBatchHandle batch); +CCspErrorCode ccsp_push_input_adapter_push_timedelta(CCspPushInputAdapterHandle adapter, CCspTimeDelta value, CCspPushBatchHandle batch); +CCspErrorCode ccsp_push_input_adapter_push_string(CCspPushInputAdapterHandle adapter, const char* data, size_t length, CCspPushBatchHandle batch); +CCspErrorCode ccsp_push_input_adapter_push_struct(CCspPushInputAdapterHandle adapter, CCspStructHandle value, CCspPushBatchHandle batch); +``` + +### Batch Management + +Batches group multiple events to be processed atomically: + +```c +CCspPushBatchHandle ccsp_push_batch_create(CCspEngineHandle engine); +void ccsp_push_batch_flush(CCspPushBatchHandle batch); +void ccsp_push_batch_destroy(CCspPushBatchHandle batch); +``` + +### Group Management + +Groups synchronize multiple input adapters: + +```c +CCspPushGroupHandle ccsp_push_group_create(void); +void ccsp_push_group_destroy(CCspPushGroupHandle group); +``` + +______________________________________________________________________ + +## Engine Access + +**Header:** `` + +### Functions + +```c +// Get current engine time +CCspDateTime ccsp_engine_now(CCspEngineHandle engine); + +// Get current cycle count +uint64_t ccsp_engine_cycle_count(CCspEngineHandle engine); +``` + +______________________________________________________________________ + +## Input Access + +**Header:** `` + +Functions for reading input values in output adapter `execute` callbacks: + +### Status Functions + +```c +int ccsp_input_is_valid(CCspInputHandle input); +int32_t ccsp_input_num_ticks(CCspInputHandle input); +CCspType ccsp_input_get_type(CCspInputHandle input); +CCspDateTime ccsp_input_get_last_time(CCspInputHandle input); +``` + +### Value Access + +```c +// Generic value access +CCspErrorCode ccsp_input_get_last_value(CCspInputHandle input, CCspValue* out_value); +CCspErrorCode ccsp_input_get_value_at(CCspInputHandle input, int32_t index, CCspValue* out_value); +CCspErrorCode ccsp_input_get_time_at(CCspInputHandle input, int32_t index, CCspDateTime* out_time); + +// Convenience functions (more efficient for common types) +CCspErrorCode ccsp_input_get_last_string(CCspInputHandle input, const char** out_data, size_t* out_length); +CCspErrorCode ccsp_input_get_last_int64(CCspInputHandle input, int64_t* out_value); +CCspErrorCode ccsp_input_get_last_double(CCspInputHandle input, double* out_value); +CCspErrorCode ccsp_input_get_last_bool(CCspInputHandle input, int8_t* out_value); +CCspErrorCode ccsp_input_get_last_datetime(CCspInputHandle input, CCspDateTime* out_value); +``` + +______________________________________________________________________ + +## Adapter Managers + +**Header:** `` + +Adapter managers coordinate a group of related adapters, handling shared lifecycle, status reporting, and resource management. + +### Opaque Handles + +```c +typedef struct CCspAdapterManagerImpl* CCspAdapterManagerHandle; +typedef struct CCspStatusAdapterImpl* CCspStatusAdapterHandle; +typedef struct CCspManagedSimInputAdapterImpl* CCspManagedSimInputAdapterHandle; +``` + +### VTable Structure + +```c +typedef struct CCspAdapterManagerVTable { + void* user_data; + + // REQUIRED: Return manager name (for logging) + const char* (*name)(void* user_data); + + // REQUIRED: Process simulation time slice + // Return next timestamp, or 0 if no more data + CCspDateTime (*process_next_sim_time_slice)(void* user_data, CCspDateTime time); + + // REQUIRED: Clean up manager resources + void (*destroy)(void* user_data); + + // OPTIONAL: Called when graph starts + void (*start)(void* user_data, CCspAdapterManagerHandle manager, + CCspDateTime start_time, CCspDateTime end_time); + + // OPTIONAL: Called when graph stops + void (*stop)(void* user_data); + +} CCspAdapterManagerVTable; +``` + +#### Callbacks + +| Callback | Required | Description | +| ----------------------------- | ------------ | ---------------------------------------------------- | +| `name` | **Required** | Returns manager name for logging/debugging | +| `process_next_sim_time_slice` | **Required** | Processes sim data, returns next timestamp or 0 | +| `destroy` | **Required** | Frees all allocated resources | +| `start` | Optional | Called when graph starts. Initialize resources here. | +| `stop` | Optional | Called when graph stops. Clean up resources. | + +### Creation and Lifecycle + +```c +// Create an adapter manager +CCspAdapterManagerHandle ccsp_adapter_manager_extern_create( + CCspEngineHandle engine, + const CCspAdapterManagerVTable* vtable); + +// Destroy is automatic - engine handles cleanup +void ccsp_adapter_manager_extern_destroy(CCspAdapterManagerHandle manager); +``` + +### Engine and Time Access + +```c +// Get engine handle for use with other C API functions +CCspEngineHandle ccsp_adapter_manager_engine(CCspAdapterManagerHandle manager); + +// Get graph start time (valid after start() called) +CCspDateTime ccsp_adapter_manager_start_time(CCspAdapterManagerHandle manager); + +// Get graph end time (valid after start() called) +CCspDateTime ccsp_adapter_manager_end_time(CCspAdapterManagerHandle manager); +``` + +### Adapter Creation from Manager + +Adapters created via the manager share its lifecycle: + +```c +// Create a managed output adapter +CCspOutputAdapterHandle ccsp_adapter_manager_create_output_adapter( + CCspAdapterManagerHandle manager, + CCspType input_type, + const CCspOutputAdapterVTable* vtable); + +// Create a managed push input adapter +CCspPushInputAdapterHandle ccsp_adapter_manager_create_push_input_adapter( + CCspAdapterManagerHandle manager, + CCspType type, + CCspPushMode push_mode, + const CCspPushInputAdapterVTable* vtable); +``` + +### Status Reporting + +```c +typedef enum { + CCSP_STATUS_LEVEL_CRITICAL = 0, + CCSP_STATUS_LEVEL_ERROR = 1, + CCSP_STATUS_LEVEL_WARNING = 2, + CCSP_STATUS_LEVEL_INFO = 3, + CCSP_STATUS_LEVEL_DEBUG = 4 +} CCspStatusLevel; + +// Push a status message to the graph +CCspErrorCode ccsp_adapter_manager_push_status( + CCspAdapterManagerHandle manager, + CCspStatusLevel level, + int64_t err_code, + const char* message); +``` + +### Managed Simulation Input Adapter + +For adapters that provide data in simulation mode: + +```c +// Create a managed sim input adapter +CCspManagedSimInputAdapterHandle ccsp_adapter_manager_create_managed_sim_input_adapter( + CCspAdapterManagerHandle manager, + CCspType type, + CCspPushMode push_mode); + +// Push data (call from process_next_sim_time_slice) +CCspErrorCode ccsp_managed_sim_input_adapter_push_bool( + CCspManagedSimInputAdapterHandle adapter, int8_t value); +CCspErrorCode ccsp_managed_sim_input_adapter_push_int64( + CCspManagedSimInputAdapterHandle adapter, int64_t value); +CCspErrorCode ccsp_managed_sim_input_adapter_push_double( + CCspManagedSimInputAdapterHandle adapter, double value); +CCspErrorCode ccsp_managed_sim_input_adapter_push_string( + CCspManagedSimInputAdapterHandle adapter, const char* data, size_t length); +CCspErrorCode ccsp_managed_sim_input_adapter_push_datetime( + CCspManagedSimInputAdapterHandle adapter, CCspDateTime value); +``` + +______________________________________________________________________ + +## Python Bridge Functions + +CSP provides bridge functions that consume C API capsules and create native adapter objects. These functions are essential for integrating C API adapters with CSP's Python wiring layer. + +### Available Bridge Functions + +These functions are available in `csp.impl.__cspimpl._cspimpl`: + +| Function | Description | +| ------------------------------- | ---------------------------------------------------------------- | +| `_c_api_push_input_adapter` | Creates `PushInputAdapterExtern` from an input adapter capsule | +| `_c_api_output_adapter` | Creates `OutputAdapterExtern` from an output adapter capsule | +| `_c_api_adapter_manager_bridge` | Converts C API adapter manager capsule to CSP-compatible capsule | + +### `_c_api_push_input_adapter` + +Creates a native push input adapter from a C API capsule. + +**Signature:** + +```python +_cspimpl._c_api_push_input_adapter( + mgr, # Adapter manager (or None) + engine, # CSP engine + pytype, # Python type for the timeseries + push_mode, # PushMode enum value + args # Tuple: (capsule, push_group_or_none) +) +``` + +**Parameters:** + +- `mgr`: Adapter manager capsule or `None` +- `engine`: CSP engine object +- `pytype`: Python type (e.g., `int`, `str`) for the output timeseries +- `push_mode`: `csp.PushMode` value +- `args`: Tuple containing: + - Capsule with name `"csp.c.InputAdapterCapsule"` containing `CCspPushInputAdapterVTable` + - Push group capsule or `None` + +**Returns:** Native adapter wrapper compatible with CSP's wiring layer + +### `_c_api_output_adapter` + +Creates a native output adapter from a C API capsule. + +**Signature:** + +```python +_cspimpl._c_api_output_adapter( + mgr, # Adapter manager (or None) + engine, # CSP engine + args # Tuple: (input_type, capsule) +) +``` + +**Parameters:** + +- `mgr`: Adapter manager capsule or `None` +- `engine`: CSP engine object +- `args`: Tuple containing: + - Input type (Python type, e.g., `int`) + - Capsule with name `"csp.c.OutputAdapterCapsule"` containing `CCspOutputAdapterVTable` + +**Returns:** Native adapter wrapper compatible with CSP's wiring layer + +### `_c_api_adapter_manager_bridge` + +Converts a C API adapter manager capsule to a CSP-compatible adapter manager capsule. This is essential for using C API adapter managers with CSP's wiring layer. + +**Signature:** + +```python +_cspimpl._c_api_adapter_manager_bridge( + engine, # CSP engine + capsule # C API adapter manager capsule +) +``` + +**Parameters:** + +- `engine`: CSP engine object (from the `_create()` method or graph wiring) +- `capsule`: Capsule with name `"csp.c.AdapterManagerCapsule"` containing `CCspAdapterManagerVTable` + +**Returns:** A capsule compatible with CSP's adapter manager wiring (name `"adapterMgr"`, containing `AdapterManagerExtern*`) + +**Usage in Adapter Manager class:** + +```python +class MyAdapterManager: + def __init__(self, config): + self._config = config + self._push_group = PushGroup() + + def _create(self, engine, memo): + """Called by CSP wiring layer to create the manager.""" + # Create C API capsule from your native module + c_api_capsule = _my_native_module._create_adapter_manager(self._config) + + # Bridge to CSP-compatible format + return _cspimpl._c_api_adapter_manager_bridge(engine, c_api_capsule) + + def subscribe(self, ts_type, **kwargs): + return _managed_input_adapter_def(self, ts_type, **kwargs) +``` + +The bridge function: + +1. Extracts the `CCspAdapterManagerVTable` from the C API capsule +1. Creates an `AdapterManagerExtern` wrapper that owns the VTable +1. Clears the original capsule's destructor to prevent double-free +1. Returns a new capsule that CSP's wiring layer can use + +### Capsule Names + +The bridge functions expect capsules with specific names: + +| Adapter Type | Capsule Name | +| ------------------ | ------------------------------- | +| Push Input Adapter | `"csp.c.InputAdapterCapsule"` | +| Output Adapter | `"csp.c.OutputAdapterCapsule"` | +| Adapter Manager | `"csp.c.AdapterManagerCapsule"` | + +### Usage Pattern + +```python +from csp.impl.__cspimpl import _cspimpl +from csp.impl.wiring import input_adapter_def +from . import _my_native_module + +def _create_my_adapter(mgr, engine, pytype, push_mode, scalars): + # Create capsule from your C code + capsule = _my_native_module._my_input_adapter(interval_ms=100) + + # Pass to CSP bridge + return _cspimpl._c_api_push_input_adapter( + mgr, engine, pytype, push_mode, (capsule, None) + ) + +my_adapter = input_adapter_def( + "my_adapter", + _create_my_adapter, + ts["T"], + typ="T", + interval_ms=int, +) +``` + +### Ownership Transfer + +When you pass a capsule to a bridge function: + +1. The VTable is extracted from the capsule +1. A native adapter (`PushInputAdapterExtern` or `OutputAdapterExtern`) is created +1. **Ownership of the VTable is transferred** to the native adapter +1. The capsule's destructor is cleared to prevent double-free +1. The adapter's destructor will call `vtable.destroy()` when the graph ends + +______________________________________________________________________ + +## Thread Safety + +| Function Category | Thread Safety | +| ------------------------------------------------- | ----------------------------------------------------- | +| Push functions (`ccsp_push_input_adapter_push_*`) | **Thread-safe** | +| Batch functions | Thread-safe | +| Input access functions | **Not thread-safe** (call only from execute callback) | +| Engine access functions | Not thread-safe | +| Error functions | Thread-safe (thread-local storage) | + +______________________________________________________________________ + +## Memory Ownership + +| Type | Ownership | +| ----------------------------------------- | -------------------------------------------------- | +| `CCspValue` with string | If `is_owned=1`, you must call `ccsp_value_free()` | +| Strings from `ccsp_input_get_last_string` | Borrowed from CSP, valid until next tick | +| VTable `user_data` | You own this memory, free in `destroy` callback | +| Opaque handles | CSP owns these, do not free | + +______________________________________________________________________ + +## See Also + +- [Write C API Adapters](../how-tos/Write-C-API-Adapters.md) - How-to guide +- [C API Adapter Example](../../../examples/05_cpp/4_c_api_adapter/) - C implementation +- [Rust Adapter Example](../../../examples/05_cpp/5_c_api_adapter_rust/) - Rust implementation diff --git a/docs/wiki/concepts/Adapters.md b/docs/wiki/concepts/Adapters.md index fc4e54907..d99a1ea7a 100644 --- a/docs/wiki/concepts/Adapters.md +++ b/docs/wiki/concepts/Adapters.md @@ -12,4 +12,5 @@ In CSP terminology, a single adapter corresponds to a single timeseries edge in There are common cases where a single data source may be used to provide data to multiple adapter (timeseries) instances, for example a single CSV file with price data for many stocks can be read once but used to provide data to many individual, one per stock. In such cases an AdapterManager is used to coordinate management of the single source (CSV file, database, Kafka connection, etc) and provided data to individual adapters. -Note that adapters can be quickly written and prototyped in python, and if needed can be moved to a c+ implementation for more efficiency. +Note that adapters can be quickly written and prototyped in Python, and if needed can be moved to a C++ implementation for more efficiency. +For maximum portability and ABI stability, adapters can also be written in C (or any language with C FFI such as Rust or Go) using the [C API](../api-references/C-APIs.md). diff --git a/docs/wiki/get-started/More-with-CSP.md b/docs/wiki/get-started/More-with-CSP.md index b25e37eb3..fd85f76f1 100644 --- a/docs/wiki/get-started/More-with-CSP.md +++ b/docs/wiki/get-started/More-with-CSP.md @@ -15,6 +15,7 @@ We also turn off memoization for the node by passing `memoize=False`. This means ```python import csp from csp import ts + from datetime import timedelta import numpy as np @@ -38,9 +39,10 @@ def poisson_counter(rate: float) -> ts[int]: We can run the node using `csp.run` as follows: ```python -from datetime import datetime +from datetime import timedelta +from csp.utils.datetime import utc_now -res = csp.run(poisson_counter, rate=2.0, starttime=datetime.utcnow(), endtime=timedelta(seconds=10), realtime=False) +res = csp.run(poisson_counter, rate=2.0, starttime=utc_now(), endtime=timedelta(seconds=10), realtime=False) print(f'Final count: {res[0][-1][1]}') ``` diff --git a/docs/wiki/how-tos/Write-C-API-Adapters.md b/docs/wiki/how-tos/Write-C-API-Adapters.md new file mode 100644 index 000000000..b2b57581c --- /dev/null +++ b/docs/wiki/how-tos/Write-C-API-Adapters.md @@ -0,0 +1,814 @@ +## Table of Contents + +- [Table of Contents](#table-of-contents) +- [Writing C API Adapters](#writing-c-api-adapters) + - [Overview](#overview) + - [When to Use the C API](#when-to-use-the-c-api) + - [The VTable Pattern](#the-vtable-pattern) + - [Writing an Output Adapter in C](#writing-an-output-adapter-in-c) + - [Step 1: Define Your State](#step-1-define-your-state) + - [Step 2: Implement Callbacks](#step-2-implement-callbacks) + - [Step 3: Create the Factory Function](#step-3-create-the-factory-function) + - [Writing a Push Input Adapter in C](#writing-a-push-input-adapter-in-c) + - [Step 1: Define State with Threading](#step-1-define-state-with-threading) + - [Step 2: Implement the Data Source Thread](#step-2-implement-the-data-source-thread) + - [Step 3: Implement Lifecycle Callbacks](#step-3-implement-lifecycle-callbacks) + - [Step 4: Create Factory Function](#step-4-create-factory-function) + - [Writing an Adapter Manager in C](#writing-an-adapter-manager-in-c) + - [Why Use an Adapter Manager](#why-use-an-adapter-manager) + - [Adapter Manager VTable](#adapter-manager-vtable) + - [Example: Managed Adapter](#example-managed-adapter) + - [Building and Linking](#building-and-linking) + - [Python Integration](#python-integration) + - [Create Python Bindings (C code)](#create-python-bindings-c-code) + - [Create Python Wrapper with Bridge Functions](#create-python-wrapper-with-bridge-functions) + - [CSP Bridge Functions](#csp-bridge-functions) + - [Managed Adapter Python Wrapper](#managed-adapter-python-wrapper) + - [Use Managed Adapters in Your Graph](#use-managed-adapters-in-your-graph) + - [Use in Your Graph](#use-in-your-graph) +- [See Also](#see-also) + +## Writing C API Adapters + +CSP provides a C API for writing adapters that can be compiled separately from CSP and loaded at runtime. This enables: + +- Writing adapters in any language with C FFI (C, Rust, Go, etc.) +- Distributing adapters as separate packages +- ABI stability across CSP versions + +### Overview + +The C API uses a **VTable (Virtual Table) pattern** to define adapters. A VTable is a struct containing function pointers that CSP calls at the appropriate times during the adapter lifecycle: + +```raw ++-------------------------------------------------------------+ +| CSP Engine (C++) | +| | +| 1. Calls vtable.start() when graph starts | +| 2. Calls vtable.execute() when input ticks (output adapter)| +| 3. Calls vtable.stop() when graph stops | +| 4. Calls vtable.destroy() to clean up | +| | ++---------------------+---------------------------------------+ + | calls your functions + v ++-------------------------------------------------------------+ +| Your C Adapter | +| | +| - Allocate state struct | +| - Implement callback functions | +| - Return VTable with function pointers | +| | ++-------------------------------------------------------------+ +``` + +### When to Use the C API + +Use the C API when you need: + +| Use Case | Example | +| ----------------------------------- | ----------------------------------------------- | +| **Separate compilation** | Distribute Kafka adapter independently from CSP | +| **Non-Python languages** | Write an adapter in Rust or Go | +| **Performance-critical code** | Avoid Python overhead in hot paths | +| **Third-party library integration** | Wrap a C library directly | + +For simpler use cases, consider using Python adapters instead (see [Write Output Adapters](Write-Output-Adapters.md) and [Write Realtime Input Adapters](Write-Realtime-Input-Adapters.md)). + +### The VTable Pattern + +Every C adapter consists of: + +1. **A state struct** - holds your adapter's data +1. **Callback functions** - implement the adapter logic +1. **A factory function** - allocates state and returns a VTable + +Here's the VTable structure for output adapters: + +```c +typedef struct CCspOutputAdapterVTable { + void* user_data; // Pointer to your state struct + + // Called when graph starts + void (*start)(void* user_data, CCspEngineHandle engine, + CCspDateTime start_time, CCspDateTime end_time); + + // Called when graph stops + void (*stop)(void* user_data); + + // Called when input ticks (REQUIRED) + void (*execute)(void* user_data, CCspEngineHandle engine, + CCspInputHandle input); + + // Called to clean up (REQUIRED) + void (*destroy)(void* user_data); +} CCspOutputAdapterVTable; +``` + +### Writing an Output Adapter in C + +Let's write a simple output adapter that logs values to stdout. + +#### Step 1: Define Your State + +```c +#include +#include +#include +#include + +typedef struct { + char* prefix; // Prefix for log messages + FILE* output; // Where to write +} LogAdapterState; +``` + +#### Step 2: Implement Callbacks + +```c +static void log_adapter_start(void* user_data, CCspEngineHandle engine, + CCspDateTime start_time, CCspDateTime end_time) +{ + LogAdapterState* state = (LogAdapterState*)user_data; + fprintf(state->output, "[LogAdapter] Started\n"); +} + +static void log_adapter_stop(void* user_data) +{ + LogAdapterState* state = (LogAdapterState*)user_data; + fprintf(state->output, "[LogAdapter] Stopped\n"); +} + +static void log_adapter_execute(void* user_data, CCspEngineHandle engine, + CCspInputHandle input) +{ + LogAdapterState* state = (LogAdapterState*)user_data; + + // Get the current time + CCspDateTime now = ccsp_engine_now(engine); + + // Get the input value based on type + CCspType type = ccsp_input_get_type(input); + + switch (type) { + case CCSP_TYPE_STRING: { + const char* data; + size_t len; + if (ccsp_input_get_last_string(input, &data, &len) == CCSP_OK) { + fprintf(state->output, "%s[%lld] %.*s\n", + state->prefix, (long long)now, (int)len, data); + } + break; + } + case CCSP_TYPE_INT64: { + int64_t val; + if (ccsp_input_get_last_int64(input, &val) == CCSP_OK) { + fprintf(state->output, "%s[%lld] %lld\n", + state->prefix, (long long)now, (long long)val); + } + break; + } + case CCSP_TYPE_DOUBLE: { + double val; + if (ccsp_input_get_last_double(input, &val) == CCSP_OK) { + fprintf(state->output, "%s[%lld] %f\n", + state->prefix, (long long)now, val); + } + break; + } + default: + fprintf(state->output, "%s[%lld] \n", + state->prefix, (long long)now); + } +} + +static void log_adapter_destroy(void* user_data) +{ + LogAdapterState* state = (LogAdapterState*)user_data; + if (state) { + free(state->prefix); + free(state); + } +} +``` + +#### Step 3: Create the Factory Function + +```c +CCspOutputAdapterVTable create_log_adapter(const char* prefix) +{ + CCspOutputAdapterVTable vtable = {0}; + + // Allocate state + LogAdapterState* state = malloc(sizeof(LogAdapterState)); + if (!state) { + return vtable; // Return zeroed vtable on error + } + + // Initialize state + state->output = stdout; + state->prefix = prefix ? strdup(prefix) : strdup(""); + + // Fill in the vtable + vtable.user_data = state; + vtable.start = log_adapter_start; + vtable.stop = log_adapter_stop; + vtable.execute = log_adapter_execute; + vtable.destroy = log_adapter_destroy; + + return vtable; +} +``` + +### Writing a Push Input Adapter in C + +Push input adapters are more complex because they push data from external threads into the CSP engine. + +#### Step 1: Define State with Threading + +```c +#include +#include + +typedef struct { + int interval_ms; + int running; + int64_t counter; + CCspPushInputAdapterHandle adapter; // Handle for pushing data + pthread_t thread; +} CounterAdapterState; +``` + +#### Step 2: Implement the Data Source Thread + +```c +static void* counter_thread(void* arg) +{ + CounterAdapterState* state = (CounterAdapterState*)arg; + + while (state->running) { + // Push the current value (thread-safe) + ccsp_push_input_adapter_push_int64(state->adapter, state->counter, NULL); + state->counter++; + + usleep(state->interval_ms * 1000); + } + + return NULL; +} +``` + +#### Step 3: Implement Lifecycle Callbacks + +```c +static void counter_start(void* user_data, CCspEngineHandle engine, + CCspPushInputAdapterHandle adapter, + CCspDateTime start_time, CCspDateTime end_time) +{ + CounterAdapterState* state = (CounterAdapterState*)user_data; + + // Save the adapter handle for pushing data + state->adapter = adapter; + state->running = 1; + state->counter = 0; + + // Start the data thread + pthread_create(&state->thread, NULL, counter_thread, state); +} + +static void counter_stop(void* user_data) +{ + CounterAdapterState* state = (CounterAdapterState*)user_data; + state->running = 0; + pthread_join(state->thread, NULL); +} + +static void counter_destroy(void* user_data) +{ + free(user_data); +} +``` + +#### Step 4: Create Factory Function + +```c +CCspPushInputAdapterVTable create_counter_adapter(int interval_ms) +{ + CCspPushInputAdapterVTable vtable = {0}; + + CounterAdapterState* state = malloc(sizeof(CounterAdapterState)); + if (!state) return vtable; + + memset(state, 0, sizeof(CounterAdapterState)); + state->interval_ms = interval_ms > 0 ? interval_ms : 100; + + vtable.user_data = state; + vtable.start = counter_start; + vtable.stop = counter_stop; + vtable.destroy = counter_destroy; + + return vtable; +} +``` + +### Writing an Adapter Manager in C + +For complex adapters like Kafka or WebSocket, you'll want to use an **Adapter Manager** to coordinate multiple adapters that share resources (connections, threads, configuration). + +#### Why Use an Adapter Manager + +| Scenario | Without Manager | With Manager | +| ------------------------ | ------------------------------- | ------------------------- | +| Multiple output adapters | Each manages its own connection | Shared connection pool | +| Start/stop lifecycle | Each adapter independently | Coordinated start/stop | +| Status reporting | No unified status | Single status stream | +| Configuration | Duplicated across adapters | Centralized configuration | + +Adapter managers are used by CSP's built-in Kafka, Parquet, and WebSocket adapters. + +#### Adapter Manager VTable + +```c +typedef struct CCspAdapterManagerVTable { + void* user_data; + + // REQUIRED: Return the name of this manager + const char* (*name)(void* user_data); + + // REQUIRED: Process simulation time slice (return 0 for realtime-only) + CCspDateTime (*process_next_sim_time_slice)(void* user_data, CCspDateTime time); + + // REQUIRED: Clean up resources + void (*destroy)(void* user_data); + + // OPTIONAL: Called when graph starts + void (*start)(void* user_data, CCspAdapterManagerHandle manager, + CCspDateTime start_time, CCspDateTime end_time); + + // OPTIONAL: Called when graph stops + void (*stop)(void* user_data); + +} CCspAdapterManagerVTable; +``` + +#### Example: Managed Adapter + +This example shows a manager that coordinates multiple output adapters (like Kafka topics): + +```c +#include +#include +#include +#include + +/* Shared state for all adapters in this manager */ +typedef struct { + char name[64]; + int is_started; + int total_messages; + CCspAdapterManagerHandle manager_handle; +} ManagedState; + +/* State for each output adapter */ +typedef struct { + ManagedState* shared; /* Points to manager's state */ + char topic[64]; + int messages_sent; +} OutputState; + +/* Manager callbacks */ +static const char* manager_name(void* user_data) { + ManagedState* state = (ManagedState*)user_data; + return state->name; +} + +static void manager_start(void* user_data, CCspAdapterManagerHandle manager, + CCspDateTime start_time, CCspDateTime end_time) { + ManagedState* state = (ManagedState*)user_data; + state->manager_handle = manager; + state->is_started = 1; + printf("[%s] Manager started\n", state->name); + + /* Report status to the graph */ + ccsp_adapter_manager_push_status(manager, CCSP_STATUS_LEVEL_INFO, 0, + "Manager started successfully"); +} + +static void manager_stop(void* user_data) { + ManagedState* state = (ManagedState*)user_data; + printf("[%s] Manager stopped. Total messages: %d\n", + state->name, state->total_messages); + state->is_started = 0; +} + +static CCspDateTime manager_process_sim(void* user_data, CCspDateTime time) { + /* Realtime-only adapter - return 0 */ + (void)user_data; + (void)time; + return 0; +} + +static void manager_destroy(void* user_data) { + free(user_data); +} + +/* Create the adapter manager */ +CCspAdapterManagerVTable create_my_manager(const char* name) { + CCspAdapterManagerVTable vtable = {0}; + + ManagedState* state = malloc(sizeof(ManagedState)); + if (!state) return vtable; + + memset(state, 0, sizeof(ManagedState)); + strncpy(state->name, name ? name : "MyManager", sizeof(state->name) - 1); + + vtable.user_data = state; + vtable.name = manager_name; + vtable.start = manager_start; + vtable.stop = manager_stop; + vtable.process_next_sim_time_slice = manager_process_sim; + vtable.destroy = manager_destroy; + + return vtable; +} + +/* Output adapter callbacks - uses shared state */ +static void output_execute(void* user_data, CCspEngineHandle engine, + CCspInputHandle input) { + OutputState* state = (OutputState*)user_data; + if (!ccsp_input_is_valid(input)) return; + + /* Get value and log it */ + CCspType type = ccsp_input_get_type(input); + printf(" [%s/%s] received type %d\n", + state->shared->name, state->topic, type); + + state->messages_sent++; + state->shared->total_messages++; +} + +static void output_destroy(void* user_data) { + free(user_data); +} + +/* Create an output adapter that uses the manager's shared state */ +CCspOutputAdapterVTable create_managed_output(ManagedState* shared, const char* topic) { + CCspOutputAdapterVTable vtable = {0}; + + OutputState* state = malloc(sizeof(OutputState)); + if (!state) return vtable; + + memset(state, 0, sizeof(OutputState)); + state->shared = shared; + strncpy(state->topic, topic ? topic : "default", sizeof(state->topic) - 1); + + vtable.user_data = state; + vtable.execute = output_execute; + vtable.destroy = output_destroy; + + return vtable; +} +``` + +See [ExampleManagedAdapter.c](../../cpp/csp/adapters/c/example/ExampleManagedAdapter.c) for the complete implementation. + +### Building and Linking + +Your C adapter should be compiled as a shared library that links against the CSP C API: + +```cmake +# CMakeLists.txt for your adapter +find_package(csp REQUIRED) + +add_library(my_adapter SHARED + my_adapter.c +) + +target_link_libraries(my_adapter + csp::csp_c_api +) + +target_include_directories(my_adapter PRIVATE + ${CSP_INCLUDE_DIRS} +) +``` + +Build with: + +```bash +mkdir build && cd build +cmake .. +make +``` + +### Python Integration + +To use your C adapter from Python, you need to: + +1. Create a Python extension module that wraps your C functions and returns capsules +1. Write Python bridge functions that pass capsules to CSP's adapter bridge +1. Define adapters using `input_adapter_def` / `output_adapter_def` + +#### Create Python Bindings (C code) + +Your C extension should create capsules wrapping the VTables: + +```c +#include +#include +#include +#include "my_adapter.h" + +// Output adapter - returns a capsule +static PyObject* create_log_adapter_py(PyObject* self, PyObject* args, PyObject* kwargs) +{ + static char* kwlist[] = {"prefix", NULL}; + const char* prefix = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|s", kwlist, &prefix)) { + return NULL; + } + + CCspOutputAdapterVTable vtable = create_log_adapter(prefix); + if (!vtable.execute) { + PyErr_SetString(PyExc_MemoryError, "Failed to create adapter"); + return NULL; + } + + // Create a capsule that CSP's bridge can consume + return ccsp_py_create_output_adapter_capsule_owned(&vtable); +} + +// Input adapter - returns a capsule +static PyObject* create_input_adapter_py(PyObject* self, PyObject* args, PyObject* kwargs) +{ + static char* kwlist[] = {"interval_ms", NULL}; + int interval_ms = 100; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", kwlist, &interval_ms)) { + return NULL; + } + + CCspPushInputAdapterVTable vtable = create_my_input_adapter(interval_ms); + if (!vtable.destroy) { + PyErr_SetString(PyExc_MemoryError, "Failed to create adapter"); + return NULL; + } + + return ccsp_py_create_input_adapter_capsule_owned(&vtable); +} + +static PyMethodDef methods[] = { + {"_log_adapter", (PyCFunction)create_log_adapter_py, METH_VARARGS | METH_KEYWORDS, "Create log adapter"}, + {"_my_input_adapter", (PyCFunction)create_input_adapter_py, METH_VARARGS | METH_KEYWORDS, "Create input adapter"}, + {NULL, NULL, 0, NULL} +}; + +static PyModuleDef module = { + PyModuleDef_HEAD_INIT, "my_adapter", NULL, -1, methods +}; + +PyMODINIT_FUNC PyInit_my_adapter(void) { + return PyModule_Create(&module); +} +``` + +#### Create Python Wrapper with Bridge Functions + +CSP provides bridge functions that consume capsules and create native adapters. These are essential for integrating C API adapters with CSP's wiring layer. + +```python +# my_adapter.py +from csp import ts +from csp.impl.__cspimpl import _cspimpl +from csp.impl.wiring import input_adapter_def, output_adapter_def +from . import _my_adapter_impl # Your C extension + +def _create_input_bridge(mgr, engine, pytype, push_mode, scalars): + """ + Bridge function for input adapters. + + The wiring layer calls this with: + - mgr: adapter manager (or None) + - engine: CSP engine + - pytype: Python type for the timeseries + - push_mode: PushMode enum value + - scalars: tuple of scalar arguments from the adapter_def + """ + # Extract your parameters from scalars + # For standalone adapters: scalars[0] is typ, scalars[1] is interval_ms + interval_ms = scalars[1] if len(scalars) > 1 else 100 + + # Create the VTable capsule using your C function + capsule = _my_adapter_impl._my_input_adapter(interval_ms=interval_ms) + + # Pass to CSP bridge which creates PushInputAdapterExtern + # Arguments: (capsule, push_group_or_none) + return _cspimpl._c_api_push_input_adapter( + mgr, engine, pytype, push_mode, (capsule, None) + ) + +def _create_output_bridge(mgr, engine, scalars): + """ + Bridge function for output adapters. + + The wiring layer calls this with: + - mgr: adapter manager (or None) + - engine: CSP engine + - scalars: tuple of scalar arguments from the adapter_def + """ + # Extract your parameters + prefix = scalars[0] if scalars else "" + + # Create the VTable capsule + capsule = _my_adapter_impl._log_adapter(prefix=prefix) + + # Pass to CSP bridge which creates OutputAdapterExtern + # Arguments: (input_type, capsule) + return _cspimpl._c_api_output_adapter(mgr, engine, (int, capsule)) + +# Define input adapter +my_input = input_adapter_def( + "my_input_adapter", + _create_input_bridge, + ts["T"], + typ="T", + interval_ms=int, +) + +# Define output adapter +LogAdapter = output_adapter_def( + "LogAdapter", + _create_output_bridge, + input=ts["T"], + prefix=str, +) +``` + +#### CSP Bridge Functions + +CSP provides three bridge functions in `_cspimpl`: + +| Function | Purpose | Arguments | +| ------------------------------- | ----------------------------------------------- | --------------------------------------------------------- | +| `_c_api_push_input_adapter` | Creates PushInputAdapterExtern from capsule | `(mgr, engine, pytype, push_mode, (capsule, push_group))` | +| `_c_api_output_adapter` | Creates OutputAdapterExtern from capsule | `(mgr, engine, (input_type, capsule))` | +| `_c_api_adapter_manager_bridge` | Converts C API manager to CSP-compatible format | `(engine, capsule)` | + +These functions: + +1. Extract the VTable from the capsule +1. Create the appropriate native adapter (`PushInputAdapterExtern`, `OutputAdapterExtern`, or `AdapterManagerExtern`) +1. Transfer ownership of the VTable to prevent double-free +1. Return a wrapper object compatible with CSP's wiring layer + +#### Managed Adapter Python Wrapper + +For adapter managers, you need to create a Python class that wraps your C manager and its adapters: + +```python +# managed_adapter.py +import csp +from csp import ts +from csp.impl.__cspimpl import _cspimpl +from csp.impl.pushadapter import PushGroup +from csp.impl.wiring import input_adapter_def, output_adapter_def +from . import _my_native_module # Your C/Rust extension + + +class MyAdapterManager: + """ + Python wrapper for a C API adapter manager. + + The adapter manager pattern allows coordinating multiple adapters that share: + - Startup/shutdown coordination + - Push groups for batched event processing + - Common configuration + """ + + def __init__(self, prefix: str = ""): + self._prefix = prefix + self._push_group = PushGroup() + self._properties = {"prefix": prefix} + + def subscribe(self, ts_type=int, interval_ms=100, push_mode=csp.PushMode.NON_COLLAPSING): + """Create an input adapter managed by this manager.""" + return _managed_input_def( + self, ts_type, interval_ms=interval_ms, + push_mode=push_mode, push_group=self._push_group + ) + + def publish(self, x, prefix=None): + """Create an output adapter managed by this manager.""" + return _managed_output_def(self, x, prefix=prefix or self._prefix) + + def _create(self, engine, memo): + """ + Called by CSP wiring layer to create the native manager. + + This is the key integration point - it bridges the C API capsule + to CSP's expected format. + """ + # Create C API adapter manager capsule + c_api_capsule = _my_native_module._my_adapter_manager(engine, self._properties) + + # Bridge to CSP format (returns AdapterManagerExtern* wrapped in capsule) + return _cspimpl._c_api_adapter_manager_bridge(engine, c_api_capsule) + + +def _create_managed_input(mgr_capsule, engine, pytype, push_mode, scalars): + """Bridge function for managed input adapters.""" + # Extract interval_ms from scalars + interval_ms = 100 + for s in scalars: + if isinstance(s, int) and not isinstance(s, bool): + interval_ms = s + break + + # Create VTable capsule + capsule = _my_native_module._my_input_adapter(interval_ms=interval_ms) + + # Extract push group (last element if present) + push_group = None + if scalars and "PushGroup" in type(scalars[-1]).__name__: + push_group = scalars[-1] + + # Pass manager capsule (not None) to bridge + return _cspimpl._c_api_push_input_adapter( + mgr_capsule, engine, pytype, push_mode, (capsule, push_group) + ) + + +def _create_managed_output(mgr_capsule, engine, scalars): + """Bridge function for managed output adapters.""" + prefix = scalars[1] if len(scalars) > 1 else "" + capsule = _my_native_module._my_output_adapter(prefix=prefix) + return _cspimpl._c_api_output_adapter(mgr_capsule, engine, (int, capsule)) + + +# Managed adapter definitions - note the manager_type argument +_managed_input_def = input_adapter_def( + "my_managed_input", + _create_managed_input, + ts["T"], + MyAdapterManager, # <-- manager type + typ="T", + interval_ms=int, + push_group=(object, None), # Accept push_group kwarg +) + +_managed_output_def = output_adapter_def( + "my_managed_output", + _create_managed_output, + MyAdapterManager, # <-- manager type + input=ts["T"], + prefix=str, +) +``` + +#### Use Managed Adapters in Your Graph + +```python +from datetime import timedelta + +import csp +from csp.utils.datetime import utc_now + +from my_adapter import MyAdapterManager + +@csp.graph +def my_graph(): + # Create a manager - all adapters share this instance + mgr = MyAdapterManager(prefix="[MyApp] ") + + # Subscribe and publish through the manager + data = mgr.subscribe(int, interval_ms=100) + mgr.publish(data) + +csp.run(my_graph, starttime=utc_now(), endtime=timedelta(seconds=10)) +``` + +#### Use in Your Graph + +```python +from datetime import timedelta + +import csp +from csp.utils.datetime import utc_now + +from my_adapter import my_input, LogAdapter + +@csp.graph +def my_graph(): + data = my_input(int, interval_ms=100) + LogAdapter(data, prefix="[MyApp] ") + +csp.run(my_graph, starttime=utc_now(), endtime=timedelta(seconds=10)) +``` + +## See Also + +- [C API Reference](../api-references/C-APIs.md) - Complete API documentation +- [Write Output Adapters](Write-Output-Adapters.md) - Python output adapters +- [Write Realtime Input Adapters](Write-Realtime-Input-Adapters.md) - Python input adapters +- [C API Adapter Example](../../../examples/05_cpp/4_c_api_adapter/) - Working example diff --git a/examples/05_cpp/1_cpp_node/CMakeLists.txt b/examples/05_cpp/1_cpp_node/CMakeLists.txt index 3558941a5..ed02fdd7e 100644 --- a/examples/05_cpp/1_cpp_node/CMakeLists.txt +++ b/examples/05_cpp/1_cpp_node/CMakeLists.txt @@ -2,105 +2,35 @@ cmake_minimum_required(VERSION 3.20.0) project(csp-example-piglatin VERSION "0.0.1") set(CMAKE_CXX_STANDARD 17) -include(CheckCCompilerFlag) +# Find Python +find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) -if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - set(MACOS ON) - set(LINUX OFF) -else() - set(MACOS OFF) - set(LINUX ON) -endif() +# Find CSP include and lib paths +execute_process( + COMMAND "${Python_EXECUTABLE}" -c "import csp; print(csp.get_include_path(), end='')" + OUTPUT_VARIABLE CSP_INCLUDE_DIR) +execute_process( + COMMAND "${Python_EXECUTABLE}" -c "import csp; print(csp.get_lib_path(), end='')" + OUTPUT_VARIABLE CSP_LIB_DIR) -list(PREPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../cpp/cmake/modules") +find_library(CSP_LIBRARY NAMES _cspimpl.so PATHS "${CSP_LIB_DIR}" NO_DEFAULT_PATH) -set(CMAKE_MACOSX_RPATH TRUE) -set(CMAKE_SKIP_RPATH FALSE) -set(CMAKE_SKIP_BUILD_RPATH FALSE) -set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) -set(CMAKE_INSTALL_NAME_DIR "@rpath") +# Position independent code for shared libraries set(CMAKE_POSITION_INDEPENDENT_CODE ON) -if(NOT DEFINED PYTHON_VERSION) - set(PYTHON_VERSION 3.11) -endif() - -find_package(Color) - -if(MACOS) - # fix for threads on osx - # assume built-in pthreads on MacOS - set(CMAKE_THREAD_LIBS_INIT "-lpthread") - set(CMAKE_HAVE_THREADS_LIBRARY 1) - set(CMAKE_USE_WIN32_THREADS_INIT 0) - set(CMAKE_USE_PTHREADS_INIT 1) - set(THREADS_PREFER_PTHREAD_FLAG ON) - - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum OS X deployment version") - - # don't link against build python - # https://blog.tim-smith.us/2015/09/python-extension-modules-os-x/ +# macOS: don't link against build python +if(APPLE) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -undefined dynamic_lookup") - - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-ld_classic") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-ld_classic") - - # Support cross build - check_c_compiler_flag("-arch x86_64" x86_64Supported) - check_c_compiler_flag("-arch arm64" arm64Supported) - - if(x86_64Supported AND arm64Supported) - set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "Build universal architecture for OSX" FORCE) - elseif(x86_64Supported) - set(CMAKE_REQUIRED_LINK_OPTIONS "-arch;x86_64") - set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE STRING "Build universal architecture for OSX" FORCE) - elseif(arm64Supported) - set(CMAKE_REQUIRED_LINK_OPTIONS "-arch;arm64") - set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "Build universal architecture for OSX" FORCE) - endif() - - set(CMAKE_INSTALL_RPATH "@loader_path/../csp/lib") - - message("${Cyan}Use python shared libraries${ColorReset}") - find_package(Python ${PYTHON_VERSION} EXACT REQUIRED COMPONENTS Interpreter Development) - - # Run with exact version so its cached for pybind - find_package(PythonInterp ${PYTHON_VERSION} EXACT REQUIRED) - find_package(PythonLibs ${PYTHON_VERSION} EXACT REQUIRED) - - link_directories(${Python_LIBRARY_DIRS}) -elseif(LINUX) - set(CMAKE_INSTALL_RPATH "\$ORIGIN/../csp/lib") - - # Manylinux docker images have no shared libraries - # The instead use a statically built python. - # Cmake's default FindPython can't find the python headers - # without also finding (or failing to find) the python libraries - # so we use a custom FindPythonHeaders that is the same as the - # default, but ignores when the python libraries can't be found. - message("${Red}Manylinux build has no python shared libraries${ColorReset}") - find_package(Python ${PYTHON_VERSION} EXACT REQUIRED COMPONENTS Interpreter) - find_package(PythonHeaders ${PYTHON_VERSION} EXACT REQUIRED) - - # Run with exact version so its cached for pybind - find_package(PythonInterp ${PYTHON_VERSION} EXACT REQUIRED) endif() -message("${Cyan}Using Python ${Python_VERSION}\nPython_INCLUDE_DIRS: ${Python_INCLUDE_DIRS}\nPython_LIBRARIES: ${Python_LIBRARIES}\nPython_EXECUTABLE: ${Python_EXECUTABLE} ${ColorReset}") -include_directories(${Python_INCLUDE_DIRS}) - -# prefix is _ by default +# Shared library naming conventions set(CMAKE_SHARED_LIBRARY_PREFIX _) - -# shared suffix is .so for both linux and mac set(CMAKE_SHARED_LIBRARY_SUFFIX .so) -find_package(CSP REQUIRED) -message("${Cyan}Found CSP:\n\tincludes in: ${CSP_INCLUDE_DIR}\n\tlibraries in: ${CSP_LIBS_DIR}${ColorReset}") include_directories(${CSP_INCLUDE_DIR}) +include_directories(${Python_INCLUDE_DIRS}) -add_library(piglatin SHARED piglatin.cpp) +add_library(piglatin SHARED cpp/piglatin.cpp) target_link_libraries(piglatin ${CSP_LIBRARY}) install(TARGETS piglatin LIBRARY DESTINATION piglatin) diff --git a/examples/05_cpp/1_cpp_node/README.md b/examples/05_cpp/1_cpp_node/README.md index 3c30ef50f..c2b242b72 100644 --- a/examples/05_cpp/1_cpp_node/README.md +++ b/examples/05_cpp/1_cpp_node/README.md @@ -5,7 +5,7 @@ This is a small example to create a custom C++ node. Compile: ```bash -python setup.py build build_ext --inplace +hatch-build --hooks-only -t wheel ``` Run: @@ -24,3 +24,8 @@ Output: 2020-01-01 00:00:05 input:fun 2020-01-01 00:00:05 output:UNFAY ``` + +> [!WARNING] +> This example is for demonstration, and is a pattern CSP uses internally for fast nodes. +> It is not recommended to use as the C++ API is not stable and may change without notice. Use at your own risk. +> For adapters, we have a stable C API that is recommended to use instead. See [C API Adapter](../4_c_api_adapter/README.md) example for more details. diff --git a/examples/05_cpp/1_cpp_node/cpp/piglatin.cpp b/examples/05_cpp/1_cpp_node/cpp/piglatin.cpp new file mode 100644 index 000000000..be6894585 --- /dev/null +++ b/examples/05_cpp/1_cpp_node/cpp/piglatin.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include + +namespace csp::piglatin +{ + +DECLARE_CPPNODE( piglatin ) +{ + INIT_CPPNODE( piglatin ) + {} + + TS_INPUT( std::string, x ); + SCALAR_INPUT( bool, capitalize ); + // ALARM(Generic, alarm); + TS_OUTPUT( std::string ); + + START() + {} + + INVOKE() + { + if( csp.ticked( x ) && csp.valid( x ) ) + { + std::string str = x.lastValue(); + if( capitalize ) + { + std::transform( str.begin(), str.end(), str.begin(), ::toupper ); + } + RETURN( str.substr( 1, std::string::npos ) + str[0] + ( capitalize ? "AY" : "ay" ) ); + } + } +}; + +EXPORT_CPPNODE( piglatin ); +} + +REGISTER_CPPNODE( csp::piglatin, piglatin ); + +static PyModuleDef _piglatin_module = { + PyModuleDef_HEAD_INIT, + "_piglatin", + "_piglatin c++ module", + -1, + NULL, NULL, NULL, NULL, NULL +}; + +PyMODINIT_FUNC PyInit__piglatin( void ) +{ + PyObject * m; + + m = PyModule_Create( &_piglatin_module ); + if( m == NULL ) + return NULL; + + if( !csp::python::InitHelper::instance().execute( m ) ) + return NULL; + + return m; +} diff --git a/examples/05_cpp/1_cpp_node/piglatin.cpp b/examples/05_cpp/1_cpp_node/piglatin.cpp deleted file mode 100644 index 6b4327c82..000000000 --- a/examples/05_cpp/1_cpp_node/piglatin.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include -#include -#include -#include - -namespace csp::piglatin -{ - -DECLARE_CPPNODE(piglatin) -{ - INIT_CPPNODE(piglatin) - {} - - TS_INPUT(std::string, x); - SCALAR_INPUT(bool, capitalize); - // ALARM(Generic, alarm); - TS_OUTPUT(std::string); - - START() - {} - - INVOKE() - { - if(csp.ticked(x) && csp.valid(x)) - { - std::string str = x.lastValue(); - if (capitalize) - { - std::transform(str.begin(), str.end(), str.begin(), ::toupper); - } - RETURN(str.substr(1, std::string::npos) + str[0] + (capitalize ? "AY" : "ay")); - } - } -}; - -EXPORT_CPPNODE(piglatin); -} - -REGISTER_CPPNODE(csp::piglatin, piglatin); - -static PyModuleDef _piglatin_module = { - PyModuleDef_HEAD_INIT, - "_piglatin", - "_piglatin c++ module", - -1, - NULL, NULL, NULL, NULL, NULL -}; - -PyMODINIT_FUNC PyInit__piglatin(void) -{ - PyObject* m; - - m = PyModule_Create(&_piglatin_module); - if(m == NULL) - return NULL; - - if(!csp::python::InitHelper::instance().execute(m)) - return NULL; - - return m; -} diff --git a/examples/05_cpp/1_cpp_node/piglatin/__main__.py b/examples/05_cpp/1_cpp_node/piglatin/__main__.py index 8b2962af1..665b8a405 100644 --- a/examples/05_cpp/1_cpp_node/piglatin/__main__.py +++ b/examples/05_cpp/1_cpp_node/piglatin/__main__.py @@ -1,32 +1,34 @@ from datetime import datetime, timedelta -import csp +from csp import Outputs, curve, graph, print, run, ts from . import piglatin -if __name__ == "__main__": - @csp.graph - def my_graph(): - st = datetime(2020, 1, 1) - - # curve of values - names = csp.curve( - str, - [ - (st + timedelta(seconds=0.5), "pig"), - (st + timedelta(seconds=1.5), "latin"), - (st + timedelta(seconds=5), "fun"), - ], - ) - - # piglatinify - csp.print("input", names) - piglatinify = piglatin(names, capitalize=True) - csp.print("output", piglatinify) +@graph +def my_graph() -> Outputs(ts[str]): + st = datetime(2020, 1, 1) + + # curve of values + names = curve( + str, + [ + (st + timedelta(seconds=0.5), "pig"), + (st + timedelta(seconds=1.5), "latin"), + (st + timedelta(seconds=5), "fun"), + ], + ) + + # piglatinify + print("input", names) + piglatinify = piglatin(names, capitalize=True) + print("output", piglatinify) + return piglatinify + +if __name__ == "__main__": start = datetime(2020, 1, 1) - csp.run(my_graph, starttime=start) + run(my_graph, starttime=start) # Output: # 2020-01-01 00:00:00.500000 input:pig diff --git a/examples/05_cpp/1_cpp_node/piglatin/test_piglatin.py b/examples/05_cpp/1_cpp_node/piglatin/test_piglatin.py new file mode 100644 index 000000000..3bfb4098f --- /dev/null +++ b/examples/05_cpp/1_cpp_node/piglatin/test_piglatin.py @@ -0,0 +1,26 @@ +from datetime import datetime + +import csp + +from .__main__ import my_graph + + +def test_piglatin(): + start = datetime(2020, 1, 1) + res = csp.run(my_graph, starttime=start) + assert res == { + 0: [ + ( + datetime(2020, 1, 1, 0, 0, 0, 500000), + "IGPAY", + ), + ( + datetime(2020, 1, 1, 0, 0, 1, 500000), + "ATINLAY", + ), + ( + datetime(2020, 1, 1, 0, 0, 5), + "UNFAY", + ), + ], + } diff --git a/examples/05_cpp/1_cpp_node/pyproject.toml b/examples/05_cpp/1_cpp_node/pyproject.toml index 625989b0a..530abf198 100644 --- a/examples/05_cpp/1_cpp_node/pyproject.toml +++ b/examples/05_cpp/1_cpp_node/pyproject.toml @@ -1,12 +1,18 @@ [build-system] -requires = ["cmake", "csp", "scikit-build"] -build-backend="setuptools.build_meta" +requires = ["hatchling", "hatch-cpp", "csp"] +build-backend = "hatchling.build" [project] name = "csp-example-piglatin" authors = [{name = "the csp authors", email = "CSPOpenSource@point72.com"}] -description="csp example of C++ node" +description = "csp example of C++ node" readme = "README.md" version = "0.0.1" -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = ["csp"] + +[tool.hatch.build.targets.wheel] +packages = ["piglatin"] + +[tool.hatch.build.hooks.hatch-cpp] +cmake = { root = "." } diff --git a/examples/05_cpp/1_cpp_node/setup.py b/examples/05_cpp/1_cpp_node/setup.py deleted file mode 100644 index 3b178fe25..000000000 --- a/examples/05_cpp/1_cpp_node/setup.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -import os.path -import sys - -from skbuild import setup - -python_version = f"{sys.version_info.major}.{sys.version_info.minor}" -cmake_args = [f"-DPYTHON_VERSION={python_version}"] - -if "CXX" in os.environ: - cmake_args.append(f"-DCMAKE_CXX_COMPILER={os.environ['CXX']}") - -print(f"CMake Args: {cmake_args}") -setup( - name="csp-example-piglatin", - version="0.0.1", - packages=["piglatin"], - cmake_install_dir=".", - cmake_args=cmake_args, - # cmake_with_sdist=True, -) diff --git a/examples/05_cpp/2_cpp_node_with_struct/CMakeLists.txt b/examples/05_cpp/2_cpp_node_with_struct/CMakeLists.txt index 0386265a2..2b530a82b 100644 --- a/examples/05_cpp/2_cpp_node_with_struct/CMakeLists.txt +++ b/examples/05_cpp/2_cpp_node_with_struct/CMakeLists.txt @@ -2,111 +2,44 @@ cmake_minimum_required(VERSION 3.20.0) project(csp-example-struct VERSION "0.0.1") set(CMAKE_CXX_STANDARD 17) -include(CheckCCompilerFlag) +# Find Python +find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) -if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - set(MACOS ON) - set(LINUX OFF) -else() - set(MACOS OFF) - set(LINUX ON) -endif() +# Find CSP include and lib paths +execute_process( + COMMAND "${Python_EXECUTABLE}" -c "import csp; print(csp.get_include_path(), end='')" + OUTPUT_VARIABLE CSP_INCLUDE_DIR) +execute_process( + COMMAND "${Python_EXECUTABLE}" -c "import csp; print(csp.get_lib_path(), end='')" + OUTPUT_VARIABLE CSP_LIB_DIR) -list(PREPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../cpp/cmake/modules") -set(ENV{PYTHONPATH} "${CMAKE_SOURCE_DIR}/../../../:$ENV{PYTHONPATH}") +find_library(CSP_LIBRARY NAMES _cspimpl.so PATHS "${CSP_LIB_DIR}" NO_DEFAULT_PATH) -set(CMAKE_MACOSX_RPATH TRUE) -set(CMAKE_SKIP_RPATH FALSE) -set(CMAKE_SKIP_BUILD_RPATH FALSE) -set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) -set(CMAKE_INSTALL_NAME_DIR "@rpath") +# Position independent code for shared libraries set(CMAKE_POSITION_INDEPENDENT_CODE ON) -if(NOT DEFINED PYTHON_VERSION) - set(PYTHON_VERSION 3.11) -endif() - -find_package(Color) - -if(MACOS) - # fix for threads on osx - # assume built-in pthreads on MacOS - set(CMAKE_THREAD_LIBS_INIT "-lpthread") - set(CMAKE_HAVE_THREADS_LIBRARY 1) - set(CMAKE_USE_WIN32_THREADS_INIT 0) - set(CMAKE_USE_PTHREADS_INIT 1) - set(THREADS_PREFER_PTHREAD_FLAG ON) - - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum OS X deployment version") - - # don't link against build python - # https://blog.tim-smith.us/2015/09/python-extension-modules-os-x/ +# macOS: don't link against build python +if(APPLE) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -undefined dynamic_lookup") - - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-ld_classic") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-ld_classic") - - # Support cross build - check_c_compiler_flag("-arch x86_64" x86_64Supported) - check_c_compiler_flag("-arch arm64" arm64Supported) - - if(x86_64Supported AND arm64Supported) - set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "Build universal architecture for OSX" FORCE) - elseif(x86_64Supported) - set(CMAKE_REQUIRED_LINK_OPTIONS "-arch;x86_64") - set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE STRING "Build universal architecture for OSX" FORCE) - elseif(arm64Supported) - set(CMAKE_REQUIRED_LINK_OPTIONS "-arch;arm64") - set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "Build universal architecture for OSX" FORCE) - endif() - - set(CMAKE_INSTALL_RPATH "@loader_path/../csp/lib") - - message("${Cyan}Use python shared libraries${ColorReset}") - find_package(Python ${PYTHON_VERSION} EXACT REQUIRED COMPONENTS Interpreter Development) - - # Run with exact version so its cached for pybind - find_package(PythonInterp ${PYTHON_VERSION} EXACT REQUIRED) - find_package(PythonLibs ${PYTHON_VERSION} EXACT REQUIRED) - - link_directories(${Python_LIBRARY_DIRS}) -elseif(LINUX) - set(CMAKE_INSTALL_RPATH "\$ORIGIN/../csp/lib") - - # Manylinux docker images have no shared libraries - # The instead use a statically built python. - # Cmake's default FindPython can't find the python headers - # without also finding (or failing to find) the python libraries - # so we use a custom FindPythonHeaders that is the same as the - # default, but ignores when the python libraries can't be found. - message("${Red}Manylinux build has no python shared libraries${ColorReset}") - find_package(Python ${PYTHON_VERSION} EXACT REQUIRED COMPONENTS Interpreter) - find_package(PythonHeaders ${PYTHON_VERSION} EXACT REQUIRED) - - # Run with exact version so its cached for pybind - find_package(PythonInterp ${PYTHON_VERSION} EXACT REQUIRED) endif() -message("${Cyan}Using Python ${Python_VERSION}\nPython_INCLUDE_DIRS: ${Python_INCLUDE_DIRS}\nPython_LIBRARIES: ${Python_LIBRARIES}\nPython_EXECUTABLE: ${Python_EXECUTABLE} ${ColorReset}") -include_directories(${Python_INCLUDE_DIRS}) - -# prefix is _ by default +# Shared library naming conventions set(CMAKE_SHARED_LIBRARY_PREFIX _) - -# shared suffix is .so for both linux and mac set(CMAKE_SHARED_LIBRARY_SUFFIX .so) -find_package(CSP REQUIRED) -message("${Cyan}Found CSP:\n\tincludes in: ${CSP_INCLUDE_DIR}\n\tlibraries in: ${CSP_LIBS_DIR}${ColorReset}") include_directories(${CSP_INCLUDE_DIR}) +include_directories(${Python_INCLUDE_DIRS}) -find_package(csp_autogen REQUIRED) -csp_autogen(mystruct.struct mystruct STRUCT_AUTOGEN_HEADER STRUCT_AUTOGEN_SOURCE) +set(MYSTRUCT_SRC "${CMAKE_CURRENT_BINARY_DIR}/mystruct.cpp") +set(MYSTRUCT_HDR "${CMAKE_CURRENT_BINARY_DIR}/mystruct.h") -add_library(mystruct SHARED struct.cpp ${STRUCT_AUTOGEN_SOURCE}) +add_custom_command(OUTPUT "${MYSTRUCT_SRC}" "${MYSTRUCT_HDR}" + COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}" "${Python_EXECUTABLE}" -m csp.build.csp_autogen -m mystruct.struct -d "${CMAKE_CURRENT_BINARY_DIR}" -o mystruct +) + +add_library(mystruct SHARED cpp/struct.cpp ${MYSTRUCT_SRC}) target_link_libraries(mystruct ${CSP_LIBRARY}) -target_include_directories(mystruct PRIVATE "${CMAKE_BINARY_DIR}/csp_autogen") -set_target_properties(mystruct PROPERTIES PUBLIC_HEADER "${STRUCT_AUTOGEN_HEADER}") +target_include_directories(mystruct PRIVATE "${CMAKE_BINARY_DIR}") +set_target_properties(mystruct PROPERTIES PUBLIC_HEADER "${MYSTRUCT_HDR}") install(TARGETS mystruct LIBRARY DESTINATION mystruct PUBLIC_HEADER DESTINATION mystruct/include) diff --git a/examples/05_cpp/2_cpp_node_with_struct/README.md b/examples/05_cpp/2_cpp_node_with_struct/README.md index d557af3c1..ffe8cf2fc 100644 --- a/examples/05_cpp/2_cpp_node_with_struct/README.md +++ b/examples/05_cpp/2_cpp_node_with_struct/README.md @@ -5,7 +5,7 @@ This is a small example to create a custom C++ node, interfacing with `csp.Struc Compile: ```bash -python setup.py build build_ext --inplace +hatch-build --hooks-only -t wheel ``` Run: @@ -13,3 +13,8 @@ Run: ```bash python -m mystruct ``` + +> [!WARNING] +> This example is for demonstration, and is a pattern CSP uses internally for fast nodes. +> It is not recommended to use as the C++ API is not stable and may change without notice. Use at your own risk. +> For adapters, we have a stable C API that is recommended to use instead. See [C API Adapter](../4_c_api_adapter/README.md) example for more details. diff --git a/examples/05_cpp/2_cpp_node_with_struct/cpp/struct.cpp b/examples/05_cpp/2_cpp_node_with_struct/cpp/struct.cpp new file mode 100644 index 000000000..93c96d7df --- /dev/null +++ b/examples/05_cpp/2_cpp_node_with_struct/cpp/struct.cpp @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// autogenerated +#include "mystruct.h" + +namespace csp::mystruct +{ + +DECLARE_CPPNODE( use_struct_generic ) +{ + INIT_CPPNODE( use_struct_generic ) {} + + TS_INPUT( StructPtr, x ); + TS_OUTPUT( StructPtr ); + + START() + { + auto * structType = static_cast( x.type() ); + m_fieldAccess = structType -> meta() -> field( "a" ); + if( !m_fieldAccess ) + CSP_THROW( TypeError, "Struct " << structType -> meta() -> name() << " has no field named " << "a" ); + } + + INVOKE() + { + if( m_fieldAccess -> isSet( x.lastValue().get() ) ) + { + auto copy = x.lastValue() -> copy(); + switchCspType( m_fieldAccess -> type(), [this, ©]( auto tag ) + { + using ElemT = typename decltype( tag )::type; + if( std::is_same() ) + { + m_fieldAccess -> setValue( copy.get(), 0 ); + } + } ); + RETURN( copy ); + } + RETURN( x ); + } + +private: + StructFieldPtr m_fieldAccess; +}; + +DECLARE_CPPNODE( use_struct_specific ) +{ + INIT_CPPNODE( use_struct_specific ) {} + + // Use StructPtr like use_struct_generic for safer memory handling + TS_INPUT( StructPtr, x ); + TS_OUTPUT( StructPtr ); + + INVOKE() + { + // Copy the struct + auto copy = x.lastValue() -> copy(); + // Get typed pointers (input is const, copy is mutable) + const auto * input = static_cast( x.lastValue().get() ); + auto * myCopy = static_cast( copy.get() ); + // Uppercase the b field if set + if( input -> b_isSet() ) + { + std::string str = input -> b(); + std::transform( str.begin(), str.end(), str.begin(), ::toupper ); + myCopy -> set_b( str ); + } + RETURN( copy ); + } +}; + + +EXPORT_CPPNODE( use_struct_generic ); +EXPORT_CPPNODE( use_struct_specific ); + +} + +REGISTER_CPPNODE( csp::mystruct, use_struct_generic ); +REGISTER_CPPNODE( csp::mystruct, use_struct_specific ); + +static PyModuleDef _mystruct_module = { + PyModuleDef_HEAD_INIT, + "_mystruct", + "_mystruct c++ module", + -1, + NULL, NULL, NULL, NULL, NULL +}; + +PyMODINIT_FUNC PyInit__mystruct( void ) +{ + PyObject * m; + + m = PyModule_Create( &_mystruct_module ); + if( m == NULL ) + return NULL; + + if( !csp::python::InitHelper::instance().execute( m ) ) + return NULL; + + return m; +} diff --git a/examples/05_cpp/2_cpp_node_with_struct/mystruct/__main__.py b/examples/05_cpp/2_cpp_node_with_struct/mystruct/__main__.py index f9141aed7..a543d3ce8 100644 --- a/examples/05_cpp/2_cpp_node_with_struct/mystruct/__main__.py +++ b/examples/05_cpp/2_cpp_node_with_struct/mystruct/__main__.py @@ -5,17 +5,20 @@ from .node import use_struct_generic, use_struct_specific from .struct import MyStruct -if __name__ == "__main__": - @csp.graph - def my_graph(): - st = csp.const(MyStruct(a=1, b="abc")) +@csp.graph +def my_graph() -> csp.Outputs(generic=csp.ts[MyStruct], specific=csp.ts[MyStruct]): + st = csp.const(MyStruct(a=1, b="abc")) + + csp.print("input", st) + generic = use_struct_generic(st) + csp.print("use_struct_generic", generic) + specific = use_struct_specific(generic) + csp.print("use_struct_specific", specific) - csp.print("input", st) - generic = use_struct_generic(st) - csp.print("use_struct_generic", generic) - specific = use_struct_specific(generic) - csp.print("use_struct_specific", specific) + csp.output(generic=generic, specific=specific) + +if __name__ == "__main__": start = datetime(2020, 1, 1) csp.run(my_graph, starttime=start) diff --git a/examples/05_cpp/2_cpp_node_with_struct/mystruct/test_mystruct.py b/examples/05_cpp/2_cpp_node_with_struct/mystruct/test_mystruct.py new file mode 100644 index 000000000..33b19b542 --- /dev/null +++ b/examples/05_cpp/2_cpp_node_with_struct/mystruct/test_mystruct.py @@ -0,0 +1,55 @@ +from datetime import datetime + +import csp +from mystruct.node import use_struct_generic, use_struct_specific +from mystruct.struct import MyStruct + + +@csp.graph +def simple_graph() -> csp.Outputs(generic=csp.ts[MyStruct], specific=csp.ts[MyStruct]): + """Test graph that uses both generic and specific struct nodes.""" + st = csp.const(MyStruct(a=1, b="abc")) + generic = use_struct_generic(st) + specific = use_struct_specific(generic) + csp.output(generic=generic, specific=specific) + + +def test_mystruct(): + start = datetime(2020, 1, 1) + ret = csp.run(simple_graph, starttime=start) + + # Graph should return generic and specific outputs + assert "generic" in ret + assert "specific" in ret + assert len(ret["generic"]) == 1 + assert len(ret["specific"]) == 1 + + # Verify the struct values by accessing fields directly + generic_value = ret["generic"][0][1] + specific_value = ret["specific"][0][1] + + assert isinstance(generic_value, MyStruct) + assert isinstance(specific_value, MyStruct) + + # use_struct_generic passes through unchanged + assert generic_value.a == 1 + assert generic_value.b == "abc" + + # use_struct_specific uppercases the 'b' field + assert specific_value.a == 1 + assert specific_value.b == "ABC" + + +def test_struct_creation(): + """Basic test that struct creation and field access works.""" + s = MyStruct(a=42, b="hello") + assert s.a == 42 + assert s.b == "hello" + + +def test_node_import(): + """Test that the C++ node can be imported.""" + from mystruct.node import use_struct_generic, use_struct_specific + + assert use_struct_generic is not None + assert use_struct_specific is not None diff --git a/examples/05_cpp/2_cpp_node_with_struct/pyproject.toml b/examples/05_cpp/2_cpp_node_with_struct/pyproject.toml index e991ab32f..cb406e889 100644 --- a/examples/05_cpp/2_cpp_node_with_struct/pyproject.toml +++ b/examples/05_cpp/2_cpp_node_with_struct/pyproject.toml @@ -1,12 +1,18 @@ [build-system] -requires = ["cmake", "csp", "scikit-build"] -build-backend="setuptools.build_meta" +requires = ["hatchling", "hatch-cpp", "csp"] +build-backend = "hatchling.build" [project] name = "csp-example-struct" authors = [{name = "the csp authors", email = "CSPOpenSource@point72.com"}] -description="csp example of C++ node with csp.Structs" +description = "csp example of C++ node with csp.Structs" readme = "README.md" version = "0.0.1" -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = ["csp"] + +[tool.hatch.build.targets.wheel] +packages = ["mystruct"] + +[tool.hatch.build.hooks.hatch-cpp] +cmake = { root = "." } diff --git a/examples/05_cpp/2_cpp_node_with_struct/setup.py b/examples/05_cpp/2_cpp_node_with_struct/setup.py deleted file mode 100644 index 8975b5cdd..000000000 --- a/examples/05_cpp/2_cpp_node_with_struct/setup.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -import os.path -import sys - -from skbuild import setup - -python_version = f"{sys.version_info.major}.{sys.version_info.minor}" -cmake_args = [f"-DPYTHON_VERSION={python_version}"] - -if "CXX" in os.environ: - cmake_args.append(f"-DCMAKE_CXX_COMPILER={os.environ['CXX']}") - -print(f"CMake Args: {cmake_args}") -setup( - name="csp-example-struct", - version="0.0.1", - packages=["mystruct"], - cmake_install_dir=".", - cmake_args=cmake_args, - # cmake_with_sdist=True, -) diff --git a/examples/05_cpp/2_cpp_node_with_struct/struct.cpp b/examples/05_cpp/2_cpp_node_with_struct/struct.cpp deleted file mode 100644 index d385c93b9..000000000 --- a/examples/05_cpp/2_cpp_node_with_struct/struct.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -// autogenerated -#include "mystruct.h" - -namespace csp::mystruct -{ - -DECLARE_CPPNODE(use_struct_generic) -{ - INIT_CPPNODE(use_struct_generic) {} - - TS_INPUT(StructPtr, x); - TS_OUTPUT(StructPtr); - - START() - { - auto * structType = static_cast( x.type() ); - m_fieldAccess = structType -> meta() -> field("a"); - if( !m_fieldAccess ) - CSP_THROW( TypeError, "Struct " << structType -> meta() -> name() << " has no field named " << "a" ); - } - - INVOKE() - { - if( m_fieldAccess -> isSet( x.lastValue().get() ) ) - { - auto copy = x.lastValue()->copy(); - switchCspType( m_fieldAccess -> type(), [this,©]( auto tag ) - { - using ElemT = typename decltype(tag)::type; - if(std::is_same()) { - m_fieldAccess -> setValue( copy.get(), 0); - } - }); - RETURN(copy); - } - RETURN(x); - } - -private: - StructFieldPtr m_fieldAccess; -}; - -DECLARE_CPPNODE(use_struct_specific) -{ - INIT_CPPNODE(use_struct_specific) {} - - TS_INPUT(csp::autogen::MyStruct*, x); - TS_OUTPUT(csp::autogen::MyStruct*); - - INVOKE() - { - if (x.lastValue()->b_isSet()) { - auto str = x.lastValue()->b(); - std::transform(str.begin(), str.end(), str.begin(), ::toupper); - x.lastValue()->set_b(str); - } - RETURN(x); - } -}; - - -EXPORT_CPPNODE(use_struct_generic); -EXPORT_CPPNODE(use_struct_specific); - -} - -REGISTER_CPPNODE(csp::mystruct, use_struct_generic); -REGISTER_CPPNODE(csp::mystruct, use_struct_specific); - -static PyModuleDef _mystruct_module = { - PyModuleDef_HEAD_INIT, - "_mystruct", - "_mystruct c++ module", - -1, - NULL, NULL, NULL, NULL, NULL -}; - -PyMODINIT_FUNC PyInit__mystruct(void) -{ - PyObject* m; - - m = PyModule_Create(&_mystruct_module); - if(m == NULL) - return NULL; - - if(!csp::python::InitHelper::instance().execute(m)) - return NULL; - - return m; -} diff --git a/examples/05_cpp/3_cpp_adapter/CMakeLists.txt b/examples/05_cpp/3_cpp_adapter/CMakeLists.txt new file mode 100644 index 000000000..2ce665712 --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 3.20.0) +project(csp-example-counter-adapter VERSION "0.0.1") +set(CMAKE_CXX_STANDARD 17) + +# Find Python +find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) + +# Find CSP include and lib paths +execute_process( + COMMAND "${Python_EXECUTABLE}" -c "import csp; print(csp.get_include_path(), end='')" + OUTPUT_VARIABLE CSP_INCLUDE_DIR) +execute_process( + COMMAND "${Python_EXECUTABLE}" -c "import csp; print(csp.get_lib_path(), end='')" + OUTPUT_VARIABLE CSP_LIB_DIR) + +find_library(CSP_LIBRARY NAMES _cspimpl.so PATHS "${CSP_LIB_DIR}" NO_DEFAULT_PATH) + +# Position independent code for shared libraries +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# macOS: don't link against build python +if(APPLE) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -undefined dynamic_lookup") +endif() + +# Shared library naming conventions +set(CMAKE_SHARED_LIBRARY_PREFIX _) +set(CMAKE_SHARED_LIBRARY_SUFFIX .so) + +include_directories(${CSP_INCLUDE_DIR}) +include_directories(${Python_INCLUDE_DIRS}) + +# The Counter Adapter library +add_library(counteradapter SHARED + cpp/CounterAdapterManager.cpp + cpp/CounterInputAdapter.cpp + cpp/CounterOutputAdapter.cpp + cpp/counteradapterimpl.cpp +) +target_link_libraries(counteradapter ${CSP_LIBRARY}) +target_include_directories(counteradapter PRIVATE "${CMAKE_SOURCE_DIR}") + +install(TARGETS counteradapter LIBRARY DESTINATION counteradapter) diff --git a/examples/05_cpp/3_cpp_adapter/README.md b/examples/05_cpp/3_cpp_adapter/README.md new file mode 100644 index 000000000..ac4b33d62 --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/README.md @@ -0,0 +1,168 @@ +# Custom C++ Adapter + +> [!WARNING] +> **Internal API - Use with Caution** +> +> This example demonstrates CSP's internal C++ adapter pattern. The C++ API is +> **not stable** and may change without notice. For production adapters, use +> the stable [C API](../4_c_api_adapter/README.md) instead. +> +> This example is useful for understanding how CSP adapters work internally, +> or if you need features not yet exposed through the C API. + +## Overview + +This example demonstrates how to create a complete C++ adapter ecosystem for CSP, +including: + +- **`CounterAdapterManager`** - Manages adapter lifecycle and coordinates input/output +- **`CounterInputAdapter`** - A push input adapter that generates sequential counter values +- **`CounterOutputAdapter`** - An output adapter that logs values to stdout + +The adapter generates counter values at a configurable interval, demonstrating +the push input adapter pattern commonly used for real-time data sources. + +## Key Concepts + +### AdapterManager + +The `AdapterManager` is responsible for: + +- Managing the lifecycle of all adapters it creates +- Coordinating start/stop across adapters +- Providing factory methods for creating input and output adapters +- Managing background threads (for real-time push adapters) + +```cpp +class CounterAdapterManager final : public csp::AdapterManager +{ +public: + void start( DateTime starttime, DateTime endtime ) override; + void stop() override; + + PushInputAdapter * getInputAdapter( ... ); + OutputAdapter * getOutputAdapter( ... ); +}; +``` + +### PushInputAdapter + +For real-time data sources, use `PushInputAdapter` which allows pushing data +from background threads: + +```cpp +class CounterInputAdapter final : public PushInputAdapter +{ + // Data is pushed via pushTick() from a background thread +}; + +// In the adapter manager's background thread: +void CounterAdapterManager::pushValue( int64_t value ) +{ + PushBatch batch( rootEngine() ); + m_inputAdapter->pushTick( value, &batch ); +} +``` + +### OutputAdapter + +Output adapters receive data from the graph and perform side effects: + +```cpp +class CounterOutputAdapter final : public OutputAdapter +{ + void executeImpl() override + { + int64_t value = input()->lastValueTyped(); + std::cout << "Received: " << value << std::endl; + } +}; +``` + +### Python Bindings + +Use CSP's registration macros to expose C++ adapters to Python: + +```cpp +REGISTER_ADAPTER_MANAGER( _counter_adapter_manager, create_counter_adapter_manager ); +REGISTER_INPUT_ADAPTER( _counter_input_adapter, create_counter_input_adapter ); +REGISTER_OUTPUT_ADAPTER( _counter_output_adapter, create_counter_output_adapter ); +``` + +## Building + +```bash +cd examples/05_cpp/3_cpp_adapter +python setup.py build build_ext --inplace +``` + +## Usage + +After building, you can use the adapter in Python: + +```python +import csp +from csp.utils.datetime import utc_now + +from datetime import timedelta + +from counteradapter import CounterAdapterManager + +@csp.graph +def my_graph(): + # Create manager with 100ms interval, max 10 counts + mgr = CounterAdapterManager(interval_ms=100, max_count=10) + + # Subscribe to counter values + data = mgr.subscribe() + + # Print values + csp.print("Counter", data) + + # Also publish to output adapter + mgr.publish(data) + +# Run for 2 seconds in realtime mode +csp.run(my_graph, starttime=utc_now(), endtime=timedelta(seconds=2), realtime=True) +``` + +Or run the example directly: + +```bash +python -m counteradapter +``` + +## API Reference + +### CounterAdapterManager + +| Parameter | Type | Default | Description | +| ------------- | ----- | ------- | ---------------------------------------------- | +| `interval_ms` | `int` | `1000` | Interval between counter ticks in milliseconds | +| `max_count` | `int` | `0` | Maximum count before stopping (0 = unlimited) | + +### Methods + +- **`subscribe() -> csp.ts[int]`** - Subscribe to counter values +- **`publish(data: csp.ts[int])`** - Publish values to the output adapter + +## CSP Internal Headers Used + +This example uses the following internal CSP headers: + +```cpp +#include // Base AdapterManager class +#include // PushInputAdapter for real-time data +#include // Base OutputAdapter class +#include // Configuration dictionary +#include // Python bindings +#include // Input adapter bindings +#include // Output adapter bindings +#include // Registration macros +``` + +## See Also + +- [C API Adapter](../4_c_api_adapter/README.md) - Stable C API for adapters (recommended) +- [Rust C API Adapter](../5_c_api_adapter_rust/README.md) - Rust adapter using the C API +- [CSP Adapters Documentation](../../../docs/wiki/how-tos/Write-Adapters.md) diff --git a/examples/05_cpp/3_cpp_adapter/counteradapter/__init__.py b/examples/05_cpp/3_cpp_adapter/counteradapter/__init__.py new file mode 100644 index 000000000..0e5e4eaa5 --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/counteradapter/__init__.py @@ -0,0 +1,93 @@ +""" +Counter Adapter - Python module for the Counter adapter example + +This module demonstrates how to create a Python wrapper around C++ adapters. +It provides a clean Python interface to the CounterAdapterManager, +CounterInputAdapter, and CounterOutputAdapter. + +Usage: + from counteradapter import CounterAdapterManager + + @csp.graph + def my_graph(): + counter_mgr = CounterAdapterManager(interval_ms=100) + data = counter_mgr.subscribe() + counter_mgr.publish(data) + return data + + result = csp.run(my_graph, starttime=datetime.now(), endtime=timedelta(seconds=5)) +""" + +import csp +from csp.impl.wiring import input_adapter_def, output_adapter_def + +# Import the C++ extension module +from . import _counteradapter + + +class CounterAdapterManager: + """ + A simple example adapter manager that generates sequential counter values. + + This demonstrates the basic pattern for creating a Python wrapper around + a C++ AdapterManager. + + Args: + interval_ms: Interval between counter ticks in milliseconds (default: 1000) + max_count: Maximum count before stopping, 0 for unlimited (default: 0) + """ + + def __init__(self, interval_ms: int = 1000, max_count: int = 0): + self._properties = { + "interval_ms": interval_ms, + "max_count": max_count, + } + + def _create(self, engine, memo): + """Create the C++ adapter manager.""" + return _counteradapter._counter_adapter_manager(engine, self._properties) + + def subscribe(self) -> csp.ts[int]: + """ + Subscribe to counter values. + + Returns a time series of integer counter values that tick at the + configured interval. + + Returns: + csp.ts[int]: Time series of counter values + """ + return _counter_input_adapter(self, typ=int, properties={}, push_mode=csp.PushMode.NON_COLLAPSING) + + def publish(self, data: csp.ts[int]): + """ + Publish values to the output adapter. + + This will log the values to stdout. + + Args: + data: Time series of integer values to publish + """ + _counter_output_adapter(self, data, typ=int, properties={}) + + +_counter_input_adapter = input_adapter_def( + "counter_input_adapter", + _counteradapter._counter_input_adapter, + csp.ts["T"], + CounterAdapterManager, + typ="T", + properties=dict, +) + +_counter_output_adapter = output_adapter_def( + "counter_output_adapter", + _counteradapter._counter_output_adapter, + CounterAdapterManager, + input=csp.ts["T"], + typ="T", + properties=dict, +) + + +__all__ = ["CounterAdapterManager"] diff --git a/examples/05_cpp/3_cpp_adapter/counteradapter/__main__.py b/examples/05_cpp/3_cpp_adapter/counteradapter/__main__.py new file mode 100644 index 000000000..d8fb560f5 --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/counteradapter/__main__.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +""" +Example usage of the Counter Adapter + +This script demonstrates how to use the CounterAdapterManager to generate +and process sequential counter values using CSP's reactive programming model. + +To run this example, first build the adapter: + cd examples/05_cpp/3_cpp_adapter + mkdir build && cd build + cmake .. -DPYTHON_VERSION=$(python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')") + make -j + +Then add the build directory to your Python path and run: + PYTHONPATH=build:$PYTHONPATH python example.py +""" + +from datetime import timedelta + +import csp +from counteradapter import CounterAdapterManager +from csp import ts +from csp.utils.datetime import utc_now + + +@csp.node +def process_counter(value: ts[int]) -> ts[str]: + """Process counter values and return a formatted string.""" + if csp.ticked(value): + return f"Counter is at: {value}" + + +@csp.graph +def counter_graph() -> csp.OutputBasket(dict[str, csp.ts[int]]): + """ + Example graph using the CounterAdapterManager. + + Creates a counter that ticks every 100ms and processes the values. + """ + # Create the adapter manager with a 100ms interval + counter_mgr = CounterAdapterManager(interval_ms=100, max_count=10) + + # Subscribe to counter values + counter_values = counter_mgr.subscribe() + + # Process the values + formatted = process_counter(counter_values) + + # Print the formatted values + csp.print("Formatted", formatted) + + # Also publish to the output adapter (logs to stdout) + counter_mgr.publish(counter_values) + + # Return the raw values for inspection + return {"counter": counter_values} + + +def main(): + """Run the counter example.""" + print("Starting Counter Adapter Example") + print("=" * 40) + + # Run the graph for 2 seconds + start = utc_now() + end = start + timedelta(seconds=2) + + result = csp.run(counter_graph, starttime=start, endtime=end, realtime=True) + + print("=" * 40) + print(f"Received {len(result.get('counter', []))} counter values") + + if result.get("counter"): + print(f"First value: {result['counter'][0]}") + print(f"Last value: {result['counter'][-1]}") + + +if __name__ == "__main__": + main() diff --git a/examples/05_cpp/3_cpp_adapter/counteradapter/test_counteradapter.py b/examples/05_cpp/3_cpp_adapter/counteradapter/test_counteradapter.py new file mode 100644 index 000000000..c971d48f8 --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/counteradapter/test_counteradapter.py @@ -0,0 +1,20 @@ +from datetime import timedelta + +import csp +from csp.utils.datetime import utc_now + +from .__main__ import counter_graph + + +def test_counteradapter(): + start = utc_now() + end = start + timedelta(seconds=2) + + result = csp.run(counter_graph, starttime=start, endtime=end, realtime=True) + + # Verify we got counter values + assert "counter" in result + assert len(result["counter"]) == 10 # max_count=10 in counter_graph + # Verify counter values are sequential 1-10 + values = [v for _, v in result["counter"]] + assert values == list(range(1, 11)) diff --git a/examples/05_cpp/3_cpp_adapter/cpp/CounterAdapterManager.cpp b/examples/05_cpp/3_cpp_adapter/cpp/CounterAdapterManager.cpp new file mode 100644 index 000000000..176668c87 --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/cpp/CounterAdapterManager.cpp @@ -0,0 +1,108 @@ +#include "CounterAdapterManager.h" +#include "CounterInputAdapter.h" +#include "CounterOutputAdapter.h" +#include +#include + +namespace csp::adapters::counter +{ + +CounterAdapterManager::CounterAdapterManager( csp::Engine * engine, const Dictionary & properties ) + : AdapterManager( engine ), + m_intervalMs( 1000 ), + m_maxCount( 0 ), + m_running( false ), + m_inputAdapter( nullptr ), + m_outputAdapter( nullptr ) +{ + // Extract configuration from properties + if( properties.exists( "interval_ms" ) ) + m_intervalMs = properties.get( "interval_ms" ); + + if( properties.exists( "max_count" ) ) + m_maxCount = properties.get( "max_count" ); +} + +CounterAdapterManager::~CounterAdapterManager() +{ + stop(); +} + +void CounterAdapterManager::start( DateTime starttime, DateTime endtime ) +{ + // Call base class start + AdapterManager::start( starttime, endtime ); + + // Start the push thread + m_running = true; + m_pushThread = std::make_unique( &CounterAdapterManager::runPushThread, this ); +} + +void CounterAdapterManager::stop() +{ + // Signal the thread to stop + m_running = false; + + // Wait for thread to complete + if( m_pushThread && m_pushThread -> joinable() ) + { + m_pushThread -> join(); + m_pushThread.reset(); + } + + // Call base class stop + AdapterManager::stop(); +} + +PushInputAdapter * CounterAdapterManager::getInputAdapter( CspTypePtr & type, PushMode pushMode, const Dictionary & properties ) +{ + // For this simple example, we only support one input adapter + if( m_inputAdapter != nullptr ) + CSP_THROW( RuntimeException, "CounterAdapterManager only supports one input adapter" ); + + m_inputAdapter = engine() -> createOwnedObject( type, pushMode, &m_pushGroup ); + return m_inputAdapter; +} + +OutputAdapter * CounterAdapterManager::getOutputAdapter( CspTypePtr & type, const Dictionary & properties ) +{ + // For this simple example, we only support one output adapter + if( m_outputAdapter != nullptr ) + CSP_THROW( RuntimeException, "CounterAdapterManager only supports one output adapter" ); + + m_outputAdapter = engine() -> createOwnedObject( type ); + return m_outputAdapter; +} + +void CounterAdapterManager::pushValue( int64_t value ) +{ + if( m_inputAdapter ) + { + PushBatch batch( rootEngine() ); + m_inputAdapter -> pushTick( value, &batch ); + } +} + +void CounterAdapterManager::runPushThread() +{ + int64_t counter = 0; + + while( m_running ) + { + // Sleep for the configured interval + std::this_thread::sleep_for( std::chrono::milliseconds( m_intervalMs ) ); + + if( !m_running ) + break; + + // Push the counter value + counter++; + pushValue( counter ); + + // Check if we've reached the max count + if( m_maxCount > 0 && counter >= m_maxCount ) + break; + } +} + +} diff --git a/examples/05_cpp/3_cpp_adapter/cpp/CounterAdapterManager.h b/examples/05_cpp/3_cpp_adapter/cpp/CounterAdapterManager.h new file mode 100644 index 000000000..c500fc573 --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/cpp/CounterAdapterManager.h @@ -0,0 +1,67 @@ +#ifndef _IN_CSP_EXAMPLE_COUNTER_ADAPTER_MANAGER_H +#define _IN_CSP_EXAMPLE_COUNTER_ADAPTER_MANAGER_H + +#include +#include +#include +#include +#include + +namespace csp::adapters::counter +{ + +class CounterInputAdapter; +class CounterOutputAdapter; + +/** + * CounterAdapterManager - A simple example adapter manager that demonstrates + * the basic pattern for creating custom adapters in csp. + * + * This adapter manager: + * - Controls the lifecycle of CounterInputAdapter and CounterOutputAdapter + * - Manages a background thread that periodically generates counter values + * - Demonstrates PushInputAdapter and OutputAdapter patterns + */ +class CounterAdapterManager final : public csp::AdapterManager +{ +public: + CounterAdapterManager( csp::Engine * engine, const Dictionary & properties ); + ~CounterAdapterManager(); + + const char * name() const override { return "CounterAdapterManager"; } + + void start( DateTime starttime, DateTime endtime ) override; + void stop() override; + + // For sim inputs - we return NONE since this is a realtime adapter + DateTime processNextSimTimeSlice( DateTime time ) override { return DateTime::NONE(); } + + // Factory methods for creating adapters + PushInputAdapter * getInputAdapter( CspTypePtr & type, PushMode pushMode, const Dictionary & properties ); + OutputAdapter * getOutputAdapter( CspTypePtr & type, const Dictionary & properties ); + + // Internal method called by the push thread + void pushValue( int64_t value ); + +private: + void runPushThread(); + + // Configuration from properties + int64_t m_intervalMs; // Interval between pushes in milliseconds + int64_t m_maxCount; // Maximum count before stopping (0 = unlimited) + + // Thread management + std::unique_ptr m_pushThread; + std::atomic m_running; + + // Push group for coordinating input adapters + PushGroup m_pushGroup; + + // Registered adapters + CounterInputAdapter * m_inputAdapter; + CounterOutputAdapter * m_outputAdapter; +}; + +} + +#endif diff --git a/examples/05_cpp/3_cpp_adapter/cpp/CounterInputAdapter.cpp b/examples/05_cpp/3_cpp_adapter/cpp/CounterInputAdapter.cpp new file mode 100644 index 000000000..2806b4548 --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/cpp/CounterInputAdapter.cpp @@ -0,0 +1,11 @@ +#include "CounterInputAdapter.h" + +namespace csp::adapters::counter +{ + +CounterInputAdapter::CounterInputAdapter( Engine * engine, CspTypePtr & type, PushMode pushMode, PushGroup * group ) + : PushInputAdapter( engine, type, pushMode, group ) +{ +} + +} diff --git a/examples/05_cpp/3_cpp_adapter/cpp/CounterInputAdapter.h b/examples/05_cpp/3_cpp_adapter/cpp/CounterInputAdapter.h new file mode 100644 index 000000000..4cc4c34fc --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/cpp/CounterInputAdapter.h @@ -0,0 +1,27 @@ +#ifndef _IN_CSP_EXAMPLE_COUNTER_INPUT_ADAPTER_H +#define _IN_CSP_EXAMPLE_COUNTER_INPUT_ADAPTER_H + +#include + +namespace csp::adapters::counter +{ + +/** + * CounterInputAdapter - A simple push input adapter that receives counter values + * from the CounterAdapterManager's background thread. + * + * This demonstrates the basic pattern for a PushInputAdapter: + * - Inherits from PushInputAdapter + * - Receives data via pushTick() called from a background thread + * - Data is automatically marshaled to the engine thread + */ +class CounterInputAdapter final : public PushInputAdapter +{ +public: + CounterInputAdapter( Engine * engine, CspTypePtr & type, PushMode pushMode, PushGroup * group ); + ~CounterInputAdapter() = default; +}; + +} + +#endif diff --git a/examples/05_cpp/3_cpp_adapter/cpp/CounterOutputAdapter.cpp b/examples/05_cpp/3_cpp_adapter/cpp/CounterOutputAdapter.cpp new file mode 100644 index 000000000..db4969895 --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/cpp/CounterOutputAdapter.cpp @@ -0,0 +1,21 @@ +#include "CounterOutputAdapter.h" +#include +#include + +namespace csp::adapters::counter +{ + +CounterOutputAdapter::CounterOutputAdapter( Engine * engine, CspTypePtr & type ) + : OutputAdapter( engine ), m_type( type ) +{ +} + +void CounterOutputAdapter::executeImpl() +{ + // Get the last value from the input time series + // For this example, we assume int64_t type + int64_t value = input() -> lastValueTyped(); + std::cout << "[CounterOutputAdapter] Received value: " << value << std::endl; +} + +} diff --git a/examples/05_cpp/3_cpp_adapter/cpp/CounterOutputAdapter.h b/examples/05_cpp/3_cpp_adapter/cpp/CounterOutputAdapter.h new file mode 100644 index 000000000..8a51ce9ef --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/cpp/CounterOutputAdapter.h @@ -0,0 +1,32 @@ +#ifndef _IN_CSP_EXAMPLE_COUNTER_OUTPUT_ADAPTER_H +#define _IN_CSP_EXAMPLE_COUNTER_OUTPUT_ADAPTER_H + +#include + +namespace csp::adapters::counter +{ + +/** + * CounterOutputAdapter - A simple output adapter that logs values to stdout. + * + * This demonstrates the basic pattern for an OutputAdapter: + * - Inherits from OutputAdapter + * - Implements executeImpl() which is called whenever the input ticks + * - Uses input()->lastValueTyped() to get the current value + */ +class CounterOutputAdapter final : public OutputAdapter +{ +public: + CounterOutputAdapter( Engine * engine, CspTypePtr & type ); + ~CounterOutputAdapter() = default; + + void executeImpl() override; + const char * name() const override { return "CounterOutputAdapter"; } + +private: + CspTypePtr m_type; +}; + +} + +#endif diff --git a/examples/05_cpp/3_cpp_adapter/cpp/counteradapterimpl.cpp b/examples/05_cpp/3_cpp_adapter/cpp/counteradapterimpl.cpp new file mode 100644 index 000000000..a4391c40f --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/cpp/counteradapterimpl.cpp @@ -0,0 +1,125 @@ +/** + * counteradapterimpl.cpp - Python bindings for the Counter adapter example + * + * This file demonstrates how to expose C++ adapters to Python using CSP's + * registration macros. It provides the glue between Python and the C++ + * CounterAdapterManager, CounterInputAdapter, and CounterOutputAdapter. + */ + +#include "CounterAdapterManager.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace csp::adapters::counter; + +namespace csp::python +{ + +/** + * Factory function for creating the CounterAdapterManager. + * Called by the Python adapter manager wrapper when the graph starts. + * + * @param engine The PyEngine wrapper + * @param properties Dictionary of configuration properties + * @return The created AdapterManager + */ +csp::AdapterManager * create_counter_adapter_manager( PyEngine * engine, const Dictionary & properties ) +{ + return engine -> engine() -> createOwnedObject( properties ); +} + +/** + * Factory function for creating the CounterInputAdapter. + * Called when the Python code creates an input adapter instance. + * + * @param manager The parent AdapterManager + * @param pyengine The PyEngine wrapper + * @param pyType The CSP type of the adapter output + * @param pushMode Push mode (LAST_VALUE, NON_COLLAPSING, etc.) + * @param args Python tuple of additional arguments + * @return The created InputAdapter + */ +static InputAdapter * create_counter_input_adapter( csp::AdapterManager * manager, PyEngine * pyengine, + PyObject * pyType, PushMode pushMode, PyObject * args ) +{ + auto & cspType = pyTypeAsCspType( pyType ); + + PyObject * pyProperties; + PyObject * type; + + auto * counterManager = dynamic_cast( manager ); + if( !counterManager ) + CSP_THROW( TypeError, "Expected CounterAdapterManager" ); + + if( !PyArg_ParseTuple( args, "O!O!", + &PyType_Type, &type, + &PyDict_Type, &pyProperties ) ) + CSP_THROW( PythonPassthrough, "" ); + + return counterManager -> getInputAdapter( cspType, pushMode, fromPython( pyProperties ) ); +} + +/** + * Factory function for creating the CounterOutputAdapter. + * Called when the Python code creates an output adapter instance. + * + * @param manager The parent AdapterManager + * @param pyengine The PyEngine wrapper + * @param args Python tuple of additional arguments + * @return The created OutputAdapter + */ +static OutputAdapter * create_counter_output_adapter( csp::AdapterManager * manager, PyEngine * pyengine, PyObject * args ) +{ + PyObject * pyProperties; + PyObject * pyType; + + auto * counterManager = dynamic_cast( manager ); + if( !counterManager ) + CSP_THROW( TypeError, "Expected CounterAdapterManager" ); + + if( !PyArg_ParseTuple( args, "OO!", &pyType, &PyDict_Type, &pyProperties ) ) + CSP_THROW( PythonPassthrough, "" ); + + auto & cspType = pyTypeAsCspType( pyType ); + + return counterManager -> getOutputAdapter( cspType, fromPython( pyProperties ) ); +} + +// Register the adapter manager and adapters with CSP +// These macros make the C++ code available to Python +REGISTER_ADAPTER_MANAGER( _counter_adapter_manager, create_counter_adapter_manager ); +REGISTER_INPUT_ADAPTER( _counter_input_adapter, create_counter_input_adapter ); +REGISTER_OUTPUT_ADAPTER( _counter_output_adapter, create_counter_output_adapter ); + +// Python module definition +static PyModuleDef _counteradapterimpl_module = { + PyModuleDef_HEAD_INIT, + "_counteradapter", // Module name + "Counter adapter example C++ module", // Module docstring + -1, + NULL, NULL, NULL, NULL, NULL +}; + +// Module initialization function +PyMODINIT_FUNC PyInit__counteradapter(void) +{ + PyObject* m; + + m = PyModule_Create( &_counteradapterimpl_module ); + if( m == NULL ) + return NULL; + + // Execute all registered initializers (adapter manager, input/output adapters) + if( !InitHelper::instance().execute( m ) ) + return NULL; + + return m; +} + +} diff --git a/examples/05_cpp/3_cpp_adapter/pyproject.toml b/examples/05_cpp/3_cpp_adapter/pyproject.toml new file mode 100644 index 000000000..d17fc29d8 --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["hatchling", "hatch-cpp", "csp"] +build-backend = "hatchling.build" + +[project] +name = "csp-example-adapter" +authors = [{name = "the csp authors", email = "CSPOpenSource@point72.com"}] +description = "csp example of C++ adapter" +readme = "README.md" +version = "0.0.1" +requires-python = ">=3.9" +dependencies = ["csp"] + +[tool.hatch.build.targets.wheel] +packages = ["counteradapter"] + +[tool.hatch.build.hooks.hatch-cpp] +cmake = { root = "." } diff --git a/examples/05_cpp/4_c_api_adapter/CMakeLists.txt b/examples/05_cpp/4_c_api_adapter/CMakeLists.txt new file mode 100644 index 000000000..e9d8906c0 --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required(VERSION 3.20.0) +project(csp-example-c-api-adapter VERSION "0.0.1") +set(CMAKE_CXX_STANDARD 17) + +# Find Python +find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) + +# Find CSP include and lib paths +execute_process( + COMMAND "${Python_EXECUTABLE}" -c "import csp; print(csp.get_include_path(), end='')" + OUTPUT_VARIABLE CSP_INCLUDE_DIR) +execute_process( + COMMAND "${Python_EXECUTABLE}" -c "import csp; print(csp.get_lib_path(), end='')" + OUTPUT_VARIABLE CSP_LIB_DIR) + +find_library(CSP_LIBRARY NAMES _cspimpl.so PATHS "${CSP_LIB_DIR}" NO_DEFAULT_PATH) + +# Position independent code for shared libraries +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# macOS: don't link against build python +if(APPLE) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -undefined dynamic_lookup") +endif() + +# Shared library naming conventions +set(CMAKE_SHARED_LIBRARY_PREFIX _) +set(CMAKE_SHARED_LIBRARY_SUFFIX .so) + +include_directories(${CSP_INCLUDE_DIR}) +include_directories(${Python_INCLUDE_DIRS}) + +# Example C adapters demonstrating the C ABI interface +add_library(csp_c_example_adapter STATIC + cpp/ExamplePushInputAdapter.c + cpp/ExampleOutputAdapter.c + cpp/ExampleManagedAdapter.c +) +target_include_directories(csp_c_example_adapter PUBLIC ${CMAKE_SOURCE_DIR}/cpp) +set_target_properties(csp_c_example_adapter PROPERTIES C_STANDARD 11 C_STANDARD_REQUIRED ON) + +if(NOT WIN32) + target_link_libraries(csp_c_example_adapter pthread) +endif() + +install(TARGETS csp_c_example_adapter LIBRARY DESTINATION exampleadapter/lib) + +# Python bindings for example C adapters +Python_add_library(_exampleadapterimpl MODULE cpp/exampleadapterimpl.c) +target_link_libraries(_exampleadapterimpl PRIVATE csp_c_example_adapter ${CSP_LIBRARY}) +target_include_directories(_exampleadapterimpl PRIVATE ${CMAKE_SOURCE_DIR}/cpp ${Python_INCLUDE_DIRS}) +set_target_properties(_exampleadapterimpl PROPERTIES PREFIX "" C_STANDARD 11 C_STANDARD_REQUIRED ON) + +install(TARGETS _exampleadapterimpl LIBRARY DESTINATION exampleadapter) diff --git a/examples/05_cpp/4_c_api_adapter/README.md b/examples/05_cpp/4_c_api_adapter/README.md new file mode 100644 index 000000000..16e49512c --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/README.md @@ -0,0 +1,195 @@ +# C API Adapter Example + +This example demonstrates how to implement CSP adapters using the **stable C ABI interface**. The C API provides a language-agnostic way to create input adapters, output adapters, and adapter managers that can be compiled separately from CSP. + +## Why Use the C API? + +- **ABI Stability**: The C API is designed to be stable across CSP versions +- **Language Flexibility**: C code can be called from Rust, Go, Zig, or any language with C FFI +- **Separate Compilation**: Adapters can be built as standalone libraries +- **Simpler Build**: No need to link against C++ STL or match compiler versions + +## Architecture + +The C API uses a capsule-based pattern where: + +1. **C code** creates VTable structs with callbacks and wraps them in Python capsules +1. **CSP bridge functions** consume these capsules and create native adapter objects +1. **Python wiring** connects everything to the CSP graph + +``` ++-------------------+ +----------------------+ +------------------+ +| Your C Code | --> | Python Capsule | --> | CSP Bridge | +| (VTable + state) | | (wraps VTable ptr) | | (creates adapter)| ++-------------------+ +----------------------+ +------------------+ +``` + +## Key Concepts + +### Output Adapter + +Receives data from the CSP graph and sends it to external systems (files, network, databases). + +```c +typedef struct CCspOutputAdapterVTable { + void* user_data; + void (*start)(void* user_data, CCspEngineHandle engine, + CCspDateTime start_time, CCspDateTime end_time); + void (*stop)(void* user_data); + void (*execute)(void* user_data, CCspEngineHandle engine, CCspInputHandle input); + void (*destroy)(void* user_data); +} CCspOutputAdapterVTable; +``` + +### Push Input Adapter + +Pushes data into the CSP graph from external sources (threads, callbacks). + +```c +typedef struct CCspPushInputAdapterVTable { + void* user_data; + void (*start)(void* user_data, CCspEngineHandle engine, + CCspPushInputAdapterHandle adapter, ...); + void (*stop)(void* user_data); + void (*destroy)(void* user_data); +} CCspPushInputAdapterVTable; +``` + +### Adapter Manager + +Coordinates the lifecycle of multiple adapters (shared connections, time slicing). + +```c +typedef struct CCspAdapterManagerVTable { + void* user_data; + const char* name; + void (*start)(void* user_data, CCspDateTime start_time, CCspDateTime end_time); + void (*stop)(void* user_data); + void (*destroy)(void* user_data); +} CCspAdapterManagerVTable; +``` + +## Building + +```bash +# From this directory +hatch-build --hooks-only -t wheel + +# Or install in development mode +pip install -e . +``` + +## Python Wiring Pattern + +The key to integrating C API adapters with CSP is the **bridge function pattern**. CSP provides bridge functions that consume capsules and create native adapters: + +- `_cspimpl._c_api_push_input_adapter` - For push input adapters +- `_cspimpl._c_api_output_adapter` - For output adapters + +### Input Adapter Wiring + +```python +from csp.impl.__cspimpl import _cspimpl +from csp.impl.wiring import input_adapter_def + +from . import _my_native_module + +def _create_my_input_adapter(mgr, engine, pytype, push_mode, scalars): + """Bridge function called by input_adapter_def.""" + # Extract parameters from scalars + interval_ms = scalars[1] if len(scalars) > 1 else 100 + + # Create the VTable capsule using your C function + capsule = _my_native_module._my_input_adapter(interval_ms=interval_ms) + + # Pass to CSP bridge which creates the actual adapter + # Args: (capsule, push_group_or_none) + return _cspimpl._c_api_push_input_adapter( + mgr, engine, pytype, push_mode, (capsule, None) + ) + +# Define the adapter +my_input_adapter = input_adapter_def( + "my_input_adapter", + _create_my_input_adapter, + ts["T"], + typ="T", + interval_ms=int, +) +``` + +### Output Adapter Wiring + +```python +from csp.impl.__cspimpl import _cspimpl +from csp.impl.wiring import output_adapter_def + +def _create_my_output_adapter(mgr, engine, scalars): + """Bridge function called by output_adapter_def.""" + # Extract parameters from scalars + prefix = scalars[0] if scalars else "" + + # Create the VTable capsule + capsule = _my_native_module._my_output_adapter(prefix=prefix) + + # Pass to CSP bridge + # Args: (input_type, capsule) + return _cspimpl._c_api_output_adapter(mgr, engine, (int, capsule)) + +# Define the adapter +my_output_adapter = output_adapter_def( + "my_output_adapter", + _create_my_output_adapter, + input=ts["T"], + prefix=str, +) +``` + +## Usage from Python + +```python +from datetime import timedelta + +import csp +from csp.utils.datetime import utc_now + +from exampleadapter import example_input, example_output + +@csp.graph +def my_graph(): + # Create input that generates integers every 100ms + data = example_input(int, interval_ms=100) + + # Output to stdout with a prefix + example_output(data, prefix="[MyApp] ") + +csp.run(my_graph, starttime=utc_now(), endtime=timedelta(seconds=5)) +``` + +## API Headers + +The C API is defined in these headers (installed with CSP): + +- `` - Type definitions +- `` - Value types +- `` - DateTime/TimeDelta +- `` - Error handling +- `` - Struct access +- `` - Dictionary access +- `` - Output adapter interface +- `` - Input adapter interface +- `` - Adapter manager interface + +## Python Integration Headers + +Helper functions for creating Python capsules: + +- `` - Output adapter capsule helpers +- `` - Input adapter capsule helpers +- `` - Adapter manager capsule helpers + +## See Also + +- [C-APIs Reference](../../../docs/wiki/api-references/C-APIs.md) +- [Write C API Adapters Guide](../../../docs/wiki/how-tos/Write-C-API-Adapters.md) +- [Rust Adapter Example](../5_c_api_adapter_rust/) - Using the C API from Rust diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c new file mode 100644 index 000000000..c64899584 --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c @@ -0,0 +1,220 @@ +/* + * Example Managed Adapter Implementation + * + * Demonstrates how to build an adapter manager that coordinates + * multiple input/output adapters, similar to KafkaAdapterManager. + */ + +#include +#include +#include + +#include "ExampleManagedAdapter.h" + +/* ============================================================================ + * Adapter Manager Callbacks + * ============================================================================ */ + +static const char * managed_adapter_name( void * user_data ) +{ + ManagedAdapterState * state = ( ManagedAdapterState * ) user_data; + return state -> name; +} + +static void managed_adapter_start( void * user_data, CCspAdapterManagerHandle manager, + CCspDateTime start_time, CCspDateTime end_time ) +{ + ManagedAdapterState * state = ( ManagedAdapterState * ) user_data; + state -> manager = manager; + state -> is_started = 1; + state -> message_count = 0; + + /* Calculate time in seconds for display */ + double start_sec = ( double )start_time / 1000000000.0; + double end_sec = ( double )end_time / 1000000000.0; + + printf( "[%s] Manager started (start=%.3f, end=%.3f)\n", + state -> name, start_sec, end_sec ); + + /* Report status to the graph */ + ccsp_adapter_manager_push_status( manager, CCSP_STATUS_LEVEL_INFO, 0, + "Manager started successfully" ); +} + +static void managed_adapter_stop( void * user_data ) +{ + ManagedAdapterState * state = ( ManagedAdapterState * ) user_data; + + printf( "[%s] Manager stopped. Total messages: %d\n", + state -> name, state -> message_count ); + + state -> is_started = 0; +} + +static CCspDateTime managed_adapter_process_next_sim_time_slice( void * user_data, CCspDateTime time ) +{ + /* This example is realtime-only, so we return 0 (no more sim data) */ + ( void )user_data; + ( void )time; + return 0; +} + +static void managed_adapter_destroy( void * user_data ) +{ + ManagedAdapterState * state = ( ManagedAdapterState * ) user_data; + printf( "[%s] Manager destroyed\n", state -> name ); + free( state ); +} + +CCspAdapterManagerVTable example_managed_adapter_create( const char * name ) +{ + ManagedAdapterState * state = ( ManagedAdapterState * ) malloc( sizeof( ManagedAdapterState ) ); + if( !state ) + { + CCspAdapterManagerVTable empty = {0}; + return empty; + } + + memset( state, 0, sizeof( ManagedAdapterState ) ); + if( name ) + { + strncpy( state -> name, name, sizeof( state -> name ) - 1 ); + } + else + { + strncpy( state -> name, "ExampleManagedAdapter", sizeof( state -> name ) - 1 ); + } + + CCspAdapterManagerVTable vtable; + vtable.user_data = state; + vtable.name = managed_adapter_name; + vtable.start = managed_adapter_start; + vtable.stop = managed_adapter_stop; + vtable.process_next_sim_time_slice = managed_adapter_process_next_sim_time_slice; + vtable.destroy = managed_adapter_destroy; + + return vtable; +} + +/* ============================================================================ + * Managed Output Adapter Callbacks + * ============================================================================ */ + +static void managed_output_start( void * user_data, CCspEngineHandle engine, CCspDateTime start_time, CCspDateTime end_time ) +{ + ManagedOutputState * state = ( ManagedOutputState * ) user_data; + ( void )engine; + ( void )start_time; + ( void )end_time; + + printf( " [%s/%s] Output adapter started\n", + state -> shared -> name, state -> topic ); + state -> messages_sent = 0; +} + +static void managed_output_stop( void * user_data ) +{ + ManagedOutputState * state = ( ManagedOutputState * ) user_data; + printf( " [%s/%s] Output adapter stopped. Messages sent: %d\n", + state -> shared -> name, state -> topic, state -> messages_sent ); +} + +static void managed_output_execute( void * user_data, CCspEngineHandle engine, CCspInputHandle input ) +{ + ManagedOutputState * state = ( ManagedOutputState * ) user_data; + + if( !ccsp_input_is_valid( input ) ) + { + return; + } + + /* Get current engine time */ + CCspDateTime now = ccsp_engine_now( engine ); + double now_sec = ( double )now / 1000000000.0; + + /* Get the input type and value */ + CCspType type = ccsp_input_get_type( input ); + + printf( " [%s/%s] t=%.3f -> ", state -> shared -> name, state -> topic, now_sec ); + + switch( type ) + { + case CCSP_TYPE_INT64: + { + int64_t value; + if( ccsp_input_get_last_int64( input, &value ) == CCSP_OK ) + { + printf( "int64: %lld\n", ( long long ) value ); + } + break; + } + case CCSP_TYPE_DOUBLE: + { + double value; + if( ccsp_input_get_last_double( input, &value ) == CCSP_OK ) + { + printf( "double: %.6f\n", value ); + } + break; + } + case CCSP_TYPE_STRING: + { + const char * data; + size_t length; + if( ccsp_input_get_last_string( input, &data, &length ) == CCSP_OK ) + { + printf( "string: \"%.*s\"\n", ( int ) length, data ); + } + break; + } + default: + printf( "(type %d)\n", type ); + break; + } + + state -> messages_sent++; + state -> shared -> message_count++; +} + +static void managed_output_destroy( void * user_data ) +{ + ManagedOutputState * state = ( ManagedOutputState * ) user_data; + printf( " [%s/%s] Output adapter destroyed\n", + state -> shared -> name, state -> topic ); + free( state ); +} + +CCspOutputAdapterVTable example_managed_output_adapter_create( ManagedAdapterState * shared_state, const char * topic ) +{ + CCspOutputAdapterVTable vtable = {0}; + + if( !shared_state ) + { + return vtable; + } + + ManagedOutputState * state = ( ManagedOutputState * ) malloc( sizeof( ManagedOutputState ) ); + if( !state ) + { + return vtable; + } + + memset( state, 0, sizeof( ManagedOutputState ) ); + state -> shared = shared_state; + if( topic ) + { + strncpy( state -> topic, topic, sizeof( state -> topic ) - 1 ); + } + else + { + strncpy( state -> topic, "default", sizeof( state -> topic ) - 1 ); + } + + vtable.user_data = state; + vtable.start = managed_output_start; + vtable.stop = managed_output_stop; + vtable.execute = managed_output_execute; + vtable.destroy = managed_output_destroy; + + return vtable; +} diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h new file mode 100644 index 000000000..80ef13ced --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h @@ -0,0 +1,80 @@ +/* + * Example Managed Adapter using the CSP C API + * + * This example demonstrates: + * - Creating an adapter manager that coordinates multiple adapters + * - Managing lifecycle (start/stop) across adapters + * - Status reporting + * - Coordinated output adapters + * + * This is a more realistic example than ExampleOutputAdapter.c, + * showing how real adapters like Kafka or WebSocket would be structured. + */ + +#ifndef _IN_CSP_ADAPTERS_C_EXAMPLE_MANAGED_ADAPTER_H +#define _IN_CSP_ADAPTERS_C_EXAMPLE_MANAGED_ADAPTER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ManagedAdapterState - Shared state for managed adapters + * + * In a real adapter (like Kafka), this would contain: + * - Connection handles + * - Configuration + * - Thread pools + * - Message buffers + */ +typedef struct ManagedAdapterState { + char name[64]; + int is_started; + int message_count; + CCspAdapterManagerHandle manager; +} ManagedAdapterState; + +/* + * ManagedOutputState - State for a single output adapter in the manager + */ +typedef struct ManagedOutputState { + ManagedAdapterState * shared; + char topic[64]; /* e.g., Kafka topic name */ + int messages_sent; +} ManagedOutputState; + +/* + * example_managed_adapter_create - Create managed adapter VTable + * + * Creates an adapter manager that can coordinate multiple output adapters. + * Similar to how KafkaAdapterManager works. + * + * Parameters: + * name - Name for this adapter manager instance + * + * Returns: + * VTable for use with ccsp_adapter_manager_extern_create + */ +CCspAdapterManagerVTable example_managed_adapter_create( const char * name ); + +/* + * example_managed_output_adapter_create - Create output adapter for manager + * + * Creates an output adapter that works with the managed adapter. + * + * Parameters: + * shared_state - Pointer to ManagedAdapterState from the manager + * topic - Topic/channel name for this output + * + * Returns: + * VTable for use with ccsp_adapter_manager_create_output_adapter + */ +CCspOutputAdapterVTable example_managed_output_adapter_create( ManagedAdapterState * shared_state, const char * topic ); + +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_ADAPTERS_C_EXAMPLE_MANAGED_ADAPTER_H */ diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c new file mode 100644 index 000000000..e9104eec0 --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c @@ -0,0 +1,162 @@ +/* + * Example Output Adapter implementation in C + * + * This demonstrates how to implement an output adapter using the C ABI interface. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "ExampleOutputAdapter.h" + +/* Adapter state structure */ +typedef struct { + char * prefix; /* Prefix to print before each value */ + int fd; /* File descriptor to write to */ + int owns_prefix; /* Whether we own the prefix memory */ +} ExampleOutputAdapterState; + +/* ============================================================================ + * Callback implementations + * ============================================================================ */ + +static void example_output_start( void * user_data, CCspEngineHandle engine, CCspDateTime start_time, CCspDateTime end_time ) +{ + ExampleOutputAdapterState * state = ( ExampleOutputAdapterState * ) user_data; + ( void ) engine; + + dprintf( state -> fd, "[ExampleOutputAdapter] Started. Time range: %lld - %lld ns\n", ( long long ) start_time, ( long long ) end_time ); +} + +static void example_output_stop( void * user_data ) +{ + ExampleOutputAdapterState * state = ( ExampleOutputAdapterState * ) user_data; + dprintf( state -> fd, "[ExampleOutputAdapter] Stopped.\n" ); +} + +static void example_output_execute( void * user_data, CCspEngineHandle engine, CCspInputHandle input ) +{ + ExampleOutputAdapterState * state = ( ExampleOutputAdapterState * ) user_data; + CCspDateTime now = ccsp_engine_now( engine ); + CCspType type = ccsp_input_get_type( input ); + + const char * prefix = state -> prefix ? state -> prefix : ""; + + /* Print based on type */ + switch( type ) + { + case CCSP_TYPE_BOOL: + { + int8_t val; + if( ccsp_input_get_last_bool( input, &val ) == CCSP_OK ) + { + dprintf( state -> fd, "%s[%lld] bool: %s\n", prefix, ( long long ) now, val ? "true" : "false" ); + } + break; + } + case CCSP_TYPE_INT64: + { + int64_t val; + if( ccsp_input_get_last_int64( input, &val ) == CCSP_OK ) + { + dprintf( state -> fd, "%s[%lld] int64: %lld\n", prefix, ( long long ) now, ( long long ) val ); + } + break; + } + case CCSP_TYPE_DOUBLE: + { + double val; + if( ccsp_input_get_last_double( input, &val ) == CCSP_OK ) + { + dprintf( state -> fd, "%s[%lld] double: %f\n", prefix, ( long long ) now, val ); + } + break; + } + case CCSP_TYPE_STRING: + { + const char * data; + size_t len; + if( ccsp_input_get_last_string( input, &data, &len ) == CCSP_OK ) + { + dprintf( state -> fd, "%s[%lld] string: %.*s\n", prefix, ( long long ) now, ( int ) len, data ); + } + break; + } + case CCSP_TYPE_DATETIME: + { + CCspDateTime val; + if( ccsp_input_get_last_datetime( input, &val ) == CCSP_OK ) + { + dprintf( state -> fd, "%s[%lld] datetime: %lld ns\n", prefix, ( long long ) now, ( long long ) val ); + } + break; + } + default: + dprintf( state -> fd, "%s[%lld] \n", prefix, ( long long ) now, ( int ) type ); + break; + } +} + +static void example_output_destroy( void * user_data ) +{ + ExampleOutputAdapterState * state = ( ExampleOutputAdapterState * ) user_data; + if( state ) + { + if( state -> owns_prefix && state -> prefix ) + { + free( state -> prefix ); + } + free( state ); + } +} + +/* ============================================================================ + * Public API + * ============================================================================ */ + +CCspOutputAdapterVTable example_output_adapter_create( const char * prefix ) +{ + return example_output_adapter_create_fd( STDOUT_FILENO, prefix ); +} + +CCspOutputAdapterVTable example_output_adapter_create_fd( int fd, const char * prefix ) +{ + CCspOutputAdapterVTable vtable = {0}; + + /* Allocate state */ + ExampleOutputAdapterState * state = ( ExampleOutputAdapterState * )malloc( sizeof( ExampleOutputAdapterState ) ); + if( !state ) + { + /* Return an invalid vtable with NULL callbacks */ + return vtable; + } + + state -> fd = fd; + state -> owns_prefix = 0; + state -> prefix = NULL; + + /* Copy prefix if provided */ + if( prefix ) + { + size_t len = strlen( prefix ); + state -> prefix = ( char * )malloc( len + 1 ); + if( state -> prefix ) + { + memcpy( state -> prefix, prefix, len + 1 ); + state -> owns_prefix = 1; + } + } + + /* Set up vtable */ + vtable.user_data = state; + vtable.start = example_output_start; + vtable.stop = example_output_stop; + vtable.execute = example_output_execute; + vtable.destroy = example_output_destroy; + + return vtable; +} diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.h b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.h new file mode 100644 index 000000000..6437700de --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.h @@ -0,0 +1,38 @@ +/* + * Example Output Adapter implemented in C + * + * This demonstrates how to implement an output adapter using the C ABI interface. + * The adapter simply prints received values to stdout. + */ +#ifndef _IN_CSP_ADAPTERS_C_EXAMPLE_OUTPUT_ADAPTER_H +#define _IN_CSP_ADAPTERS_C_EXAMPLE_OUTPUT_ADAPTER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Create an example output adapter. + * + * @param prefix Prefix string to print before each value (can be NULL) + * @return VTable structure to pass to ccsp_output_adapter_extern_create + */ +CCspOutputAdapterVTable example_output_adapter_create( const char * prefix ); + +/* + * Alternative: Get an adapter that logs to a specific file descriptor. + * + * @param fd File descriptor to write to + * @param prefix Prefix string (can be NULL) + * @return VTable structure + */ +CCspOutputAdapterVTable example_output_adapter_create_fd( int fd, const char * prefix ); + +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_ADAPTERS_C_EXAMPLE_OUTPUT_ADAPTER_H */ + diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.c b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.c new file mode 100644 index 000000000..42a66a3a3 --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.c @@ -0,0 +1,276 @@ +/* + * Example Push Input Adapter implementation in C + * + * This demonstrates how to implement a push input adapter using the C ABI interface. + * Note: This is a simplified example. A real adapter would use proper threading. + */ +#include +#include +#include +#include +#include + +#include "ExamplePushInputAdapter.h" + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +/* ============================================================================ + * Integer adapter state + * ============================================================================ */ + +typedef struct { + int interval_ms; + int64_t counter; + int running; + CCspPushInputAdapterHandle adapter; +#ifdef _WIN32 + HANDLE thread; +#else + pthread_t thread; +#endif +} IntAdapterState; + +static void * int_adapter_thread( void * arg ) +{ + IntAdapterState * state = ( IntAdapterState * ) arg; + + while( state -> running ) + { + /* Push the current counter value */ + ccsp_push_input_adapter_push_int64( state -> adapter, state -> counter, NULL ); + state -> counter++; + + /* Sleep for the interval */ +#ifdef _WIN32 + Sleep( state -> interval_ms ); +#else + usleep( state -> interval_ms * 1000 ); +#endif + } + + return NULL; +} + +static void int_adapter_start( void * user_data, CCspEngineHandle engine, + CCspPushInputAdapterHandle adapter, + CCspDateTime start_time, CCspDateTime end_time ) +{ + IntAdapterState * state = ( IntAdapterState * ) user_data; + ( void ) engine; + ( void ) start_time; + ( void ) end_time; + + state -> adapter = adapter; + state -> running = 1; + state -> counter = 0; + +#ifdef _WIN32 + state -> thread = CreateThread( NULL, 0, ( LPTHREAD_START_ROUTINE )int_adapter_thread, state, 0, NULL ); +#else + pthread_create( &state -> thread, NULL, int_adapter_thread, state ); +#endif + + fprintf( stdout, "[ExampleIntInputAdapter] Started with interval %d ms\n", state -> interval_ms ); +} + +static void int_adapter_stop( void * user_data ) +{ + IntAdapterState * state = ( IntAdapterState * ) user_data; + state -> running = 0; + +#ifdef _WIN32 + WaitForSingleObject( state -> thread, INFINITE ); + CloseHandle( state -> thread ); +#else + pthread_join( state -> thread, NULL ); +#endif + + fprintf( stdout, "[ExampleIntInputAdapter] Stopped after %lld values\n", ( long long ) state -> counter ); +} + +static void int_adapter_destroy( void * user_data ) +{ + IntAdapterState * state = ( IntAdapterState * ) user_data; + free( state ); +} + +CCspPushInputAdapterVTable example_push_input_adapter_create_int( int interval_ms ) +{ + CCspPushInputAdapterVTable vtable = {0}; + + IntAdapterState * state = ( IntAdapterState * )malloc( sizeof( IntAdapterState ) ); + if( !state ) + { + return vtable; + } + + memset( state, 0, sizeof( IntAdapterState ) ); + state -> interval_ms = interval_ms > 0 ? interval_ms : 100; + + vtable.user_data = state; + vtable.start = int_adapter_start; + vtable.stop = int_adapter_stop; + vtable.destroy = int_adapter_destroy; + + return vtable; +} + +/* ============================================================================ + * Double adapter state + * ============================================================================ */ + +typedef struct { + int interval_ms; + int running; + CCspPushInputAdapterHandle adapter; +#ifdef _WIN32 + HANDLE thread; +#else + pthread_t thread; +#endif +} DoubleAdapterState; + +static void * double_adapter_thread( void * arg ) +{ + DoubleAdapterState * state = ( DoubleAdapterState * ) arg; + + while( state -> running ) + { + /* Generate a random double between 0 and 1 */ + double value = ( double )rand() / ( double )RAND_MAX; + ccsp_push_input_adapter_push_double( state -> adapter, value, NULL ); + +#ifdef _WIN32 + Sleep( state -> interval_ms ); +#else + usleep( state -> interval_ms * 1000 ); +#endif + } + + return NULL; +} + +static void double_adapter_start( void * user_data, CCspEngineHandle engine, + CCspPushInputAdapterHandle adapter, + CCspDateTime start_time, CCspDateTime end_time ) +{ + DoubleAdapterState * state = ( DoubleAdapterState * ) user_data; + ( void ) engine; + ( void ) start_time; + ( void ) end_time; + + state -> adapter = adapter; + state -> running = 1; + +#ifdef _WIN32 + state -> thread = CreateThread( NULL, 0, ( LPTHREAD_START_ROUTINE ) double_adapter_thread, state, 0, NULL ); +#else + pthread_create( &state -> thread, NULL, double_adapter_thread, state ); +#endif + + fprintf( stdout, "[ExampleDoubleInputAdapter] Started\n" ); +} + +static void double_adapter_stop( void * user_data ) +{ + DoubleAdapterState * state = ( DoubleAdapterState * ) user_data; + state -> running = 0; + +#ifdef _WIN32 + WaitForSingleObject( state -> thread, INFINITE ); + CloseHandle( state -> thread ); +#else + pthread_join( state -> thread, NULL ); +#endif + + fprintf( stdout, "[ExampleDoubleInputAdapter] Stopped\n" ); +} + +static void double_adapter_destroy( void * user_data ) +{ + DoubleAdapterState * state = ( DoubleAdapterState * ) user_data; + free( state ); +} + +CCspPushInputAdapterVTable example_push_input_adapter_create_double( int interval_ms ) +{ + CCspPushInputAdapterVTable vtable = {0}; + + DoubleAdapterState * state = ( DoubleAdapterState * ) malloc( sizeof( DoubleAdapterState ) ); + if( !state ) + { + return vtable; + } + + memset( state, 0, sizeof( DoubleAdapterState ) ); + state -> interval_ms = interval_ms > 0 ? interval_ms : 100; + + vtable.user_data = state; + vtable.start = double_adapter_start; + vtable.stop = double_adapter_stop; + vtable.destroy = double_adapter_destroy; + + return vtable; +} + +/* ============================================================================ + * String adapter (callback-based) + * ============================================================================ */ + +typedef struct { + ExampleStringCallback callback; + void * callback_data; + CCspPushInputAdapterHandle adapter; +} StringAdapterState; + +static void string_adapter_start( void * user_data, CCspEngineHandle engine, + CCspPushInputAdapterHandle adapter, + CCspDateTime start_time, CCspDateTime end_time ) +{ + StringAdapterState * state = ( StringAdapterState * ) user_data; + ( void ) engine; + ( void ) start_time; + ( void ) end_time; + + state -> adapter = adapter; + fprintf( stdout, "[ExampleStringInputAdapter] Started\n" ); +} + +static void string_adapter_stop( void * user_data ) +{ + ( void )user_data; + fprintf( stdout, "[ExampleStringInputAdapter] Stopped\n" ); +} + +static void string_adapter_destroy( void * user_data ) +{ + StringAdapterState * state = ( StringAdapterState * ) user_data; + free( state ); +} + +CCspPushInputAdapterVTable example_push_input_adapter_create_string( ExampleStringCallback get_string, void * user_data ) +{ + CCspPushInputAdapterVTable vtable = {0}; + + StringAdapterState * state = ( StringAdapterState * ) malloc( sizeof( StringAdapterState ) ); + if( !state ) + { + return vtable; + } + + memset( state, 0, sizeof( StringAdapterState ) ); + state -> callback = get_string; + state -> callback_data = user_data; + + vtable.user_data = state; + vtable.start = string_adapter_start; + vtable.stop = string_adapter_stop; + vtable.destroy = string_adapter_destroy; + + return vtable; +} diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h new file mode 100644 index 000000000..7bec8527a --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h @@ -0,0 +1,46 @@ +/* + * Example Push Input Adapter implemented in C + * + * This demonstrates how to implement a push input adapter using the C ABI interface. + * The adapter generates periodic values for testing. + */ +#ifndef _IN_CSP_ADAPTERS_C_EXAMPLE_PUSH_INPUT_ADAPTER_H +#define _IN_CSP_ADAPTERS_C_EXAMPLE_PUSH_INPUT_ADAPTER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Create an example push input adapter that generates incrementing integers. + * + * @param interval_ms Interval between pushes in milliseconds + * @return VTable structure to pass to ccsp_push_input_adapter_extern_create + */ +CCspPushInputAdapterVTable example_push_input_adapter_create_int( int interval_ms ); + +/* + * Create an example push input adapter that generates random doubles. + * + * @param interval_ms Interval between pushes in milliseconds + * @return VTable structure + */ +CCspPushInputAdapterVTable example_push_input_adapter_create_double( int interval_ms ); + +/* + * Create an example push input adapter that echoes strings from a callback. + * + * @param get_string Callback to get the next string to push (must be thread-safe) + * @param user_data User data passed to the callback + * @return VTable structure + */ +typedef const char * ( *ExampleStringCallback )( void * user_data ); +CCspPushInputAdapterVTable example_push_input_adapter_create_string( ExampleStringCallback get_string, void * user_data ); + +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_ADAPTERS_C_EXAMPLE_PUSH_INPUT_ADAPTER_H */ diff --git a/examples/05_cpp/4_c_api_adapter/cpp/exampleadapterimpl.c b/examples/05_cpp/4_c_api_adapter/cpp/exampleadapterimpl.c new file mode 100644 index 000000000..53e6ec2ad --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/cpp/exampleadapterimpl.c @@ -0,0 +1,333 @@ +/* + * Python bindings for the example C adapters + */ +#include +#include +#include "Python.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ExampleOutputAdapter.h" +#include "ExamplePushInputAdapter.h" +#include "ExampleManagedAdapter.h" + +/* + * _example_adapter_manager(engine, properties: dict) -> capsule + * + * Create an example adapter manager. + */ +static PyObject * create_adapter_manager_py( PyObject * self, PyObject * args ) +{ + ( void )self; + + PyObject * engine_capsule = NULL; + PyObject * properties = NULL; + + if( !PyArg_ParseTuple( args, "OO", &engine_capsule, &properties ) ) + { + return NULL; + } + + /* Extract prefix from properties dict */ + const char * prefix = ""; + if( properties && PyDict_Check( properties ) ) + { + PyObject * prefix_obj = PyDict_GetItemString( properties, "prefix" ); + if( prefix_obj && PyUnicode_Check( prefix_obj ) ) + { + prefix = PyUnicode_AsUTF8( prefix_obj ); + } + } + + CCspAdapterManagerVTable vtable = example_managed_adapter_create( prefix ); + if( !vtable.name || !vtable.destroy ) + { + PyErr_SetString( PyExc_MemoryError, "Failed to create adapter manager" ); + return NULL; + } + + return ccsp_py_create_adapter_manager_capsule_owned( &vtable ); +} + +/* + * _example_input_adapter(mgr, engine, pytype, push_mode, scalars) -> capsule + * + * Create an example input adapter that generates incrementing integers. + * Called by input_adapter_def wiring layer with standard arguments. + */ +static PyObject * create_input_adapter_py( PyObject * self, PyObject * args, PyObject * kwargs ) +{ + ( void )self; + ( void )kwargs; /* Not used when called from wiring layer */ + + /* When called from input_adapter_def: + * args = (mgr, engine, pytype, push_mode, scalars) + * scalars is a tuple containing the kwargs defined in the adapter_def + */ + PyObject * mgr = NULL; + PyObject * engine = NULL; + PyObject * pytype = NULL; + PyObject * push_mode = NULL; + PyObject * scalars = NULL; + + if( !PyArg_ParseTuple( args, "OOOOO", &mgr, &engine, &pytype, &push_mode, &scalars ) ) + { + /* Fall back to old behavior for direct calls */ + PyErr_Clear(); + static char * kwlist[] = { "interval_ms", NULL }; + int interval_ms = 100; + + if( !PyArg_ParseTupleAndKeywords( args, kwargs, "|i", kwlist, &interval_ms ) ) + { + return NULL; + } + + CCspPushInputAdapterVTable vtable = example_push_input_adapter_create_int( interval_ms ); + if( !vtable.destroy ) + { + PyErr_SetString( PyExc_MemoryError, "Failed to create input adapter" ); + return NULL; + } + + return ccsp_py_create_input_adapter_capsule_owned( &vtable ); + } + + /* Extract interval_ms from scalars tuple */ + int interval_ms = 100; + if( scalars && PyTuple_Check( scalars ) && PyTuple_Size( scalars ) > 0 ) + { + PyObject * interval_obj = PyTuple_GetItem( scalars, 0 ); + if( interval_obj && PyLong_Check( interval_obj ) ) + { + interval_ms = ( int )PyLong_AsLong( interval_obj ); + } + } + + CCspPushInputAdapterVTable vtable = example_push_input_adapter_create_int( interval_ms ); + if( !vtable.destroy ) + { + PyErr_SetString( PyExc_MemoryError, "Failed to create input adapter" ); + return NULL; + } + + return ccsp_py_create_input_adapter_capsule_owned( &vtable ); +} + +/* + * _example_output_adapter(mgr, engine, scalars) -> capsule + * + * Create an example output adapter that prints values to stdout. + * Called by output_adapter_def wiring layer with standard arguments. + */ +static PyObject * create_output_adapter_py( PyObject * self, PyObject * args, PyObject * kwargs ) +{ + ( void ) self; + ( void ) kwargs; /* Not used when called from wiring layer */ + + /* When called from output_adapter_def: + * args = (mgr, engine, scalars) + * scalars is a tuple containing the kwargs defined in the adapter_def + */ + PyObject * mgr = NULL; + PyObject * engine = NULL; + PyObject * scalars = NULL; + + if( !PyArg_ParseTuple( args, "OOO", &mgr, &engine, &scalars ) ) + { + /* Fall back to old behavior for direct calls */ + PyErr_Clear(); + static char * kwlist[] = { "prefix", NULL }; + const char * prefix = NULL; + + if( !PyArg_ParseTupleAndKeywords( args, kwargs, "|s", kwlist, &prefix ) ) + { + return NULL; + } + + CCspOutputAdapterVTable vtable = example_output_adapter_create( prefix ); + if( !vtable.execute || !vtable.destroy ) + { + PyErr_SetString( PyExc_MemoryError, "Failed to create output adapter" ); + return NULL; + } + + return ccsp_py_create_output_adapter_capsule_owned( &vtable ); + } + + /* Extract prefix from scalars tuple */ + const char * prefix = NULL; + if( scalars && PyTuple_Check( scalars ) && PyTuple_Size( scalars ) > 0 ) + { + PyObject * prefix_obj = PyTuple_GetItem( scalars, 0 ); + if( prefix_obj && prefix_obj != Py_None && PyUnicode_Check( prefix_obj ) ) + { + prefix = PyUnicode_AsUTF8( prefix_obj ); + } + } + + CCspOutputAdapterVTable vtable = example_output_adapter_create( prefix ); + if( !vtable.execute || !vtable.destroy ) + { + PyErr_SetString( PyExc_MemoryError, "Failed to create output adapter" ); + return NULL; + } + + return ccsp_py_create_output_adapter_capsule_owned( &vtable ); +} + +/* + * _example_inspect_struct_type(struct_type) -> dict + * + * Demonstrates using the C struct API to inspect a struct type's fields. + * Returns a dict with field information. + */ +static PyObject * inspect_struct_type_py( PyObject * self, PyObject * args ) +{ + ( void )self; + + PyObject * struct_type = NULL; + + if( !PyArg_ParseTuple( args, "O", &struct_type ) ) + { + return NULL; + } + + /* Check if this is a struct type (has structMeta attribute via PyStructMeta) */ + if( !PyType_Check( struct_type ) ) + { + PyErr_SetString( PyExc_TypeError, "Expected a csp.Struct subclass" ); + return NULL; + } + + /* Try to get the structMeta capsule from the type's __dict__ or via attribute */ + PyObject * struct_meta_capsule = PyObject_GetAttrString( struct_type, "_struct_meta_capsule" ); + if( !struct_meta_capsule ) + { + PyErr_Clear(); + /* Fall back to checking if it looks like a struct type */ + PyErr_SetString( PyExc_TypeError, "Not a valid csp.Struct type (no _struct_meta_capsule)" ); + return NULL; + } + + if( !PyCapsule_CheckExact( struct_meta_capsule ) ) + { + Py_DECREF( struct_meta_capsule ); + PyErr_SetString( PyExc_TypeError, "_struct_meta_capsule is not a capsule" ); + return NULL; + } + + CCspStructMetaHandle meta = ( CCspStructMetaHandle )PyCapsule_GetPointer( struct_meta_capsule, NULL ); + Py_DECREF( struct_meta_capsule ); + + if( !meta ) + { + PyErr_SetString( PyExc_ValueError, "Invalid struct meta capsule" ); + return NULL; + } + + /* Build result dictionary */ + PyObject * result = PyDict_New(); + if( !result ) + { + return NULL; + } + + /* Get struct name */ + const char * name = ccsp_struct_meta_name( meta ); + if( name ) + { + PyObject * name_str = PyUnicode_FromString( name ); + PyDict_SetItemString( result, "name", name_str ); + Py_DECREF( name_str ); + } + + /* Get field count */ + size_t field_count = ccsp_struct_meta_field_count( meta ); + PyObject * count_int = PyLong_FromSize_t( field_count ); + PyDict_SetItemString( result, "field_count", count_int ); + Py_DECREF( count_int ); + + /* Check if strict */ + int is_strict = ccsp_struct_meta_is_strict( meta ); + PyDict_SetItemString( result, "is_strict", is_strict ? Py_True : Py_False ); + + /* Build list of field info */ + PyObject * fields_list = PyList_New( ( Py_ssize_t )field_count ); + if( !fields_list ) + { + Py_DECREF( result ); + return NULL; + } + + for( size_t i = 0; i < field_count; i++ ) + { + CCspStructFieldHandle field = ccsp_struct_meta_field_by_index( meta, i ); + if( !field ) + { + continue; + } + + PyObject * field_dict = PyDict_New(); + if( !field_dict ) + { + Py_DECREF( fields_list ); + Py_DECREF( result ); + return NULL; + } + + const char * field_name = ccsp_struct_field_name( field ); + if( field_name ) + { + PyObject * name_str = PyUnicode_FromString( field_name ); + PyDict_SetItemString( field_dict, "name", name_str ); + Py_DECREF( name_str ); + } + + CCspType field_type = ccsp_struct_field_type( field ); + PyObject * type_int = PyLong_FromLong( ( long )field_type ); + PyDict_SetItemString( field_dict, "type", type_int ); + Py_DECREF( type_int ); + + int is_optional = ccsp_struct_field_is_optional( field ); + PyDict_SetItemString( field_dict, "is_optional", is_optional ? Py_True : Py_False ); + + PyList_SET_ITEM( fields_list, ( Py_ssize_t )i, field_dict ); + } + + PyDict_SetItemString( result, "fields", fields_list ); + Py_DECREF( fields_list ); + + return result; +} + +static PyMethodDef exampleadapter_methods[] = { + { "_example_adapter_manager", ( PyCFunction )create_adapter_manager_py, METH_VARARGS, + "Create an example adapter manager." }, + { "_example_input_adapter", ( PyCFunction )create_input_adapter_py, METH_VARARGS | METH_KEYWORDS, + "Create an example input adapter that generates incrementing integers." }, + { "_example_output_adapter", ( PyCFunction )create_output_adapter_py, METH_VARARGS | METH_KEYWORDS, + "Create an example output adapter that prints values to stdout." }, + { "_example_inspect_struct_type", ( PyCFunction )inspect_struct_type_py, METH_VARARGS, + "Inspect a csp.Struct type's fields using the C struct API." }, + { NULL, NULL, 0, NULL } +}; + +static PyModuleDef exampleadapter_module = { + PyModuleDef_HEAD_INIT, + "_exampleadapterimpl", + "Example C adapter implementations for CSP", + -1, + exampleadapter_methods +}; + +PyMODINIT_FUNC PyInit__exampleadapterimpl( void ) +{ + return PyModule_Create( &exampleadapter_module ); +} \ No newline at end of file diff --git a/examples/05_cpp/4_c_api_adapter/exampleadapter/__init__.py b/examples/05_cpp/4_c_api_adapter/exampleadapter/__init__.py new file mode 100644 index 000000000..ab4affcd9 --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/exampleadapter/__init__.py @@ -0,0 +1,276 @@ +""" +Example C API adapter wiring for CSP. + +This module demonstrates how to wire C adapters (implemented via the C API) +into the CSP Python layer. The C API provides a stable ABI for implementing +adapters in C or any language with C FFI (Rust, Go, Zig, etc.). + +This example provides: + +1. Standalone adapters (no manager): + - example_input: Generates incrementing integers at a configurable interval + - example_output: Prints values to stdout with optional prefix + +2. Managed adapters (with adapter manager): + - ExampleAdapterManager: Adapter manager for coordinated lifecycles + - ExampleAdapterManager.subscribe(): Create input adapter managed by the manager + - ExampleAdapterManager.publish(): Create output adapter managed by the manager +""" + +from typing import TypeVar + +import csp +from csp import ts +from csp.impl.__cspimpl import _cspimpl +from csp.impl.pushadapter import PushGroup +from csp.impl.wiring import input_adapter_def, output_adapter_def + +from . import _exampleadapterimpl + +T = TypeVar("T") + + +# ============================================================================= +# Adapter Manager Pattern +# ============================================================================= + + +class ExampleAdapterManager: + """ + Example adapter manager using the C API. + + The adapter manager pattern allows coordinating the lifecycle of multiple + adapters together. All adapters under the same manager share: + - Startup/shutdown coordination + - Push groups for batching events + - Common configuration (like prefix) + + Usage: + mgr = ExampleAdapterManager(prefix="[MyApp] ") + + @csp.graph + def my_graph(): + data = mgr.subscribe(int, interval_ms=100) + mgr.publish(data) + + csp.run(my_graph, starttime=..., endtime=...) + """ + + def __init__(self, prefix: str = ""): + """ + Create an adapter manager. + + Args: + prefix: Prefix string for all output adapters + """ + self._prefix = prefix + self._push_group = PushGroup() + self._properties = {"prefix": prefix} + + def subscribe( + self, + ts_type: type = int, + interval_ms: int = 100, + push_mode: csp.PushMode = csp.PushMode.NON_COLLAPSING, + ) -> ts["T"]: + """ + Create an input adapter managed by this manager. + + Args: + ts_type: The timeseries type (typically int) + interval_ms: Interval between generated values in milliseconds + push_mode: How to handle incoming data + + Returns: + A CSP timeseries of the specified type + """ + return _managed_input_adapter_def( + self, ts_type, interval_ms=interval_ms, push_mode=push_mode, push_group=self._push_group + ) + + def publish(self, x: ts["T"], prefix: str = None): + """ + Create an output adapter managed by this manager. + + Args: + x: The timeseries to publish + prefix: Optional additional prefix (combined with manager prefix) + """ + effective_prefix = prefix if prefix is not None else self._prefix + return _managed_output_adapter_def(self, x, prefix=effective_prefix) + + def _create(self, engine, memo): + """ + Create the adapter manager implementation. + + This is called by CSP's wiring layer when the graph is built. + It creates a C API adapter manager and bridges it to CSP format. + + Args: + engine: The PyEngine instance + memo: Memoization dict for deduplication + + Returns: + A capsule containing the AdapterManagerExtern* + """ + # Create the C API manager capsule + c_api_capsule = _exampleadapterimpl._example_adapter_manager(engine, self._properties) + + # Bridge to CSP format + return _cspimpl._c_api_adapter_manager_bridge(engine, c_api_capsule) + + +def _create_managed_input_adapter(mgr_capsule, engine, pytype, push_mode, scalars): + """ + Bridge function for managed input adapter. + + scalars: (ExampleAdapterManager, typ, interval_ms, push_group) + """ + # Debug: print scalars to understand format + # print(f"DEBUG: scalars = {scalars}, len = {len(scalars)}") + # for i, s in enumerate(scalars): + # print(f" scalars[{i}] = {s} (type: {type(s).__name__})") + + # scalars format: (manager_instance, typ, interval_ms, push_group) + # Extract interval_ms (index 2) + interval_ms = 100 + for s in scalars: + if isinstance(s, int) and not isinstance(s, bool): + interval_ms = s + break + + # Create the VTable capsule for this adapter + capsule = _exampleadapterimpl._example_input_adapter(interval_ms=interval_ms) + + # Get push group from scalars (last element if it's a PushGroup) + push_group_capsule = None + if len(scalars) > 0 and hasattr(scalars[-1], "__class__") and "PushGroup" in type(scalars[-1]).__name__: + push_group_capsule = scalars[-1] + + # Pass to CSP bridge + return _cspimpl._c_api_push_input_adapter(mgr_capsule, engine, pytype, push_mode, (capsule, push_group_capsule)) + + +def _create_managed_output_adapter(mgr_capsule, engine, scalars): + """ + Bridge function for managed output adapter. + + scalars: (ExampleAdapterManager, prefix) + """ + # Extract prefix from scalars + prefix = scalars[1] if len(scalars) > 1 else "" + if prefix is None: + prefix = "" + + # Create the VTable capsule + capsule = _exampleadapterimpl._example_output_adapter(prefix=prefix) + + # Pass to CSP bridge + return _cspimpl._c_api_output_adapter(mgr_capsule, engine, (int, capsule)) + + +# Managed adapter definitions (with adapter manager) +_managed_input_adapter_def = input_adapter_def( + "example_input_adapter_managed", + _create_managed_input_adapter, + ts["T"], + ExampleAdapterManager, # manager_type + typ="T", + interval_ms=int, + push_group=(object, None), # Accept push_group kwarg +) + +_managed_output_adapter_def = output_adapter_def( + "example_output_adapter_managed", + _create_managed_output_adapter, + ExampleAdapterManager, # manager_type + input=ts["T"], + prefix=str, +) + + +# ============================================================================= +# Standalone Adapters (no manager) +# ============================================================================= + + +def example_input( + ts_type: type = int, + interval_ms: int = 100, + push_mode: csp.PushMode = csp.PushMode.NON_COLLAPSING, +) -> ts["T"]: + """ + Create a standalone input adapter that generates incrementing integers. + + Args: + ts_type: The timeseries type (typically int) + interval_ms: Interval between generated values in milliseconds + push_mode: How to handle incoming data + + Returns: + A CSP timeseries of the specified type + """ + return _standalone_input_adapter_def(ts_type, interval_ms=interval_ms, push_mode=push_mode) + + +def example_output(x: ts["T"], prefix: str = None): + """ + Create a standalone output adapter that prints values to stdout. + + Args: + x: The timeseries to publish + prefix: Optional prefix for output messages + """ + return _standalone_output_adapter_def(x, prefix=prefix) + + +def _create_standalone_input_adapter(mgr, engine, pytype, push_mode, scalars): + """ + Bridge function for standalone input adapter. + + For standalone adapters without a manager, scalars contains: + - scalars[0]: typ (the Python type, e.g., int) + - scalars[1]: interval_ms (int) + """ + # Extract interval_ms from scalars (second element, after typ) + interval_ms = scalars[1] if len(scalars) > 1 else 100 + + # Create the VTable capsule + capsule = _exampleadapterimpl._example_input_adapter(interval_ms=interval_ms) + + # Pass to CSP bridge + return _cspimpl._c_api_push_input_adapter(mgr, engine, pytype, push_mode, (capsule, None)) + + +def _create_standalone_output_adapter(mgr, engine, scalars): + """ + Bridge function for standalone output adapter. + """ + # Extract prefix from scalars + prefix = scalars[0] if scalars else None + # Convert None to empty string as the C function expects a string + if prefix is None: + prefix = "" + + # Create the VTable capsule + capsule = _exampleadapterimpl._example_output_adapter(prefix=prefix) + + # Pass to CSP bridge + return _cspimpl._c_api_output_adapter(mgr, engine, (int, capsule)) + + +# Standalone adapter definitions (no manager required) +_standalone_input_adapter_def = input_adapter_def( + "example_input_adapter_standalone", + _create_standalone_input_adapter, + ts["T"], + typ="T", + interval_ms=int, +) + +_standalone_output_adapter_def = output_adapter_def( + "example_output_adapter_standalone", + _create_standalone_output_adapter, + input=ts["T"], + prefix=str, +) diff --git a/examples/05_cpp/4_c_api_adapter/exampleadapter/__main__.py b/examples/05_cpp/4_c_api_adapter/exampleadapter/__main__.py new file mode 100644 index 000000000..9733b3c33 --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/exampleadapter/__main__.py @@ -0,0 +1,68 @@ +""" +Example C API adapter usage. + +This module demonstrates using C adapters implemented via the CSP C API. +It shows two patterns: + +1. Standalone adapters - Simple adapters without lifecycle coordination +2. Managed adapters - Adapters using an AdapterManager for coordinated lifecycles +""" + +from datetime import datetime, timedelta + +import csp +from exampleadapter import ExampleAdapterManager, example_input, example_output + + +@csp.graph +def standalone_graph(): + """Graph using standalone C API adapters (no manager).""" + data = example_input(int, interval_ms=100) + example_output(data, prefix="[Standalone] ") + + +@csp.graph +def managed_graph(): + """ + Graph using managed C API adapters (with adapter manager). + + The adapter manager coordinates the lifecycle of multiple adapters: + - All adapters start/stop together + - Push groups enable batched event processing + - Shared configuration (like prefix) across adapters + """ + # Create an adapter manager with a common prefix + mgr = ExampleAdapterManager(prefix="[Managed] ") + + # Subscribe to data through the manager + data = mgr.subscribe(int, interval_ms=100) + + # Publish through the manager + mgr.publish(data) + + +def demo_standalone(): + """Demonstrate standalone adapters.""" + print("=== Standalone Adapters ===") + print("Using adapters without an adapter manager.") + print("Each adapter has independent lifecycle.\n") + csp.run(standalone_graph, starttime=datetime.now(), endtime=timedelta(milliseconds=300)) + + +def demo_managed(): + """Demonstrate managed adapters.""" + print("\n=== Managed Adapters ===") + print("Using adapters with an ExampleAdapterManager.") + print("All adapters share coordinated lifecycle and configuration.\n") + csp.run(managed_graph, starttime=datetime.now(), endtime=timedelta(milliseconds=300)) + + +def main(): + """Run the C API adapter examples.""" + demo_standalone() + demo_managed() + print("\n=== Done ===") + + +if __name__ == "__main__": + main() diff --git a/examples/05_cpp/4_c_api_adapter/exampleadapter/test_example.py b/examples/05_cpp/4_c_api_adapter/exampleadapter/test_example.py new file mode 100644 index 000000000..b5ede42a4 --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/exampleadapter/test_example.py @@ -0,0 +1,44 @@ +"""Tests for the C API adapter example.""" + +from datetime import datetime, timedelta + +import csp + +from . import _exampleadapterimpl +from .__main__ import main, managed_graph, standalone_graph + + +def test_standalone_adapters(): + """Test standalone input/output adapters.""" + csp.run(standalone_graph, starttime=datetime.now(), endtime=timedelta(milliseconds=200)) + + +def test_managed_adapters(): + """Test managed adapters with adapter manager.""" + csp.run(managed_graph, starttime=datetime.now(), endtime=timedelta(milliseconds=200)) + + +def test_struct_inspection(): + """Test struct inspection via C API.""" + + class TestStruct(csp.Struct): + name: str + value: int + price: float + + result = _exampleadapterimpl._example_inspect_struct_type(TestStruct) + + assert result["name"] == "TestStruct" + assert result["field_count"] == 3 + assert "is_strict" in result + assert len(result["fields"]) == 3 + + field_names = [f["name"] for f in result["fields"]] + assert "name" in field_names + assert "value" in field_names + assert "price" in field_names + + +def test_main(): + """Test the main entry point.""" + main() diff --git a/examples/05_cpp/4_c_api_adapter/pyproject.toml b/examples/05_cpp/4_c_api_adapter/pyproject.toml new file mode 100644 index 000000000..8c1b40eb0 --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["hatchling", "hatch-cpp", "csp"] +build-backend = "hatchling.build" + +[project] +name = "csp-example-c-api-adapter" +authors = [{name = "the csp authors", email = "CSPOpenSource@point72.com"}] +description = "csp example of C++ adapter" +readme = "README.md" +version = "0.0.1" +requires-python = ">=3.9" +dependencies = ["csp"] + +[tool.hatch.build.targets.wheel] +packages = ["exampleadapter"] + +[tool.hatch.build.hooks.hatch-cpp] +cmake = { root = "." } diff --git a/examples/05_cpp/5_c_api_adapter_rust/Cargo.lock b/examples/05_cpp/5_c_api_adapter_rust/Cargo.lock new file mode 100644 index 000000000..976d218c7 --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/Cargo.lock @@ -0,0 +1,181 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "exampleadapter" +version = "0.1.0" +dependencies = [ + "libc", + "pyo3", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "syn" +version = "2.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" diff --git a/examples/05_cpp/5_c_api_adapter_rust/Cargo.toml b/examples/05_cpp/5_c_api_adapter_rust/Cargo.toml new file mode 100644 index 000000000..6c18467b5 --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "exampleadapter" +version = "0.1.0" +edition = "2021" +description = "Example Rust adapter for CSP using the C API" +license = "Apache-2.0" +authors = ["CSP Contributors"] + +[lib] +name = "exampleadapter" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = { version = "0.24", features = ["extension-module"] } +libc = "0.2" + +[build-dependencies] +# For generating Rust bindings from C headers +# bindgen = "0.69" + +[features] +default = [] diff --git a/examples/05_cpp/5_c_api_adapter_rust/README.md b/examples/05_cpp/5_c_api_adapter_rust/README.md new file mode 100644 index 000000000..408046f53 --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/README.md @@ -0,0 +1,210 @@ +# Custom Rust Adapter via C API + +This example demonstrates how to create CSP adapters in **Rust** using the stable C API +and **PyO3** for Python bindings. It is built using **hatch-rs** for seamless integration +with Python's build system. + +## Overview + +The example implements: + +- **RustAdapterManager** - Manages adapter lifecycle +- **RustInputAdapter** - Push input adapter with lifecycle callbacks +- **RustOutputAdapter** - Output adapter that logs received values + +## Project Structure + +``` +5_c_api_adapter_rust/ +├── Cargo.toml # Rust package manifest +├── pyproject.toml # Python package config (uses hatch-rs) +├── build.rs # Build script for linking +├── src/ +│ ├── lib.rs # PyO3 module with Python bindings +│ ├── bindings.rs # FFI bindings for C API +│ ├── adapter_manager.rs # Adapter manager implementation +│ ├── input_adapter.rs # Push input adapter +│ └── output_adapter.rs # Output adapter +└── exampleadapter/ + ├── __init__.py # Package init + └── __main__.py # Python wrapper and demo +``` + +## Building + +### Prerequisites + +- Rust toolchain (1.70+) +- Python 3.9+ +- CSP installed (`pip install csp`) +- hatch-rs installed (`pip install hatch-rs`) + +### Build Commands + +```bash +cd examples/05_cpp/5_c_api_adapter_rust + +# Build the wheel (recommended) +hatch-build --hooks-only -t wheel + +# Run tests +cargo test +``` + +## Current Status + +This example demonstrates the **structure and patterns** for implementing CSP adapters in +Rust. The module can be imported and the VTable capsules are properly created: + +```python +>>> import exampleadapter +>>> exampleadapter._example_input_adapter(100) + +``` + +### Integration with CSP + +CSP exports C API symbols from its shared libraries, making them available at runtime. +To fully integrate Rust adapters with CSP: + +1. **Create VTable capsules** in Rust (already done in this example) +1. **Use Python bridge functions** to connect capsules to CSP's wiring layer + +The Python wiring follows the same pattern as the C example: + +```python +from csp.impl.__cspimpl import _cspimpl +from csp.impl.wiring import input_adapter_def + +from . import _exampleadapterimpl # Rust extension + +def _create_rust_input_adapter(mgr, engine, pytype, push_mode, scalars): + interval_ms = scalars[1] if len(scalars) > 1 else 100 + + # Create capsule from Rust + capsule = _exampleadapterimpl._example_input_adapter(interval_ms) + + # Pass to CSP bridge + return _cspimpl._c_api_push_input_adapter( + mgr, engine, pytype, push_mode, (capsule, None) + ) + +rust_input = input_adapter_def( + "rust_input", + _create_rust_input_adapter, + ts["T"], + typ="T", + interval_ms=int, +) +``` + +### Limitations + +The `start` callback in push input adapters receives an adapter handle, which is needed +to push data into the graph via functions like `ccsp_push_input_adapter_push_int64`. +To call these functions from Rust: + +- Link against CSP's libraries at build time, or +- Use `dlopen`/`dlsym` to load symbols at runtime +- Or build as part of CSP's cmake build system + +See the [C API Adapter example](../4_c_api_adapter/) for a complete working implementation. + +## Key Concepts + +### PyO3 Integration + +The `lib.rs` module exposes Rust functions to Python using PyO3: + +```rust +#[pyfunction] +fn _example_input_adapter(py: Python<'_>, interval_ms: i32) -> PyResult { + let adapter = Box::new(RustInputAdapter::new(interval_ms as u64)); + let user_data = Box::into_raw(adapter) as *mut c_void; + + let vtable = CCspPushInputAdapterVTable { + user_data, + start: Some(rust_input_adapter_start), + stop: Some(rust_input_adapter_stop), + destroy: Some(rust_input_adapter_destroy), + }; + + create_input_adapter_capsule(py, vtable) +} +``` + +### C API VTables + +The adapters implement the CSP C ABI by creating VTable structures with callbacks: + +```rust +#[repr(C)] +pub struct CCspPushInputAdapterVTable { + pub user_data: *mut c_void, + pub start: Option, + pub stop: Option, + pub destroy: Option, +} +``` + +### Python Capsules + +VTables are passed to Python as capsules, which CSP's adapter system understands. +Static strings are used for capsule names to ensure they remain valid: + +```rust +static INPUT_ADAPTER_CAPSULE_NAME: &[u8] = b"csp.c.InputAdapterCapsule\0"; + +pub fn create_input_adapter_capsule( + py: Python<'_>, + vtable: CCspPushInputAdapterVTable, +) -> PyResult { + let vtable_ptr = Box::into_raw(Box::new(vtable)) as *mut c_void; + + unsafe { + let capsule = ffi::PyCapsule_New( + vtable_ptr, + INPUT_ADAPTER_CAPSULE_NAME.as_ptr() as *const c_char, + Some(destructor), + ); + Ok(PyObject::from_owned_ptr(py, capsule)) + } +} +``` + +### Memory Management + +Rust adapters use `Box` for heap allocation with proper cleanup: + +```rust +// Create adapter (transfers ownership to C via capsule) +let adapter = Box::new(RustInputAdapter::new(interval_ms)); +let user_data = Box::into_raw(adapter) as *mut c_void; + +// Destroy callback (reclaims ownership and drops) +pub unsafe extern "C" fn rust_input_adapter_destroy(user_data: *mut c_void) { + if !user_data.is_null() { + let _ = Box::from_raw(user_data as *mut RustInputAdapter); + } +} +``` + +## hatch-rs Configuration + +The `pyproject.toml` configures hatch-rs to build the Rust extension: + +```toml +[build-system] +requires = ["hatchling", "hatch-rs", "csp"] +build-backend = "hatchling.build" + +[tool.hatch.build.hooks.hatch-rs] +module = "exampleadapter" +``` + +## See Also + +- [C API Adapter](../4_c_api_adapter/README.md) - C implementation example +- [CSP Documentation](https://github.com/Point72/csp) +- [PyO3 Documentation](https://pyo3.rs/) +- [hatch-rs](https://github.com/python-project-templates/hatch-rs) diff --git a/examples/05_cpp/5_c_api_adapter_rust/build.rs b/examples/05_cpp/5_c_api_adapter_rust/build.rs new file mode 100644 index 000000000..a38e3889f --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/build.rs @@ -0,0 +1,24 @@ +//! Build script for the CSP Rust adapter. +//! +//! This build script configures linking for the CSP C API. +//! The actual linking to CSP happens at runtime when Python loads the module. + +fn main() { + // Tell Cargo to re-run this script if these files change + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src/lib.rs"); + + // For macOS, we need to allow undefined symbols since CSP functions + // are resolved at runtime when Python loads both our module and CSP + #[cfg(target_os = "macos")] + { + println!("cargo:rustc-link-arg=-undefined"); + println!("cargo:rustc-link-arg=dynamic_lookup"); + } + + // On Linux, allow undefined symbols to be resolved at runtime + #[cfg(target_os = "linux")] + { + println!("cargo:rustc-link-arg=-Wl,--allow-shlib-undefined"); + } +} diff --git a/examples/05_cpp/5_c_api_adapter_rust/exampleadapter/__init__.py b/examples/05_cpp/5_c_api_adapter_rust/exampleadapter/__init__.py new file mode 100644 index 000000000..9d6270613 --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/exampleadapter/__init__.py @@ -0,0 +1,262 @@ +""" +Example Rust adapter wiring for CSP. + +This module demonstrates how to wire Rust adapters (implemented via the C API +and PyO3) into the CSP Python layer. The C API provides a stable ABI that +Rust code can implement using FFI. + +This example provides: + +1. Standalone adapters (no manager): + - example_input: Generates incrementing integers at a configurable interval + - example_output: Prints values to stdout with optional prefix + +2. Managed adapters (with adapter manager): + - ExampleAdapterManager: Adapter manager for coordinated lifecycles + - ExampleAdapterManager.subscribe(): Create input adapter managed by the manager + - ExampleAdapterManager.publish(): Create output adapter managed by the manager +""" + +from typing import TypeVar + +import csp +from csp import ts +from csp.impl.__cspimpl import _cspimpl +from csp.impl.pushadapter import PushGroup +from csp.impl.wiring import input_adapter_def, output_adapter_def + +from .exampleadapter import ( + _example_adapter_manager, + _example_input_adapter, + _example_output_adapter, +) + +__all__ = [ + "ExampleAdapterManager", + "example_input", + "example_output", +] + + +T = TypeVar("T") + + +# ============================================================================= +# Adapter Manager Pattern +# ============================================================================= + + +class ExampleAdapterManager: + """ + Example adapter manager using the Rust + C API. + + The adapter manager pattern allows coordinating the lifecycle of multiple + adapters together. All adapters under the same manager share: + - Startup/shutdown coordination + - Push groups for batching events + - Common configuration (like prefix) + + Usage: + mgr = ExampleAdapterManager(prefix="[MyApp] ") + + @csp.graph + def my_graph(): + data = mgr.subscribe(int, interval_ms=100) + mgr.publish(data) + + csp.run(my_graph, starttime=..., endtime=...) + """ + + def __init__(self, prefix: str = ""): + """ + Create an adapter manager. + + Args: + prefix: Prefix string for all output adapters + """ + self._prefix = prefix + self._push_group = PushGroup() + self._properties = {"prefix": prefix} + + def subscribe( + self, + ts_type: type = int, + interval_ms: int = 100, + push_mode: csp.PushMode = csp.PushMode.NON_COLLAPSING, + ) -> ts["T"]: + """ + Create an input adapter managed by this manager. + + Args: + ts_type: The timeseries type (typically int) + interval_ms: Interval between generated values in milliseconds + push_mode: How to handle incoming data + + Returns: + A CSP timeseries of the specified type + """ + return _managed_input_adapter_def( + self, ts_type, interval_ms=interval_ms, push_mode=push_mode, push_group=self._push_group + ) + + def publish(self, x: ts["T"], prefix: str = None): + """ + Create an output adapter managed by this manager. + + Args: + x: The timeseries to publish + prefix: Optional additional prefix (combined with manager prefix) + """ + effective_prefix = prefix if prefix is not None else self._prefix + return _managed_output_adapter_def(self, x, prefix=effective_prefix) + + def _create(self, engine, memo): + """ + Create the adapter manager implementation. + + This is called by CSP's wiring layer when the graph is built. + It creates a Rust/C API adapter manager and bridges it to CSP format. + + Args: + engine: The PyEngine instance + memo: Memoization dict for deduplication + + Returns: + A capsule containing the AdapterManagerExtern* + """ + # Create the C API manager capsule + c_api_capsule = _example_adapter_manager(engine, self._properties) + + # Bridge to CSP format + return _cspimpl._c_api_adapter_manager_bridge(engine, c_api_capsule) + + +def _create_managed_input_adapter(mgr_capsule, engine, pytype, push_mode, scalars): + """ + Bridge function for managed input adapter. + + scalars: (ExampleAdapterManager, typ, interval_ms, push_group) + """ + # Extract interval_ms (find first int in scalars) + interval_ms = 100 + for s in scalars: + if isinstance(s, int) and not isinstance(s, bool): + interval_ms = s + break + + # Create the VTable capsule for this adapter + capsule = _example_input_adapter(interval_ms=interval_ms) + + # Get push group from scalars (last element if it's a PushGroup) + push_group_capsule = None + if len(scalars) > 0 and hasattr(scalars[-1], "__class__") and "PushGroup" in type(scalars[-1]).__name__: + push_group_capsule = scalars[-1] + + # Pass to CSP bridge + return _cspimpl._c_api_push_input_adapter(mgr_capsule, engine, pytype, push_mode, (capsule, push_group_capsule)) + + +def _create_managed_output_adapter(mgr_capsule, engine, scalars): + """ + Bridge function for managed output adapter. + + scalars: (ExampleAdapterManager, prefix) + """ + # Extract prefix from scalars + prefix = scalars[1] if len(scalars) > 1 else "" + if prefix is None: + prefix = "" + + # Create the VTable capsule + capsule = _example_output_adapter(prefix=prefix) + + # Pass to CSP bridge + return _cspimpl._c_api_output_adapter(mgr_capsule, engine, (int, capsule)) + + +# Managed adapter definitions (with adapter manager) +_managed_input_adapter_def = input_adapter_def( + "rust_input_adapter_managed", + _create_managed_input_adapter, + ts["T"], + ExampleAdapterManager, # manager_type + typ="T", + interval_ms=int, + push_group=(object, None), # Accept push_group kwarg +) + +_managed_output_adapter_def = output_adapter_def( + "rust_output_adapter_managed", + _create_managed_output_adapter, + ExampleAdapterManager, # manager_type + input=ts["T"], + prefix=str, +) + + +# ============================================================================= +# Standalone Adapters (no manager) +# ============================================================================= + + +def example_input( + ts_type: type = int, + interval_ms: int = 100, + push_mode: csp.PushMode = csp.PushMode.NON_COLLAPSING, +) -> ts["T"]: + """ + Create an input adapter that generates incrementing integers via Rust + C API. + + Args: + ts_type: The timeseries output type + interval_ms: Interval between generated values in milliseconds + push_mode: How to handle multiple values in same engine cycle + + Returns: + A timeseries of the specified type + """ + return _standalone_input_adapter_def(typ=ts_type, interval_ms=interval_ms, push_mode=push_mode) + + +def example_output(x: ts["T"], prefix: str = "") -> ts["T"]: + """ + Create an output adapter that prints values via Rust + C API. + + Args: + x: Input timeseries to print + prefix: Optional prefix for output messages + + Returns: + The input timeseries (pass-through) + """ + return _standalone_output_adapter_def(x, prefix=prefix) + + +def _create_standalone_input_adapter(mgr, engine, pytype, push_mode, scalars): + """Bridge function for standalone input adapter.""" + interval_ms = scalars[1] if len(scalars) > 1 else 100 + capsule = _example_input_adapter(interval_ms) + return _cspimpl._c_api_push_input_adapter(mgr, engine, pytype, push_mode, (capsule, None)) + + +def _create_standalone_output_adapter(mgr, engine, scalars): + """Bridge function for standalone output adapter.""" + prefix = scalars[0] if scalars else "" + capsule = _example_output_adapter(prefix) + return _cspimpl._c_api_output_adapter(mgr, engine, (int, capsule)) + + +_standalone_input_adapter_def = input_adapter_def( + "rust_input_adapter_standalone", + _create_standalone_input_adapter, + ts["T"], + typ="T", + interval_ms=int, +) + +_standalone_output_adapter_def = output_adapter_def( + "rust_output_adapter_standalone", + _create_standalone_output_adapter, + input=ts["T"], + prefix=str, +) diff --git a/examples/05_cpp/5_c_api_adapter_rust/exampleadapter/__main__.py b/examples/05_cpp/5_c_api_adapter_rust/exampleadapter/__main__.py new file mode 100644 index 000000000..5e2945710 --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/exampleadapter/__main__.py @@ -0,0 +1,68 @@ +""" +Example Rust adapter usage. + +This module demonstrates using Rust adapters implemented via the CSP C API. +It shows two patterns: + +1. Standalone adapters - Simple adapters without lifecycle coordination +2. Managed adapters - Adapters using an AdapterManager for coordinated lifecycles +""" + +from datetime import datetime, timedelta + +import csp +from exampleadapter import ExampleAdapterManager, example_input, example_output + + +@csp.graph +def standalone_graph(): + """Graph using standalone Rust adapters (no manager).""" + data = example_input(int, interval_ms=100) + example_output(data, prefix="[Standalone] ") + + +@csp.graph +def managed_graph(): + """ + Graph using managed Rust adapters (with adapter manager). + + The adapter manager coordinates the lifecycle of multiple adapters: + - All adapters start/stop together + - Push groups enable batched event processing + - Shared configuration (like prefix) across adapters + """ + # Create an adapter manager with a common prefix + mgr = ExampleAdapterManager(prefix="[Managed] ") + + # Subscribe to data through the manager + data = mgr.subscribe(int, interval_ms=100) + + # Publish through the manager + mgr.publish(data) + + +def demo_standalone(): + """Demonstrate standalone adapters.""" + print("=== Standalone Adapters ===") + print("Using adapters without an adapter manager.") + print("Each adapter has independent lifecycle.\n") + csp.run(standalone_graph, starttime=datetime.now(), endtime=timedelta(milliseconds=300)) + + +def demo_managed(): + """Demonstrate managed adapters.""" + print("\n=== Managed Adapters ===") + print("Using adapters with an ExampleAdapterManager.") + print("All adapters share coordinated lifecycle and configuration.\n") + csp.run(managed_graph, starttime=datetime.now(), endtime=timedelta(milliseconds=300)) + + +def main(): + """Run the Rust adapter examples.""" + demo_standalone() + demo_managed() + print("\n=== Done ===") + + +if __name__ == "__main__": + main() diff --git a/examples/05_cpp/5_c_api_adapter_rust/exampleadapter/test_exampleadapter.py b/examples/05_cpp/5_c_api_adapter_rust/exampleadapter/test_exampleadapter.py new file mode 100644 index 000000000..bf942e075 --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/exampleadapter/test_exampleadapter.py @@ -0,0 +1,22 @@ +"""Tests for the Rust adapter example.""" + +from datetime import datetime, timedelta + +import csp + +from .__main__ import main, managed_graph, standalone_graph + + +def test_standalone_adapters(): + """Test standalone Rust input/output adapters.""" + csp.run(standalone_graph, starttime=datetime.now(), endtime=timedelta(milliseconds=200)) + + +def test_managed_adapters(): + """Test managed adapters with adapter manager.""" + csp.run(managed_graph, starttime=datetime.now(), endtime=timedelta(milliseconds=200)) + + +def test_main(): + """Test the main entry point.""" + main() diff --git a/examples/05_cpp/5_c_api_adapter_rust/pyproject.toml b/examples/05_cpp/5_c_api_adapter_rust/pyproject.toml new file mode 100644 index 000000000..f8696cd5c --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["hatchling", "hatch-rs", "csp"] +build-backend = "hatchling.build" + +[project] +name = "csp-example-c-api-rust-adapter" +authors = [{name = "the csp authors", email = "CSPOpenSource@point72.com"}] +description = "csp example of Rust adapter using C API and PyO3" +readme = "README.md" +version = "0.0.1" +requires-python = ">=3.9" +dependencies = ["csp"] + +[tool.hatch.build.targets.wheel] +packages = ["exampleadapter"] + +[tool.hatch.build.hooks.hatch-rs] +module = "exampleadapter" diff --git a/examples/05_cpp/5_c_api_adapter_rust/src/adapter_manager.rs b/examples/05_cpp/5_c_api_adapter_rust/src/adapter_manager.rs new file mode 100644 index 000000000..dc67bc78c --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/src/adapter_manager.rs @@ -0,0 +1,181 @@ +//! Adapter Manager implementation in Rust +//! +//! This module demonstrates how to implement a CSP adapter manager in Rust +//! using the C API FFI bindings. The adapter manager coordinates the lifecycle +//! of related adapters. + +use std::ffi::{c_char, c_void, CString}; + +use crate::bindings::{CCspAdapterManagerHandle, CCspDateTime}; + +/// Adapter manager that coordinates input and output adapters. +/// +/// The manager handles: +/// - Starting and stopping all managed adapters together +/// - Status reporting +/// - Simulation time slicing (for replay mode) +pub struct RustAdapterManager { + /// Prefix for log messages + #[allow(dead_code)] + prefix: String, + + /// C-compatible name string (stored to keep pointer valid) + name_cstring: CString, + + /// Whether the manager is currently running + running: bool, +} + +impl RustAdapterManager { + /// Create a new adapter manager. + pub fn new(prefix: String) -> Self { + let name = format!("RustAdapterManager({})", prefix); + let name_cstring = CString::new(name).unwrap_or_else(|_| CString::new("RustAdapterManager").unwrap()); + + Self { + prefix, + name_cstring, + running: false, + } + } + + /// Get the name of this adapter manager. + pub fn name(&self) -> *const c_char { + self.name_cstring.as_ptr() + } + + /// Called when the graph starts. + pub fn start(&mut self, start_time: CCspDateTime, end_time: CCspDateTime) { + self.running = true; + eprintln!( + "[{}] Started. Time range: {} - {} ns", + self.name_cstring.to_string_lossy(), + start_time, + end_time + ); + } + + /// Called when the graph stops. + pub fn stop(&mut self) { + self.running = false; + eprintln!( + "[{}] Stopped.", + self.name_cstring.to_string_lossy() + ); + } + + /// Process simulation time slice. + /// + /// For realtime adapters that don't support simulation, return 0. + /// For sim adapters, process data at the given time and return the next time. + pub fn process_next_sim_time_slice(&mut self, _time: CCspDateTime) -> CCspDateTime { + // This example doesn't support simulation mode + 0 + } +} + +impl Default for RustAdapterManager { + fn default() -> Self { + Self::new(String::new()) + } +} + +// ============================================================================ +// C ABI callback functions +// ============================================================================ + +/// Return the name of this adapter manager. +/// +/// # Safety +/// +/// Called from C code with valid pointer. +pub unsafe extern "C" fn rust_adapter_manager_name(user_data: *mut c_void) -> *const c_char { + if user_data.is_null() { + return std::ptr::null(); + } + let manager = &*(user_data as *const RustAdapterManager); + manager.name() +} + +/// Process simulation time slice. +/// +/// # Safety +/// +/// Called from C code with valid pointer. +pub unsafe extern "C" fn rust_adapter_manager_process_sim_time( + user_data: *mut c_void, + time: CCspDateTime, +) -> CCspDateTime { + if user_data.is_null() { + return 0; + } + let manager = &mut *(user_data as *mut RustAdapterManager); + manager.process_next_sim_time_slice(time) +} + +/// Start callback for the adapter manager. +/// +/// # Safety +/// +/// Called from C code with valid pointers. +pub unsafe extern "C" fn rust_adapter_manager_start( + user_data: *mut c_void, + _manager: CCspAdapterManagerHandle, + start_time: CCspDateTime, + end_time: CCspDateTime, +) { + if user_data.is_null() { + return; + } + let rust_manager = &mut *(user_data as *mut RustAdapterManager); + rust_manager.start(start_time, end_time); +} + +/// Stop callback for the adapter manager. +/// +/// # Safety +/// +/// Called from C code with valid pointer. +pub unsafe extern "C" fn rust_adapter_manager_stop(user_data: *mut c_void) { + if user_data.is_null() { + return; + } + let rust_manager = &mut *(user_data as *mut RustAdapterManager); + rust_manager.stop(); +} + +/// Destroy callback for the adapter manager. +/// +/// # Safety +/// +/// Called from C code. Takes ownership and drops the manager. +pub unsafe extern "C" fn rust_adapter_manager_destroy(user_data: *mut c_void) { + if !user_data.is_null() { + let _ = Box::from_raw(user_data as *mut RustAdapterManager); + // Box is dropped here, freeing memory + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_manager() { + let manager = RustAdapterManager::new("test".to_string()); + assert!(!manager.running); + assert!(manager.prefix == "test"); + } + + #[test] + fn test_name() { + let manager = RustAdapterManager::new("prefix".to_string()); + unsafe { + let name_ptr = manager.name(); + let name = std::ffi::CStr::from_ptr(name_ptr); + assert!(name.to_string_lossy().contains("RustAdapterManager")); + assert!(name.to_string_lossy().contains("prefix")); + } + } +} + diff --git a/examples/05_cpp/5_c_api_adapter_rust/src/bindings.rs b/examples/05_cpp/5_c_api_adapter_rust/src/bindings.rs new file mode 100644 index 000000000..7f3b2ed62 --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/src/bindings.rs @@ -0,0 +1,328 @@ +//! FFI bindings for the CSP C API +//! +//! These bindings match the C ABI structures defined in the CSP headers. +//! See `cpp/csp/engine/c/` for the C header files. +//! +//! Note: The actual CSP C API functions (ccsp_*) are resolved at runtime when +//! this module is loaded alongside CSP. For standalone testing, these functions +//! are stubbed. + +use std::ffi::{c_char, c_void}; +use pyo3::prelude::*; +use pyo3::ffi; + +/// CSP DateTime type (nanoseconds since epoch) +pub type CCspDateTime = i64; + +/// Opaque handle to the CSP engine +pub type CCspEngineHandle = *mut c_void; + +/// Opaque handle to a push input adapter +pub type CCspPushInputAdapterHandle = *mut c_void; + +/// Opaque handle to an adapter manager +pub type CCspAdapterManagerHandle = *mut c_void; + +/// Opaque handle to an input (for output adapters) +pub type CCspInputHandle = *mut c_void; + +/// CSP type enumeration +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CCspType { + Unknown = 0, + Bool = 1, + Int8 = 2, + Uint8 = 3, + Int16 = 4, + Uint16 = 5, + Int32 = 6, + Uint32 = 7, + Int64 = 8, + Uint64 = 9, + Double = 10, + DateTime = 11, + TimeDelta = 12, + Date = 13, + Time = 14, + Enum = 15, + String = 16, + Struct = 17, + Array = 18, + DialectGeneric = 19, +} + +/// CSP Error codes +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CCspErrorCode { + Ok = 0, + Error = 1, + InvalidArgument = 2, + NullPointer = 3, + TypeMismatch = 4, + NotFound = 5, +} + +// ============================================================================ +// VTable structures (matching the C API headers) +// ============================================================================ + +/// VTable for output adapter callbacks +#[repr(C)] +pub struct CCspOutputAdapterVTable { + pub user_data: *mut c_void, + + /// Called when the graph starts + pub start: Option< + unsafe extern "C" fn( + user_data: *mut c_void, + engine: CCspEngineHandle, + start_time: CCspDateTime, + end_time: CCspDateTime, + ), + >, + + /// Called when the graph stops + pub stop: Option, + + /// Called each time the input has a new value + pub execute: Option< + unsafe extern "C" fn( + user_data: *mut c_void, + engine: CCspEngineHandle, + input: CCspInputHandle, + ), + >, + + /// Called to destroy the adapter + pub destroy: Option, +} + +/// VTable for push input adapter callbacks +#[repr(C)] +pub struct CCspPushInputAdapterVTable { + pub user_data: *mut c_void, + + /// Called when the graph starts + pub start: Option< + unsafe extern "C" fn( + user_data: *mut c_void, + engine: CCspEngineHandle, + adapter: CCspPushInputAdapterHandle, + start_time: CCspDateTime, + end_time: CCspDateTime, + ), + >, + + /// Called when the graph stops + pub stop: Option, + + /// Called to destroy the adapter + pub destroy: Option, +} + +/// VTable for adapter manager callbacks +#[repr(C)] +pub struct CCspAdapterManagerVTable { + pub user_data: *mut c_void, + + /// Return the name of this adapter manager + pub name: Option *const c_char>, + + /// Process simulation time slice + pub process_next_sim_time_slice: + Option CCspDateTime>, + + /// Called to destroy the adapter manager + pub destroy: Option, + + /// Called when the graph starts + pub start: Option< + unsafe extern "C" fn( + user_data: *mut c_void, + manager: CCspAdapterManagerHandle, + start_time: CCspDateTime, + end_time: CCspDateTime, + ), + >, + + /// Called when the graph stops + pub stop: Option, +} + +// ============================================================================ +// CSP C API function type definitions +// +// These are resolved at runtime when loaded alongside CSP. +// We use weak linking / dynamic_lookup on macOS. +// ============================================================================ + +// ============================================================================ +// CSP C API function declarations +// +// These are resolved at runtime via dynamic lookup (see build.rs). +// The symbols are provided by CSP's shared library when the Python module +// is loaded alongside CSP. +// ============================================================================ + +extern "C" { + // Engine functions + pub fn ccsp_engine_now(engine: CCspEngineHandle) -> CCspDateTime; + + // Input functions (for output adapters) + pub fn ccsp_input_get_type(input: CCspInputHandle) -> CCspType; + pub fn ccsp_input_get_last_bool(input: CCspInputHandle, out: *mut i8) -> CCspErrorCode; + pub fn ccsp_input_get_last_int64(input: CCspInputHandle, out: *mut i64) -> CCspErrorCode; + pub fn ccsp_input_get_last_double(input: CCspInputHandle, out: *mut f64) -> CCspErrorCode; + pub fn ccsp_input_get_last_string( + input: CCspInputHandle, + out_data: *mut *const c_char, + out_length: *mut usize, + ) -> CCspErrorCode; + pub fn ccsp_input_get_last_datetime(input: CCspInputHandle, out: *mut CCspDateTime) -> CCspErrorCode; + + // Push functions (for input adapters) + pub fn ccsp_push_input_adapter_push_bool( + adapter: CCspPushInputAdapterHandle, + value: i8, + batch: *mut c_void, + ) -> CCspErrorCode; + pub fn ccsp_push_input_adapter_push_int64( + adapter: CCspPushInputAdapterHandle, + value: i64, + batch: *mut c_void, + ) -> CCspErrorCode; + pub fn ccsp_push_input_adapter_push_double( + adapter: CCspPushInputAdapterHandle, + value: f64, + batch: *mut c_void, + ) -> CCspErrorCode; + pub fn ccsp_push_input_adapter_push_string( + adapter: CCspPushInputAdapterHandle, + data: *const c_char, + length: usize, + batch: *mut c_void, + ) -> CCspErrorCode; +} + +// ============================================================================ +// Capsule creation helpers +// ============================================================================ + +/// Capsule name for output adapters (must match C API) +pub const CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME: &[u8] = b"csp.c.OutputAdapterCapsule\0"; + +/// Capsule name for input adapters (must match C API) +pub const CSP_C_INPUT_ADAPTER_CAPSULE_NAME: &[u8] = b"csp.c.InputAdapterCapsule\0"; + +/// Capsule name for adapter managers (must match C API) +pub const CSP_C_ADAPTER_MANAGER_CAPSULE_NAME: &[u8] = b"csp.c.AdapterManagerCapsule\0"; + +/// Destructor for output adapter capsules +unsafe extern "C" fn output_adapter_capsule_destructor(capsule: *mut ffi::PyObject) { + let name = CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME.as_ptr() as *const c_char; + let ptr = ffi::PyCapsule_GetPointer(capsule, name); + if !ptr.is_null() { + let vtable = ptr as *mut CCspOutputAdapterVTable; + if let Some(destroy) = (*vtable).destroy { + destroy((*vtable).user_data); + } + drop(Box::from_raw(vtable)); + } +} + +/// Destructor for input adapter capsules +unsafe extern "C" fn input_adapter_capsule_destructor(capsule: *mut ffi::PyObject) { + let name = CSP_C_INPUT_ADAPTER_CAPSULE_NAME.as_ptr() as *const c_char; + let ptr = ffi::PyCapsule_GetPointer(capsule, name); + if !ptr.is_null() { + let vtable = ptr as *mut CCspPushInputAdapterVTable; + if let Some(destroy) = (*vtable).destroy { + destroy((*vtable).user_data); + } + drop(Box::from_raw(vtable)); + } +} + +/// Destructor for adapter manager capsules +unsafe extern "C" fn adapter_manager_capsule_destructor(capsule: *mut ffi::PyObject) { + let name = CSP_C_ADAPTER_MANAGER_CAPSULE_NAME.as_ptr() as *const c_char; + let ptr = ffi::PyCapsule_GetPointer(capsule, name); + if !ptr.is_null() { + let vtable = ptr as *mut CCspAdapterManagerVTable; + if let Some(destroy) = (*vtable).destroy { + destroy((*vtable).user_data); + } + drop(Box::from_raw(vtable)); + } +} + +// Static capsule names (must be null-terminated and live forever) +static OUTPUT_ADAPTER_CAPSULE_NAME: &[u8] = b"csp.c.OutputAdapterCapsule\0"; +static INPUT_ADAPTER_CAPSULE_NAME: &[u8] = b"csp.c.InputAdapterCapsule\0"; +static ADAPTER_MANAGER_CAPSULE_NAME: &[u8] = b"csp.c.AdapterManagerCapsule\0"; + +/// Create a Python capsule wrapping an output adapter VTable +pub fn create_output_adapter_capsule( + py: Python<'_>, + vtable: CCspOutputAdapterVTable, +) -> PyResult { + let vtable_box = Box::new(vtable); + let vtable_ptr = Box::into_raw(vtable_box) as *mut c_void; + + unsafe { + let capsule = ffi::PyCapsule_New( + vtable_ptr, + OUTPUT_ADAPTER_CAPSULE_NAME.as_ptr() as *const c_char, + Some(output_adapter_capsule_destructor), + ); + if capsule.is_null() { + return Err(PyErr::fetch(py)); + } + Ok(PyObject::from_owned_ptr(py, capsule)) + } +} + +/// Create a Python capsule wrapping an input adapter VTable +pub fn create_input_adapter_capsule( + py: Python<'_>, + vtable: CCspPushInputAdapterVTable, +) -> PyResult { + let vtable_box = Box::new(vtable); + let vtable_ptr = Box::into_raw(vtable_box) as *mut c_void; + + unsafe { + let capsule = ffi::PyCapsule_New( + vtable_ptr, + INPUT_ADAPTER_CAPSULE_NAME.as_ptr() as *const c_char, + Some(input_adapter_capsule_destructor), + ); + if capsule.is_null() { + return Err(PyErr::fetch(py)); + } + Ok(PyObject::from_owned_ptr(py, capsule)) + } +} + +/// Create a Python capsule wrapping an adapter manager VTable +pub fn create_adapter_manager_capsule( + py: Python<'_>, + vtable: CCspAdapterManagerVTable, +) -> PyResult { + let vtable_box = Box::new(vtable); + let vtable_ptr = Box::into_raw(vtable_box) as *mut c_void; + + unsafe { + let capsule = ffi::PyCapsule_New( + vtable_ptr, + ADAPTER_MANAGER_CAPSULE_NAME.as_ptr() as *const c_char, + Some(adapter_manager_capsule_destructor), + ); + if capsule.is_null() { + return Err(PyErr::fetch(py)); + } + Ok(PyObject::from_owned_ptr(py, capsule)) + } +} + diff --git a/examples/05_cpp/5_c_api_adapter_rust/src/input_adapter.rs b/examples/05_cpp/5_c_api_adapter_rust/src/input_adapter.rs new file mode 100644 index 000000000..6ef59fa5f --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/src/input_adapter.rs @@ -0,0 +1,201 @@ +//! Input Adapter implementation in Rust +//! +//! This module demonstrates how to implement a CSP push input adapter in Rust +//! using the C API FFI bindings. +//! +//! The adapter spawns a background thread that periodically pushes integer +//! values to the CSP graph using the C API. + +use std::ffi::c_void; +use std::ptr; +use std::sync::atomic::{AtomicBool, AtomicI64, AtomicPtr, Ordering}; +use std::sync::Arc; +use std::thread::{self, JoinHandle}; +use std::time::Duration; + +use crate::bindings::{ + ccsp_push_input_adapter_push_int64, CCspDateTime, CCspEngineHandle, + CCspPushInputAdapterHandle, +}; + +/// Push input adapter that generates incrementing counter values. +/// +/// This adapter spawns a background thread when started that periodically +/// pushes integer values to the CSP graph using the C API. +pub struct RustInputAdapter { + /// Interval between pushes in milliseconds + interval_ms: u64, + + /// Counter value (shared with thread) + counter: Arc, + + /// Flag to signal thread to stop + running: Arc, + + /// Push adapter handle (stored as AtomicPtr for thread-safe access) + adapter_handle: Arc>, + + /// Thread handle + thread_handle: Option>, +} + +impl RustInputAdapter { + /// Create a new input adapter. + pub fn new(interval_ms: u64) -> Self { + Self { + interval_ms: if interval_ms > 0 { interval_ms } else { 100 }, + counter: Arc::new(AtomicI64::new(0)), + running: Arc::new(AtomicBool::new(false)), + adapter_handle: Arc::new(AtomicPtr::new(ptr::null_mut())), + thread_handle: None, + } + } + + /// Start the adapter. + /// + /// Spawns a background thread that pushes integer values at the configured + /// interval using the CSP C API. + pub fn start(&mut self, adapter: CCspPushInputAdapterHandle) { + // Store the adapter handle (AtomicPtr is Send + Sync) + self.adapter_handle.store(adapter, Ordering::SeqCst); + self.running.store(true, Ordering::SeqCst); + + let running = Arc::clone(&self.running); + let counter = Arc::clone(&self.counter); + let adapter_handle = Arc::clone(&self.adapter_handle); + let interval_ms = self.interval_ms; + + eprintln!( + "[RustInputAdapter] Starting with interval {} ms (adapter handle: {:?})", + self.interval_ms, adapter + ); + + // Spawn background thread that pushes values + let handle = thread::spawn(move || { + let interval = Duration::from_millis(interval_ms); + + while running.load(Ordering::SeqCst) { + let value = counter.fetch_add(1, Ordering::SeqCst); + let adapter_ptr = adapter_handle.load(Ordering::SeqCst); + + // Push the counter value to CSP using the C API + if !adapter_ptr.is_null() { + unsafe { + let result = ccsp_push_input_adapter_push_int64( + adapter_ptr, + value, + ptr::null_mut(), + ); + // Log any errors (in production, handle more gracefully) + if result != crate::bindings::CCspErrorCode::Ok { + eprintln!( + "[RustInputAdapter] Push failed with error: {:?}", + result + ); + } + } + } + + thread::sleep(interval); + } + + eprintln!( + "[RustInputAdapter] Thread exiting after {} values", + counter.load(Ordering::SeqCst) + ); + }); + + self.thread_handle = Some(handle); + } + + /// Stop the adapter. + pub fn stop(&mut self) { + eprintln!( + "[RustInputAdapter] Stopping after {} values", + self.counter.load(Ordering::SeqCst) + ); + + self.running.store(false, Ordering::SeqCst); + + // Wait for the thread to finish + if let Some(handle) = self.thread_handle.take() { + if let Err(e) = handle.join() { + eprintln!("[RustInputAdapter] Thread join error: {:?}", e); + } + } + } +} + +impl Drop for RustInputAdapter { + fn drop(&mut self) { + self.stop(); + } +} + +// ============================================================================ +// C ABI callback functions +// ============================================================================ + +/// Start callback for the input adapter. +/// +/// # Safety +/// +/// Called from C code with valid pointers. +pub unsafe extern "C" fn rust_input_adapter_start( + user_data: *mut c_void, + _engine: CCspEngineHandle, + adapter: CCspPushInputAdapterHandle, + _start_time: CCspDateTime, + _end_time: CCspDateTime, +) { + if user_data.is_null() { + return; + } + let rust_adapter = &mut *(user_data as *mut RustInputAdapter); + rust_adapter.start(adapter); +} + +/// Stop callback for the input adapter. +/// +/// # Safety +/// +/// Called from C code with valid pointer. +pub unsafe extern "C" fn rust_input_adapter_stop(user_data: *mut c_void) { + if user_data.is_null() { + return; + } + let rust_adapter = &mut *(user_data as *mut RustInputAdapter); + rust_adapter.stop(); +} + +/// Destroy callback for the input adapter. +/// +/// # Safety +/// +/// Called from C code. Takes ownership and drops the adapter. +pub unsafe extern "C" fn rust_input_adapter_destroy(user_data: *mut c_void) { + if !user_data.is_null() { + let mut adapter = Box::from_raw(user_data as *mut RustInputAdapter); + adapter.stop(); + // Box is dropped here, freeing memory + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_adapter() { + let adapter = RustInputAdapter::new(100); + assert_eq!(adapter.interval_ms, 100); + assert!(!adapter.running.load(Ordering::SeqCst)); + } + + #[test] + fn test_default_interval() { + let adapter = RustInputAdapter::new(0); + assert_eq!(adapter.interval_ms, 100); // Should default to 100 + } +} + diff --git a/examples/05_cpp/5_c_api_adapter_rust/src/lib.rs b/examples/05_cpp/5_c_api_adapter_rust/src/lib.rs new file mode 100644 index 000000000..8722349a6 --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/src/lib.rs @@ -0,0 +1,148 @@ +//! CSP Rust Adapter Example +//! +//! This crate demonstrates how to create CSP adapters in Rust using the C API and PyO3. +//! It provides example adapters that mirror the C example in `../4_c_api_adapter/`: +//! - Push Input Adapter: Generates incrementing integers on a background thread +//! - Output Adapter: Prints values to stdout +//! - Adapter Manager: Coordinates adapter lifecycle +//! +//! # Building +//! +//! ```bash +//! hatch-build --hooks-only -t wheel +//! ``` +//! +//! # Architecture +//! +//! The Rust code implements adapters using the CSP C ABI, then exposes them to Python +//! via PyO3. The pattern is: +//! +//! 1. Implement adapter logic in Rust +//! 2. Create C-compatible VTable structures with callbacks +//! 3. Use PyO3 to create Python capsules wrapping the VTables +//! 4. CSP's Python layer extracts the VTables from capsules and registers with the engine + +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +pub mod bindings; +pub mod output_adapter; +pub mod input_adapter; +pub mod adapter_manager; + +use pyo3::prelude::*; +use pyo3::types::PyDict; + +use crate::bindings::*; +use crate::output_adapter::*; +use crate::input_adapter::*; +use crate::adapter_manager::*; + +/// Create an example adapter manager. +/// +/// # Arguments +/// * `engine` - Engine capsule (passed from Python) +/// * `properties` - Dict with configuration (e.g., {"prefix": "..."}) +/// +/// # Returns +/// A Python capsule wrapping the adapter manager VTable. +#[pyfunction] +#[pyo3(signature = (engine, properties))] +fn _example_adapter_manager( + py: Python<'_>, + engine: &Bound<'_, PyAny>, + properties: &Bound<'_, PyDict>, +) -> PyResult { + let _ = engine; // Engine handle not needed for creating vtable + + // Extract prefix from properties + let prefix = properties + .get_item("prefix")? + .and_then(|v| v.extract::().ok()) + .unwrap_or_default(); + + // Create the adapter manager + let manager = Box::new(RustAdapterManager::new(prefix)); + let user_data = Box::into_raw(manager) as *mut std::ffi::c_void; + + // Create vtable + let vtable = CCspAdapterManagerVTable { + user_data, + name: Some(rust_adapter_manager_name), + process_next_sim_time_slice: Some(rust_adapter_manager_process_sim_time), + destroy: Some(rust_adapter_manager_destroy), + start: Some(rust_adapter_manager_start), + stop: Some(rust_adapter_manager_stop), + }; + + // Create capsule + create_adapter_manager_capsule(py, vtable) +} + +/// Create an example input adapter that generates incrementing integers. +/// +/// # Arguments +/// * `interval_ms` - Interval between generated values in milliseconds +/// +/// # Returns +/// A Python capsule wrapping the input adapter VTable. +#[pyfunction] +#[pyo3(signature = (interval_ms=100))] +fn _example_input_adapter(py: Python<'_>, interval_ms: i32) -> PyResult { + // Create the input adapter + let adapter = Box::new(RustInputAdapter::new(interval_ms as u64)); + let user_data = Box::into_raw(adapter) as *mut std::ffi::c_void; + + // Create vtable + let vtable = CCspPushInputAdapterVTable { + user_data, + start: Some(rust_input_adapter_start), + stop: Some(rust_input_adapter_stop), + destroy: Some(rust_input_adapter_destroy), + }; + + // Create capsule + create_input_adapter_capsule(py, vtable) +} + +/// Create an example output adapter that prints values to stdout. +/// +/// # Arguments +/// * `prefix` - Optional prefix for output messages +/// +/// # Returns +/// A Python capsule wrapping the output adapter VTable. +#[pyfunction] +#[pyo3(signature = (prefix=None))] +fn _example_output_adapter(py: Python<'_>, prefix: Option) -> PyResult { + // Create the output adapter + let adapter = Box::new(RustOutputAdapter::new(prefix)); + let user_data = Box::into_raw(adapter) as *mut std::ffi::c_void; + + // Create vtable + let vtable = CCspOutputAdapterVTable { + user_data, + start: Some(rust_output_adapter_start), + stop: Some(rust_output_adapter_stop), + execute: Some(rust_output_adapter_execute), + destroy: Some(rust_output_adapter_destroy), + }; + + // Create capsule + create_output_adapter_capsule(py, vtable) +} + +/// Rust CSP Adapter Example module +/// +/// This module provides PyO3 functions that create VTable capsules for: +/// - AdapterManager: manages lifecycle of adapters +/// - InputAdapter: pushes values into the CSP graph +/// - OutputAdapter: receives values from the CSP graph +#[pymodule] +fn exampleadapter(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(_example_adapter_manager, m)?)?; + m.add_function(wrap_pyfunction!(_example_input_adapter, m)?)?; + m.add_function(wrap_pyfunction!(_example_output_adapter, m)?)?; + Ok(()) +} diff --git a/examples/05_cpp/5_c_api_adapter_rust/src/output_adapter.rs b/examples/05_cpp/5_c_api_adapter_rust/src/output_adapter.rs new file mode 100644 index 000000000..be96d2d49 --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/src/output_adapter.rs @@ -0,0 +1,209 @@ +//! Output Adapter implementation in Rust +//! +//! This module demonstrates how to implement a CSP output adapter in Rust +//! using the C API FFI bindings. +//! +//! The adapter receives values from the CSP graph and prints them to stderr, +//! demonstrating how to use the C API to retrieve typed values. + +use std::ffi::{c_char, c_void}; +use std::slice; +use std::str; + +use crate::bindings::{ + ccsp_engine_now, ccsp_input_get_last_bool, ccsp_input_get_last_datetime, + ccsp_input_get_last_double, ccsp_input_get_last_int64, ccsp_input_get_last_string, + ccsp_input_get_type, CCspDateTime, CCspEngineHandle, CCspErrorCode, CCspInputHandle, + CCspType, +}; + +/// Output adapter that prints values to stderr. +/// +/// This adapter is called by CSP whenever the input time series ticks. +/// It retrieves the latest value and prints it with an optional prefix. +pub struct RustOutputAdapter { + /// Optional prefix for output messages + prefix: Option, + + /// Counter for number of values received + count: u64, +} + +impl RustOutputAdapter { + /// Create a new output adapter. + pub fn new(prefix: Option) -> Self { + Self { prefix, count: 0 } + } + + /// Called when the adapter starts. + pub fn start(&mut self, start_time: CCspDateTime, end_time: CCspDateTime) { + let prefix = self.prefix.as_deref().unwrap_or(""); + eprintln!( + "{}[RustOutputAdapter] Started. Time range: {} - {} ns", + prefix, start_time, end_time + ); + } + + /// Called when the adapter stops. + pub fn stop(&mut self) { + let prefix = self.prefix.as_deref().unwrap_or(""); + eprintln!( + "{}[RustOutputAdapter] Stopped after {} values.", + prefix, self.count + ); + } + + /// Called each time the input has a new value. + /// + /// # Safety + /// + /// The engine and input handles must be valid. + pub unsafe fn execute(&mut self, engine: CCspEngineHandle, input: CCspInputHandle) { + let prefix = self.prefix.as_deref().unwrap_or(""); + + self.count += 1; + + // Get current engine time + let now = ccsp_engine_now(engine); + + // Get the type of the input + let input_type = ccsp_input_get_type(input); + + // Retrieve and print the value based on type + match input_type { + CCspType::Bool => { + let mut value: i8 = 0; + if ccsp_input_get_last_bool(input, &mut value) == CCspErrorCode::Ok { + eprintln!( + "{}[{}] bool: {}", + prefix, + now, + if value != 0 { "true" } else { "false" } + ); + } + } + CCspType::Int64 => { + let mut value: i64 = 0; + if ccsp_input_get_last_int64(input, &mut value) == CCspErrorCode::Ok { + eprintln!("{}[{}] int64: {}", prefix, now, value); + } + } + CCspType::Double => { + let mut value: f64 = 0.0; + if ccsp_input_get_last_double(input, &mut value) == CCspErrorCode::Ok { + eprintln!("{}[{}] double: {}", prefix, now, value); + } + } + CCspType::String => { + let mut data: *const c_char = std::ptr::null(); + let mut len: usize = 0; + if ccsp_input_get_last_string(input, &mut data, &mut len) == CCspErrorCode::Ok + && !data.is_null() && len > 0 { + let bytes = slice::from_raw_parts(data as *const u8, len); + if let Ok(s) = str::from_utf8(bytes) { + eprintln!("{}[{}] string: {}", prefix, now, s); + } else { + eprintln!("{}[{}] string: ", prefix, now); + } + } + } + CCspType::DateTime => { + let mut value: CCspDateTime = 0; + if ccsp_input_get_last_datetime(input, &mut value) == CCspErrorCode::Ok { + eprintln!("{}[{}] datetime: {} ns", prefix, now, value); + } + } + _ => { + eprintln!("{}[{}] ", prefix, now, input_type); + } + } + } +} + +impl Default for RustOutputAdapter { + fn default() -> Self { + Self::new(None) + } +} + +// ============================================================================ +// C ABI callback functions +// ============================================================================ + +/// Start callback for the output adapter. +/// +/// # Safety +/// +/// Called from C code with valid pointers. +pub unsafe extern "C" fn rust_output_adapter_start( + user_data: *mut c_void, + _engine: CCspEngineHandle, + start_time: CCspDateTime, + end_time: CCspDateTime, +) { + if user_data.is_null() { + return; + } + let adapter = &mut *(user_data as *mut RustOutputAdapter); + adapter.start(start_time, end_time); +} + +/// Stop callback for the output adapter. +/// +/// # Safety +/// +/// Called from C code with valid pointer. +pub unsafe extern "C" fn rust_output_adapter_stop(user_data: *mut c_void) { + if user_data.is_null() { + return; + } + let adapter = &mut *(user_data as *mut RustOutputAdapter); + adapter.stop(); +} + +/// Execute callback for the output adapter. +/// +/// # Safety +/// +/// Called from C code with valid pointers. +pub unsafe extern "C" fn rust_output_adapter_execute( + user_data: *mut c_void, + engine: CCspEngineHandle, + input: CCspInputHandle, +) { + if user_data.is_null() { + return; + } + let adapter = &mut *(user_data as *mut RustOutputAdapter); + adapter.execute(engine, input); +} + +/// Destroy callback for the output adapter. +/// +/// # Safety +/// +/// Called from C code. Takes ownership and drops the adapter. +pub unsafe extern "C" fn rust_output_adapter_destroy(user_data: *mut c_void) { + if !user_data.is_null() { + let _ = Box::from_raw(user_data as *mut RustOutputAdapter); + // Box is dropped here, freeing memory + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_adapter() { + let adapter = RustOutputAdapter::new(None); + assert_eq!(adapter.count, 0); + } + + #[test] + fn test_create_with_prefix() { + let adapter = RustOutputAdapter::new(Some("[TEST] ".to_string())); + assert_eq!(adapter.prefix, Some("[TEST] ".to_string())); + } +} + diff --git a/examples/05_cpp/README.md b/examples/05_cpp/README.md index 15e183799..f3ed30dad 100644 --- a/examples/05_cpp/README.md +++ b/examples/05_cpp/README.md @@ -1,4 +1,45 @@ # C++ Nodes and Adapters -- [C++ Node](./1_cpp_node/) -- [C++ Node w/ `csp.Struct`](./2_cpp_node_with_struct/) +This directory contains examples demonstrating how to extend CSP with custom +C++ components, including nodes and adapters. + +## Examples + +| Directory | Description | +| --------------------------------------------------- | -------------------------------------------------- | +| [1_cpp_node](./1_cpp_node/) | Basic C++ node implementation | +| [2_cpp_node_with_struct](./2_cpp_node_with_struct/) | C++ node that works with `csp.Struct` types | +| [3_cpp_adapter](./3_cpp_adapter/) | C++ adapter and adapter manager (internal API) | +| [4_c_api_adapter](./4_c_api_adapter/) | C adapter using the **stable C API** (recommended) | +| [5_c_api_adapter_rust](./5_c_api_adapter_rust/) | Rust adapter using the stable C API (scaffold) | + +## Choosing an Approach + +### For Custom Nodes + +Use **C++ Nodes** (`1_cpp_node`, `2_cpp_node_with_struct`) when you need: + +- High-performance computation in the graph +- Access to C/C++ libraries +- Complex state management in native code + +### For Custom Adapters + +**Recommended: C API** (`4_c_api_adapter`, `5_c_api_adapter_rust`) + +- Stable API with backward compatibility +- Works with C, C++, Rust, or any language with C FFI +- Well-defined VTable interface for callbacks +- See [C API Documentation](../../docs/wiki/c-api/README.md) + +**Advanced: C++ API** (`3_cpp_adapter`) + +- Full access to CSP internals +- More powerful but **API is not stable** +- Use only if you need features not exposed via C API + +## See Also + +- [CSP Documentation](../../docs/wiki/) +- [Writing Adapters Guide](../../docs/wiki/how-tos/Write-Adapters.md) +- [C API Reference](../../docs/wiki/c-api/README.md) diff --git a/examples/07_end_to_end/earthquake.ipynb b/examples/07_end_to_end/earthquake.ipynb index ac26a4221..8c538f362 100644 --- a/examples/07_end_to_end/earthquake.ipynb +++ b/examples/07_end_to_end/earthquake.ipynb @@ -279,6 +279,7 @@ "import csp\n", "from csp.impl.pushpulladapter import PushPullInputAdapter\n", "from csp.impl.wiring import py_pushpull_adapter_def\n", + "from csp.utils.datetime import utc_now\n", "\n", "\n", "# We use a csp.Struct to store the earthquake event data\n", @@ -328,7 +329,7 @@ " self.push_tick(False, event_data.time, event_data)\n", "\n", " print(\"-------------------------------------------------------------------\")\n", - " print(f\"{datetime.utcnow()}: Historical replay complete, pulled {len(catalog)} events\")\n", + " print(f\"{utc_now()}: Historical replay complete, pulled {len(catalog)} events\")\n", " print(\"-------------------------------------------------------------------\")\n", " self.flag_replay_complete()\n", "\n", @@ -349,7 +350,7 @@ " break\n", "\n", " print(\"-------------------------------------------------------------------\")\n", - " print(f\"{datetime.utcnow()}: Refreshing earthquake live feed with {len(new_events)} events\")\n", + " print(f\"{utc_now()}: Refreshing earthquake live feed with {len(new_events)} events\")\n", " print(\"-------------------------------------------------------------------\")\n", "\n", " for event in reversed(new_events):\n", @@ -400,8 +401,8 @@ " print(\"End of graph building\")\n", "\n", "\n", - "start = datetime.utcnow() - timedelta(hours=24)\n", - "end = datetime.utcnow() + timedelta(minutes=10)\n", + "start = utc_now() - timedelta(hours=24)\n", + "end = utc_now() + timedelta(minutes=10)\n", "csp.run(earthquake_graph, starttime=start, endtime=end, realtime=True)\n", "print(\"Done.\")" ] diff --git a/examples/07_end_to_end/mta.ipynb b/examples/07_end_to_end/mta.ipynb index 8eede791d..646d2f231 100644 --- a/examples/07_end_to_end/mta.ipynb +++ b/examples/07_end_to_end/mta.ipynb @@ -395,7 +395,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "f808ff5b-2735-4b58-bd29-054dabdc6620", "metadata": {}, "outputs": [ @@ -656,6 +656,7 @@ "import csp\n", "from csp.impl.pushadapter import PushInputAdapter\n", "from csp.impl.wiring import py_push_adapter_def\n", + "from csp.utils.datetime import utc_now\n", "\n", "\n", "class Event(csp.Struct):\n", @@ -691,7 +692,7 @@ "\n", " while self._running:\n", " print(\"----------------------------------------------\")\n", - " print(f\"{datetime.utcnow()}: refreshing MTA feed\")\n", + " print(f\"{utc_now()}: refreshing MTA feed\")\n", " print(\"----------------------------------------------\")\n", " print(\" Station | Line | Direction | Arrival time\")\n", " feed.refresh()\n", @@ -738,7 +739,7 @@ " print(\"End of graph building\")\n", "\n", "\n", - "start = datetime.utcnow()\n", + "start = utc_now()\n", "end = start + timedelta(minutes=3)\n", "csp.run(mta_graph, starttime=start, realtime=True, endtime=end)\n", "print(\"Done.\")" diff --git a/examples/07_end_to_end/seismic_waveform.ipynb b/examples/07_end_to_end/seismic_waveform.ipynb index 919cba18a..9356b042e 100644 --- a/examples/07_end_to_end/seismic_waveform.ipynb +++ b/examples/07_end_to_end/seismic_waveform.ipynb @@ -32,7 +32,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "9eeb07c5-1765-4433-a251-bf6da907ccd6", "metadata": {}, "outputs": [], @@ -50,6 +50,7 @@ "\n", "import csp\n", "from csp import ts\n", + "from csp.utils.datetime import utc_now\n", "\n", "warnings.filterwarnings(\"ignore\")" ] @@ -247,7 +248,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "37f4760b-3bbe-494f-a210-34d5e6332d56", "metadata": {}, "outputs": [ @@ -277,8 +278,8 @@ "csp.run(\n", " print_slices,\n", " st,\n", - " lag=datetime.utcnow() - starttime,\n", - " starttime=datetime.utcnow(),\n", + " lag=utc_now() - starttime,\n", + " starttime=utc_now(),\n", " endtime=timedelta(seconds=30),\n", " realtime=True,\n", ")" @@ -1056,7 +1057,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "id": "d298361e-2337-4633-bc4a-4bbae53ea756", "metadata": {}, "outputs": [ @@ -1157,7 +1158,7 @@ " rt_graph,\n", " address,\n", " symbols,\n", - " starttime=datetime.utcnow() - timedelta(seconds=60),\n", + " starttime=utc_now() - timedelta(seconds=60),\n", " endtime=timedelta(seconds=120),\n", " realtime=True,\n", ")" @@ -1216,7 +1217,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "id": "4ee33917-b4ce-4d99-8689-d358e8716aca", "metadata": {}, "outputs": [ @@ -1311,8 +1312,8 @@ " symbols,\n", " nsta,\n", " nlta,\n", - " starttime=datetime.utcnow() - timedelta(seconds=10), # Starting in the past helps \"warm up\" the averages\n", - " endtime=datetime.utcnow() + timedelta(seconds=60),\n", + " starttime=utc_now() - timedelta(seconds=10), # Starting in the past helps \"warm up\" the averages\n", + " endtime=utc_now() + timedelta(seconds=60),\n", " realtime=True,\n", ")" ] diff --git a/examples/07_end_to_end/wikimedia.ipynb b/examples/07_end_to_end/wikimedia.ipynb index f46ca97e1..5e22fc475 100644 --- a/examples/07_end_to_end/wikimedia.ipynb +++ b/examples/07_end_to_end/wikimedia.ipynb @@ -173,6 +173,7 @@ "from sseclient import SSEClient as EventSource\n", "\n", "import csp\n", + "from csp.utils.datetime import utc_now\n", "\n", "\n", "class WikiData(csp.Struct):\n", @@ -336,7 +337,7 @@ " print(\"End of graph building\")\n", "\n", "\n", - "start = datetime.utcnow()\n", + "start = utc_now()\n", "csp.run(wiki_graph, starttime=start, endtime=start + timedelta(seconds=30), realtime=True)\n", "print(\"Done.\")" ] @@ -523,7 +524,7 @@ " print(\"End of graph building\")\n", "\n", "\n", - "start_time = datetime.utcnow()\n", + "start_time = utc_now()\n", "end_time = start_time + timedelta(seconds=30)\n", "csp.run(wiki_graph, starttime=start_time, endtime=end_time, realtime=True)\n", "print(\"Done.\")" diff --git a/examples/07_end_to_end/wikimedia_inference.ipynb b/examples/07_end_to_end/wikimedia_inference.ipynb index 45e8675e1..4a6186058 100644 --- a/examples/07_end_to_end/wikimedia_inference.ipynb +++ b/examples/07_end_to_end/wikimedia_inference.ipynb @@ -30,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "552f580d-64a5-44a6-9226-cae7f42c3776", "metadata": {}, "outputs": [], @@ -47,6 +47,7 @@ "import csp\n", "from csp import Outputs, ts\n", "from csp.typing import NumpyNDArray\n", + "from csp.utils.datetime import utc_now\n", "\n", "np.set_printoptions(edgeitems=5, linewidth=120)" ] @@ -656,7 +657,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "b710cb50-3d2c-4160-ba43-54cc47561d68", "metadata": {}, "outputs": [ @@ -671,7 +672,7 @@ } ], "source": [ - "start = datetime.utcnow()\n", + "start = utc_now()\n", "csp.run(inference_graph, clf, widget=widget, starttime=start, endtime=start + timedelta(seconds=30), realtime=True)\n", "print(\"Done.\")" ] diff --git a/pyproject.toml b/pyproject.toml index d3d1e27b2..b6b5a7e71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,10 @@ develop = [ # build/dist "bump-my-version", "build", + "hatchling", + "hatch-build", + "hatch-cpp", + "hatch-rs", "ruamel.yaml", "scikit-build", "twine",