From 9ef31e4dee9f84cd9f1426188a6658f06458673d Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:22:05 -0500 Subject: [PATCH 01/12] messing around sketching c api for adapters Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- cpp/csp/adapters/CMakeLists.txt | 2 + cpp/csp/adapters/c/CMakeLists.txt | 1 + cpp/csp/adapters/c/example/CMakeLists.txt | 20 +++++ .../adapters/c/example/ExampleOutputAdapter.c | 0 .../adapters/c/example/ExampleOutputAdapter.h | 13 ++++ .../c/example/ExamplePushInputAdapter.c | 0 .../c/example/ExamplePushInputAdapter.h | 0 cpp/csp/engine/OutputAdapterExtern.h | 26 +++++++ cpp/csp/engine/c/CspType.h | 75 +++++++++++++++++++ cpp/csp/engine/c/OutputAdapter.h | 34 +++++++++ cpp/csp/python/PyInputAdapterWrapper.h | 1 - cpp/csp/python/adapters/CMakeLists.txt | 2 + cpp/csp/python/adapters/c/CMakeLists.txt | 4 + .../python/adapters/c/exampleadapterimpl.c | 36 +++++++++ cpp/csp/python/c/CMakeLists.txt | 0 cpp/csp/python/c/PyOutputAdapter.h | 15 ++++ csp/adapters/c_example.py | 21 ++++++ 17 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 cpp/csp/adapters/c/CMakeLists.txt create mode 100644 cpp/csp/adapters/c/example/CMakeLists.txt create mode 100644 cpp/csp/adapters/c/example/ExampleOutputAdapter.c create mode 100644 cpp/csp/adapters/c/example/ExampleOutputAdapter.h create mode 100644 cpp/csp/adapters/c/example/ExamplePushInputAdapter.c create mode 100644 cpp/csp/adapters/c/example/ExamplePushInputAdapter.h create mode 100644 cpp/csp/engine/OutputAdapterExtern.h create mode 100644 cpp/csp/engine/c/CspType.h create mode 100644 cpp/csp/engine/c/OutputAdapter.h create mode 100644 cpp/csp/python/adapters/c/CMakeLists.txt create mode 100644 cpp/csp/python/adapters/c/exampleadapterimpl.c create mode 100644 cpp/csp/python/c/CMakeLists.txt create mode 100644 cpp/csp/python/c/PyOutputAdapter.h create mode 100644 csp/adapters/c_example.py diff --git a/cpp/csp/adapters/CMakeLists.txt b/cpp/csp/adapters/CMakeLists.txt index 41a929dcf..c88069390 100644 --- a/cpp/csp/adapters/CMakeLists.txt +++ b/cpp/csp/adapters/CMakeLists.txt @@ -11,4 +11,6 @@ if(CSP_BUILD_WS_CLIENT_ADAPTER) add_subdirectory(websocket) endif() + +add_subdirectory(c) add_subdirectory(utils) diff --git a/cpp/csp/adapters/c/CMakeLists.txt b/cpp/csp/adapters/c/CMakeLists.txt new file mode 100644 index 000000000..cbce7e5ca --- /dev/null +++ b/cpp/csp/adapters/c/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(example) \ No newline at end of file diff --git a/cpp/csp/adapters/c/example/CMakeLists.txt b/cpp/csp/adapters/c/example/CMakeLists.txt new file mode 100644 index 000000000..c85c7e29b --- /dev/null +++ b/cpp/csp/adapters/c/example/CMakeLists.txt @@ -0,0 +1,20 @@ + +set(C_EXAMPLE_HEADER_FILES + ExamplePushInputAdapter.h + ExampleOutputAdapter.h +) + +set(C_EXAMPLE_SOURCE_FILES + ExamplePushInputAdapter.c + ExampleOutputAdapter.c + ${KAFKA_HEADER_FILES} +) + +add_library(csp_c_example_adapter STATIC ${C_EXAMPLE_SOURCE_FILES}) +set_target_properties(csp_c_example_adapter PROPERTIES PUBLIC_HEADER "${C_EXAMPLE_HEADER_FILES}") + +install(TARGETS csp_c_example_adapter + PUBLIC_HEADER DESTINATION include/csp/adapters/example + RUNTIME DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR} + LIBRARY DESTINATION lib/ + ) diff --git a/cpp/csp/adapters/c/example/ExampleOutputAdapter.c b/cpp/csp/adapters/c/example/ExampleOutputAdapter.c new file mode 100644 index 000000000..e69de29bb diff --git a/cpp/csp/adapters/c/example/ExampleOutputAdapter.h b/cpp/csp/adapters/c/example/ExampleOutputAdapter.h new file mode 100644 index 000000000..82c6fda5b --- /dev/null +++ b/cpp/csp/adapters/c/example/ExampleOutputAdapter.h @@ -0,0 +1,13 @@ +#ifndef _IN_CSP_ADAPTERS_C_EXAMPLE_OUTPUT_ADAPTER_H +#define _IN_CSP_ADAPTERS_C_EXAMPLE_OUTPUT_ADAPTER_H + +#include + + + +OutputAdapter * registerExampleOutputAdapter(void * properties); +void * unregisterExampleOutputAdapter(OutputAdapter * adapter); +void executeImpl(); + + +#endif diff --git a/cpp/csp/adapters/c/example/ExamplePushInputAdapter.c b/cpp/csp/adapters/c/example/ExamplePushInputAdapter.c new file mode 100644 index 000000000..e69de29bb diff --git a/cpp/csp/adapters/c/example/ExamplePushInputAdapter.h b/cpp/csp/adapters/c/example/ExamplePushInputAdapter.h new file mode 100644 index 000000000..e69de29bb diff --git a/cpp/csp/engine/OutputAdapterExtern.h b/cpp/csp/engine/OutputAdapterExtern.h new file mode 100644 index 000000000..1603bbe0e --- /dev/null +++ b/cpp/csp/engine/OutputAdapterExtern.h @@ -0,0 +1,26 @@ +#ifndef _IN_CSP_ENGINE_OUTPUT_ADAPTER_EXTERN_H +#define _IN_CSP_ENGINE_OUTPUT_ADAPTER_EXTERN_H + +/* + * This file wraps an external OutputAdapter implementation into a C++ OutputAdapter + * communicating across the ABI-stable C interface. +*/ + +namespace csp +{ + + class OutputAdapterExtern : public OutputAdapter + { + public: + OutputAdapterExtern(/* parameters to construct the external adapter */); + ~OutputAdapterExtern(); + + void executeImpl(); + + private: + struct COutputAdapter* c_adapter_; // Opaque pointer to the C Output Adapter + }; + +}; + +#endif 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/OutputAdapter.h b/cpp/csp/engine/c/OutputAdapter.h new file mode 100644 index 000000000..b1b591b43 --- /dev/null +++ b/cpp/csp/engine/c/OutputAdapter.h @@ -0,0 +1,34 @@ +/* + * ABI-stable C Output Adapter interface for CSP Engine +*/ +#ifndef _IN_CSP_ENGINE_COUTPUTADAPTER_H +#define _IN_CSP_ENGINE_COUTPUTADAPTER_H + +// C Output Adapter interface +#ifdef __cplusplus +extern "C" { +#endif + +// Construction: +// - ignore engine pointer as it is (hopefully) not needed +// - dictionary of properties + +// Execution: +// - void executeImpl() +// - inside executeImpl, input() -> lastValueTyped() will be invoked +// lastValueType() will need to be exposed via C interface as well + +// Destruction: +// - standard destructor + + +typedef struct { + /* Opaque Type internal to adapter implementation */ +} OutputAdapter; + + +#ifdef __cplusplus +} +#endif + +#endif 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/adapters/CMakeLists.txt b/cpp/csp/python/adapters/CMakeLists.txt index 62742a8b0..8022cfc47 100644 --- a/cpp/csp/python/adapters/CMakeLists.txt +++ b/cpp/csp/python/adapters/CMakeLists.txt @@ -46,3 +46,5 @@ if(CSP_BUILD_WS_CLIENT_ADAPTER) target_link_libraries(websocketadapterimpl csp_core csp_engine cspimpl csp_websocket_client_adapter) install(TARGETS websocketadapterimpl RUNTIME DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR}) endif() + +add_subdirectory(c) diff --git a/cpp/csp/python/adapters/c/CMakeLists.txt b/cpp/csp/python/adapters/c/CMakeLists.txt new file mode 100644 index 000000000..3971e1efa --- /dev/null +++ b/cpp/csp/python/adapters/c/CMakeLists.txt @@ -0,0 +1,4 @@ + +add_library(exampleadapterimpl SHARED exampleadapterimpl.c) +target_link_libraries(exampleadapterimpl csp_c_example_adapter) +install(TARGETS exampleadapterimpl RUNTIME DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR}) diff --git a/cpp/csp/python/adapters/c/exampleadapterimpl.c b/cpp/csp/python/adapters/c/exampleadapterimpl.c new file mode 100644 index 000000000..756fc4bdc --- /dev/null +++ b/cpp/csp/python/adapters/c/exampleadapterimpl.c @@ -0,0 +1,36 @@ +#include +#include "Python.h" + +#include +#include + +PyObject* create_input_adapter_py(PyObject*, PyObject*) { + // TODO + return Py_None; +} +PyObject* create_output_adapter_py(PyObject*, PyObject*) { + // TODO + uint64_t x = 42; + void* ptr = (void*) x; + return createCOutputAdapterCapsule( (OutputAdapter*) x ); +} + +static PyMethodDef exampleadapter_methods[] = { + {"_example_input_adapter", (PyCFunction)create_input_adapter_py, METH_VARARGS}, + {"_example_output_adapter", (PyCFunction)create_output_adapter_py, METH_VARARGS}, + {NULL, NULL, 0, NULL} +}; + +static PyModuleDef exampleadapter_module = { + PyModuleDef_HEAD_INIT, + "exampleadapter", + "exampleadapter module", + -1, + exampleadapter_methods +}; + + +PyMODINIT_FUNC PyInit__exampleadapterimpl(void) { + Py_Initialize(); + return PyModule_Create(&exampleadapter_module); +} \ No newline at end of file diff --git a/cpp/csp/python/c/CMakeLists.txt b/cpp/csp/python/c/CMakeLists.txt new file mode 100644 index 000000000..e69de29bb diff --git a/cpp/csp/python/c/PyOutputAdapter.h b/cpp/csp/python/c/PyOutputAdapter.h new file mode 100644 index 000000000..c45173302 --- /dev/null +++ b/cpp/csp/python/c/PyOutputAdapter.h @@ -0,0 +1,15 @@ +#ifndef _IN_CSP_PYTHON_C_PYOUTPUTADAPTER_H +#define _IN_CSP_PYTHON_C_PYOUTPUTADAPTER_H + +#include "Python.h" + +// Create a special python capsule type to indicate to the C++ code that this is an output adapter +// defined in an external language and communicating via the C ABI interface. + +const char * const CSP_PYTHON_C_OUTPUT_ADAPTER_CAPSULE_NAME = "csp.python.c.OutputAdapterCapsule"; + +PyObject * createCOutputAdapterCapsule( OutputAdapter * c_adapter_ptr ) { + return PyCapsule_New( c_adapter_ptr, CSP_PYTHON_C_OUTPUT_ADAPTER_CAPSULE_NAME, NULL ); +} + +#endif diff --git a/csp/adapters/c_example.py b/csp/adapters/c_example.py new file mode 100644 index 000000000..3637dd4e9 --- /dev/null +++ b/csp/adapters/c_example.py @@ -0,0 +1,21 @@ +from typing import TypeVar + +from csp import ts +from csp.impl.wiring import input_adapter_def, output_adapter_def +from csp.lib import _exampleadapterimpl + +T = TypeVar("T") + +_example_input_adapter_def = input_adapter_def( + "example_input_adapter", + _exampleadapterimpl._example_input_adapter, + ts["T"], + typ="T", + properties=dict, +) + +_example_output_adapter_def = output_adapter_def( + "example_output_adapter", + _exampleadapterimpl._example_output_adapter, + input=ts["T"], +) From 3e879fe6c64245073f175f447348fa74f7e6e943 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sun, 15 Feb 2026 12:32:10 -0500 Subject: [PATCH 02/12] More wip Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- cpp/csp/adapters/c/example/CMakeLists.txt | 28 +- .../c/example/ExampleManagedAdapter.c | 207 +++ .../c/example/ExampleManagedAdapter.h | 82 ++ .../adapters/c/example/ExampleOutputAdapter.c | 152 +++ .../adapters/c/example/ExampleOutputAdapter.h | 35 +- .../c/example/ExamplePushInputAdapter.c | 275 ++++ .../c/example/ExamplePushInputAdapter.h | 47 + cpp/csp/engine/AdapterManagerExtern.cpp | 286 ++++ cpp/csp/engine/AdapterManagerExtern.h | 39 + cpp/csp/engine/CMakeLists.txt | 23 + cpp/csp/engine/DictionaryExtern.cpp | 919 +++++++++++++ cpp/csp/engine/OutputAdapterExtern.cpp | 291 +++++ cpp/csp/engine/OutputAdapterExtern.h | 41 +- cpp/csp/engine/StructExtern.cpp | 1141 ++++++++++++++++ cpp/csp/engine/c/AdapterManager.h | 341 +++++ cpp/csp/engine/c/CspDictionary.h | 265 ++++ cpp/csp/engine/c/CspError.h | 65 + cpp/csp/engine/c/CspString.h | 99 ++ cpp/csp/engine/c/CspStruct.h | 369 ++++++ cpp/csp/engine/c/CspTime.h | 109 ++ cpp/csp/engine/c/CspValue.h | 220 ++++ cpp/csp/engine/c/InputAdapter.h | 265 ++++ cpp/csp/engine/c/OutputAdapter.h | 216 ++- cpp/csp/python/adapters/c/CMakeLists.txt | 30 +- .../python/adapters/c/exampleadapterimpl.c | 267 +++- cpp/csp/python/c/PyAdapterManager.h | 113 ++ cpp/csp/python/c/PyInputAdapter.h | 113 ++ cpp/csp/python/c/PyOutputAdapter.h | 110 +- csp/adapters/c_example.py | 240 ++++ docs/C_API_ROADMAP.md | 670 ++++++++++ docs/wiki/api-references/C-APIs.md | 1158 +++++++++++++++++ docs/wiki/how-tos/Write-C-API-Adapters.md | 577 ++++++++ .../04_writing_adapters/e8_c_api_adapter.py | 152 +++ 33 files changed, 8874 insertions(+), 71 deletions(-) create mode 100644 cpp/csp/adapters/c/example/ExampleManagedAdapter.c create mode 100644 cpp/csp/adapters/c/example/ExampleManagedAdapter.h create mode 100644 cpp/csp/engine/AdapterManagerExtern.cpp create mode 100644 cpp/csp/engine/AdapterManagerExtern.h create mode 100644 cpp/csp/engine/DictionaryExtern.cpp create mode 100644 cpp/csp/engine/OutputAdapterExtern.cpp create mode 100644 cpp/csp/engine/StructExtern.cpp create mode 100644 cpp/csp/engine/c/AdapterManager.h create mode 100644 cpp/csp/engine/c/CspDictionary.h create mode 100644 cpp/csp/engine/c/CspError.h create mode 100644 cpp/csp/engine/c/CspString.h create mode 100644 cpp/csp/engine/c/CspStruct.h create mode 100644 cpp/csp/engine/c/CspTime.h create mode 100644 cpp/csp/engine/c/CspValue.h create mode 100644 cpp/csp/engine/c/InputAdapter.h create mode 100644 cpp/csp/python/c/PyAdapterManager.h create mode 100644 cpp/csp/python/c/PyInputAdapter.h create mode 100644 docs/C_API_ROADMAP.md create mode 100644 docs/wiki/api-references/C-APIs.md create mode 100644 docs/wiki/how-tos/Write-C-API-Adapters.md create mode 100644 examples/04_writing_adapters/e8_c_api_adapter.py diff --git a/cpp/csp/adapters/c/example/CMakeLists.txt b/cpp/csp/adapters/c/example/CMakeLists.txt index c85c7e29b..06007539b 100644 --- a/cpp/csp/adapters/c/example/CMakeLists.txt +++ b/cpp/csp/adapters/c/example/CMakeLists.txt @@ -1,20 +1,38 @@ +# Example C adapters demonstrating the C ABI interface set(C_EXAMPLE_HEADER_FILES ExamplePushInputAdapter.h ExampleOutputAdapter.h + ExampleManagedAdapter.h ) set(C_EXAMPLE_SOURCE_FILES ExamplePushInputAdapter.c ExampleOutputAdapter.c - ${KAFKA_HEADER_FILES} + ExampleManagedAdapter.c ) -add_library(csp_c_example_adapter STATIC ${C_EXAMPLE_SOURCE_FILES}) -set_target_properties(csp_c_example_adapter PROPERTIES PUBLIC_HEADER "${C_EXAMPLE_HEADER_FILES}") +add_library(csp_c_example_adapter STATIC ${C_EXAMPLE_SOURCE_FILES} ${C_EXAMPLE_HEADER_FILES}) + +# Include the engine c headers +target_include_directories(csp_c_example_adapter PUBLIC + ${CMAKE_SOURCE_DIR}/cpp +) + +# Set C standard +set_target_properties(csp_c_example_adapter PROPERTIES + PUBLIC_HEADER "${C_EXAMPLE_HEADER_FILES}" + C_STANDARD 11 + C_STANDARD_REQUIRED ON +) + +# On non-Windows, we need pthread for the example threaded adapters +if(NOT WIN32) + target_link_libraries(csp_c_example_adapter pthread) +endif() install(TARGETS csp_c_example_adapter - PUBLIC_HEADER DESTINATION include/csp/adapters/example + PUBLIC_HEADER DESTINATION include/csp/adapters/c/example RUNTIME DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR} LIBRARY DESTINATION lib/ - ) +) diff --git a/cpp/csp/adapters/c/example/ExampleManagedAdapter.c b/cpp/csp/adapters/c/example/ExampleManagedAdapter.c new file mode 100644 index 000000000..8610956b2 --- /dev/null +++ b/cpp/csp/adapters/c/example/ExampleManagedAdapter.c @@ -0,0 +1,207 @@ +/* + * Example Managed Adapter Implementation + * + * Demonstrates how to build an adapter manager that coordinates + * multiple input/output adapters, similar to KafkaAdapterManager. + */ + +#include "ExampleManagedAdapter.h" +#include +#include +#include + +/* ============================================================================ + * 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/cpp/csp/adapters/c/example/ExampleManagedAdapter.h b/cpp/csp/adapters/c/example/ExampleManagedAdapter.h new file mode 100644 index 000000000..ca7a57291 --- /dev/null +++ b/cpp/csp/adapters/c/example/ExampleManagedAdapter.h @@ -0,0 +1,82 @@ +/* + * 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/cpp/csp/adapters/c/example/ExampleOutputAdapter.c b/cpp/csp/adapters/c/example/ExampleOutputAdapter.c index e69de29bb..12bd0cb1b 100644 --- a/cpp/csp/adapters/c/example/ExampleOutputAdapter.c +++ b/cpp/csp/adapters/c/example/ExampleOutputAdapter.c @@ -0,0 +1,152 @@ +/* + * Example Output Adapter implementation in C + * + * This demonstrates how to implement an output adapter using the C ABI interface. + */ +#include "ExampleOutputAdapter.h" +#include +#include +#include +#include +#include +#include +#include + +/* 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/cpp/csp/adapters/c/example/ExampleOutputAdapter.h b/cpp/csp/adapters/c/example/ExampleOutputAdapter.h index 82c6fda5b..15ff26a86 100644 --- a/cpp/csp/adapters/c/example/ExampleOutputAdapter.h +++ b/cpp/csp/adapters/c/example/ExampleOutputAdapter.h @@ -1,13 +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 - +#include +#ifdef __cplusplus +extern "C" { +#endif -OutputAdapter * registerExampleOutputAdapter(void * properties); -void * unregisterExampleOutputAdapter(OutputAdapter * adapter); -void executeImpl(); +/* + * 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/cpp/csp/adapters/c/example/ExamplePushInputAdapter.c b/cpp/csp/adapters/c/example/ExamplePushInputAdapter.c index e69de29bb..02f2b742c 100644 --- a/cpp/csp/adapters/c/example/ExamplePushInputAdapter.c +++ b/cpp/csp/adapters/c/example/ExamplePushInputAdapter.c @@ -0,0 +1,275 @@ +/* + * 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 "ExamplePushInputAdapter.h" +#include +#include +#include +#include +#include + +#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/cpp/csp/adapters/c/example/ExamplePushInputAdapter.h b/cpp/csp/adapters/c/example/ExamplePushInputAdapter.h index e69de29bb..fbe41fbbf 100644 --- a/cpp/csp/adapters/c/example/ExamplePushInputAdapter.h +++ b/cpp/csp/adapters/c/example/ExamplePushInputAdapter.h @@ -0,0 +1,47 @@ +/* + * 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/cpp/csp/engine/AdapterManagerExtern.cpp b/cpp/csp/engine/AdapterManagerExtern.cpp new file mode 100644 index 000000000..7f134664c --- /dev/null +++ b/cpp/csp/engine/AdapterManagerExtern.cpp @@ -0,0 +1,286 @@ +/* + * 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..dcb3eebe4 --- /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..d4b3b025e 100644 --- a/cpp/csp/engine/CMakeLists.txt +++ b/cpp/csp/engine/CMakeLists.txt @@ -18,6 +18,20 @@ set(CSP_TYPES_SOURCE_FILES TypeCast.h ) +# C API headers (ABI-stable interface) +set(CSP_C_API_HEADERS + 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,6 +53,8 @@ set(ENGINE_PUBLIC_HEADERS InputId.h Node.h OutputAdapter.h + OutputAdapterExtern.h + AdapterManagerExtern.h PendingPushEvents.h Profiler.h PushPullEvent.h @@ -73,6 +89,10 @@ set(ENGINE_SOURCE_FILES InputAdapter.cpp Node.cpp OutputAdapter.cpp + OutputAdapterExtern.cpp + AdapterManagerExtern.cpp + DictionaryExtern.cpp + StructExtern.cpp PendingPushEvents.cpp PushPullInputAdapter.cpp RootEngine.cpp @@ -93,6 +113,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..bfdca246a --- /dev/null +++ b/cpp/csp/engine/DictionaryExtern.cpp @@ -0,0 +1,919 @@ +/* + * 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..52a71fd05 --- /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 index 1603bbe0e..2670f8871 100644 --- a/cpp/csp/engine/OutputAdapterExtern.h +++ b/cpp/csp/engine/OutputAdapterExtern.h @@ -2,25 +2,38 @@ #define _IN_CSP_ENGINE_OUTPUT_ADAPTER_EXTERN_H /* - * This file wraps an external OutputAdapter implementation into a C++ OutputAdapter - * communicating across the ABI-stable C interface. -*/ + * 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 : public OutputAdapter - { - public: - OutputAdapterExtern(/* parameters to construct the external adapter */); - ~OutputAdapterExtern(); - void executeImpl(); +class OutputAdapterExtern final : public OutputAdapter +{ +public: + OutputAdapterExtern( Engine * engine, const CspTypePtr & type, + const CCspOutputAdapterVTable & vtable ); + ~OutputAdapterExtern() override; + + const char* name() const override { return "OutputAdapterExtern"; } - private: - struct COutputAdapter* c_adapter_; // Opaque pointer to the C Output Adapter - }; + void start() override; + void stop() override; + void executeImpl() override; +private: + CCspOutputAdapterVTable m_vtable; + DateTime m_startTime; + DateTime m_endTime; }; -#endif +} + +#endif /* _IN_CSP_ENGINE_OUTPUT_ADAPTER_EXTERN_H */ + diff --git a/cpp/csp/engine/StructExtern.cpp b/cpp/csp/engine/StructExtern.cpp new file mode 100644 index 000000000..2a3a76526 --- /dev/null +++ b/cpp/csp/engine/StructExtern.cpp @@ -0,0 +1,1141 @@ +/* + * 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..9bd7bd09c --- /dev/null +++ b/cpp/csp/engine/c/AdapterManager.h @@ -0,0 +1,341 @@ +/* + * 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 + +#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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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); + +#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..2ed471b7a --- /dev/null +++ b/cpp/csp/engine/c/CspDictionary.h @@ -0,0 +1,265 @@ +/* + * 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 + +#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) + */ +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 + */ +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 + */ +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 + */ +CCspDictValueType ccsp_dictionary_get_type( CCspDictionaryHandle dict, const char * key ); + +/* ============================================================================ + * Type-Safe Getters (return error if type mismatch or key not found) + * ============================================================================ */ + +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 ); + +/* + * 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 + */ +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 + */ +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. + */ + +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 ); + +/* + * 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) + */ +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 + */ +CCspDictIteratorHandle ccsp_dictionary_iter_create( CCspDictionaryHandle dict ); + +/* + * ccsp_dictionary_iter_destroy - Destroy an iterator + * + * Parameters: + * iter - Iterator handle + */ +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 + */ +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 + */ +CCspDictValueType ccsp_dictionary_iter_value_type( CCspDictIteratorHandle iter ); + +/* Type-safe value getters for current iterator position */ +CCspErrorCode ccsp_dictionary_iter_get_bool( CCspDictIteratorHandle iter, int8_t * out_value ); +CCspErrorCode ccsp_dictionary_iter_get_int32( CCspDictIteratorHandle iter, int32_t * out_value ); +CCspErrorCode ccsp_dictionary_iter_get_uint32( CCspDictIteratorHandle iter, uint32_t * out_value ); +CCspErrorCode ccsp_dictionary_iter_get_int64( CCspDictIteratorHandle iter, int64_t * out_value ); +CCspErrorCode ccsp_dictionary_iter_get_uint64( CCspDictIteratorHandle iter, uint64_t * out_value ); +CCspErrorCode ccsp_dictionary_iter_get_double( CCspDictIteratorHandle iter, double * out_value ); +CCspErrorCode ccsp_dictionary_iter_get_datetime( CCspDictIteratorHandle iter, CCspDateTime * out_value ); +CCspErrorCode ccsp_dictionary_iter_get_timedelta( CCspDictIteratorHandle iter, CCspTimeDelta * out_value ); +CCspErrorCode ccsp_dictionary_iter_get_string( CCspDictIteratorHandle iter, const char ** out_data, size_t * out_length ); +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..962866e50 --- /dev/null +++ b/cpp/csp/engine/c/CspError.h @@ -0,0 +1,65 @@ +/* + * 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 + +#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 */ +CCspErrorCode ccsp_get_last_error(void); + +/* Get the last error message for the current thread (may be NULL) */ +const char* ccsp_get_last_error_message(void); + +/* Clear the last error for the current thread */ +void ccsp_clear_error(void); + +/* + * Set an error (for adapter implementations). + * The message is copied internally. + */ +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/CspString.h b/cpp/csp/engine/c/CspString.h new file mode 100644 index 000000000..70d5dbe38 --- /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..0109ae26e --- /dev/null +++ b/cpp/csp/engine/c/CspStruct.h @@ -0,0 +1,369 @@ +/* + * 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 /* 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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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. + * ============================================================================ */ + +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 ); +CCspErrorCode ccsp_struct_get_datetime( CCspStructHandle s, CCspStructFieldHandle field, CCspDateTime * out_value ); +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 + */ +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 + */ +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 + */ +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. + * ============================================================================ */ + +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. + * CCSP_ERROR_TYPE_MISMATCH is returned if the field type doesn't match. + * ============================================================================ */ + +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 ); +CCspErrorCode ccsp_struct_set_datetime( CCspStructHandle s, CCspStructFieldHandle field, CCspDateTime value ); +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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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..b95d8a9e0 --- /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/CspValue.h b/cpp/csp/engine/c/CspValue.h new file mode 100644 index 000000000..7d875745c --- /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..5ac31ba3c --- /dev/null +++ b/cpp/csp/engine/c/InputAdapter.h @@ -0,0 +1,265 @@ +/* + * 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 + +#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 + */ +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. + */ +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) + */ +CCspErrorCode ccsp_push_input_adapter_push_value( + CCspPushInputAdapterHandle adapter, + const CCspValue* value, + CCspPushBatchHandle batch +); + +/* Type-specific push functions (more efficient, avoid CCspValue overhead) */ + +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); + +/* + * Push a string value. + * The string data is copied internally. + */ +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. + */ +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 + */ +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. + */ +void ccsp_push_batch_flush(CCspPushBatchHandle batch); + +/* + * Destroy a push batch. + * Any unflushed events are flushed before destruction. + */ +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 + */ +CCspPushGroupHandle ccsp_push_group_create(void); + +/* + * Destroy a push group. + */ +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 index b1b591b43..611fefa8f 100644 --- a/cpp/csp/engine/c/OutputAdapter.h +++ b/cpp/csp/engine/c/OutputAdapter.h @@ -1,34 +1,214 @@ -/* +/* * ABI-stable C Output Adapter interface for CSP Engine -*/ -#ifndef _IN_CSP_ENGINE_COUTPUTADAPTER_H -#define _IN_CSP_ENGINE_COUTPUTADAPTER_H + * + * 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 -// C Output Adapter interface #ifdef __cplusplus extern "C" { #endif -// Construction: -// - ignore engine pointer as it is (hopefully) not needed -// - dictionary of properties +/* ============================================================================ + * 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) + */ +int ccsp_input_is_valid(CCspInputHandle input); + +/* + * Get the number of ticks available in the input buffer + */ +int32_t ccsp_input_num_ticks(CCspInputHandle input); + +/* + * Get the type of the input + */ +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. + */ +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. + */ +CCspErrorCode ccsp_input_get_value_at(CCspInputHandle input, int32_t index, CCspValue* out_value); + +/* + * Get the timestamp of the value at a specific index. + */ +CCspErrorCode ccsp_input_get_time_at(CCspInputHandle input, int32_t index, CCspDateTime* out_time); + +/* + * Get the timestamp of the last value. + */ +CCspDateTime ccsp_input_get_last_time(CCspInputHandle input); + +/* ============================================================================ + * Engine access functions + * ============================================================================ */ + +/* + * Get current engine time + */ +CCspDateTime ccsp_engine_now(CCspEngineHandle engine); -// Execution: -// - void executeImpl() -// - inside executeImpl, input() -> lastValueTyped() will be invoked -// lastValueType() will need to be exposed via C interface as well +/* + * Get current cycle count + */ +uint64_t ccsp_engine_cycle_count(CCspEngineHandle engine); -// Destruction: -// - standard destructor +/* ============================================================================ + * 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; -typedef struct { - /* Opaque Type internal to adapter implementation */ -} OutputAdapter; + /* + * 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. + */ +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. + */ +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. + */ +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) + */ +CCspErrorCode ccsp_input_get_last_int64(CCspInputHandle input, int64_t* out_value); + +/* + * Get last value as double (convenience for double outputs) + */ +CCspErrorCode ccsp_input_get_last_double(CCspInputHandle input, double* out_value); + +/* + * Get last value as bool (convenience for bool outputs) + */ +CCspErrorCode ccsp_input_get_last_bool(CCspInputHandle input, int8_t* out_value); + +/* + * Get last value as datetime (convenience for datetime outputs) + */ +CCspErrorCode ccsp_input_get_last_datetime(CCspInputHandle input, CCspDateTime* out_value); #ifdef __cplusplus } #endif -#endif +#endif /* _IN_CSP_ENGINE_C_OUTPUTADAPTER_H */ diff --git a/cpp/csp/python/adapters/c/CMakeLists.txt b/cpp/csp/python/adapters/c/CMakeLists.txt index 3971e1efa..f2895923a 100644 --- a/cpp/csp/python/adapters/c/CMakeLists.txt +++ b/cpp/csp/python/adapters/c/CMakeLists.txt @@ -1,4 +1,28 @@ +# Python bindings for example C adapters -add_library(exampleadapterimpl SHARED exampleadapterimpl.c) -target_link_libraries(exampleadapterimpl csp_c_example_adapter) -install(TARGETS exampleadapterimpl RUNTIME DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR}) +# This creates a Python extension module +Python_add_library(_exampleadapterimpl MODULE exampleadapterimpl.c) + +# Link with the example adapter library +target_link_libraries(_exampleadapterimpl PRIVATE + csp_c_example_adapter +) + +# Include directories +target_include_directories(_exampleadapterimpl PRIVATE + ${CMAKE_SOURCE_DIR}/cpp + ${Python_INCLUDE_DIRS} +) + +# Set output name without lib prefix +set_target_properties(_exampleadapterimpl PROPERTIES + PREFIX "" + C_STANDARD 11 + C_STANDARD_REQUIRED ON +) + +# Install to the Python package location +install(TARGETS _exampleadapterimpl + LIBRARY DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR} + RUNTIME DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR} +) diff --git a/cpp/csp/python/adapters/c/exampleadapterimpl.c b/cpp/csp/python/adapters/c/exampleadapterimpl.c index 756fc4bdc..e9fe9395c 100644 --- a/cpp/csp/python/adapters/c/exampleadapterimpl.c +++ b/cpp/csp/python/adapters/c/exampleadapterimpl.c @@ -1,36 +1,261 @@ +/* + * Python bindings for the example C adapters + */ #include +#include #include "Python.h" #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include -PyObject* create_input_adapter_py(PyObject*, PyObject*) { - // TODO - return Py_None; +/* + * _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 ); } -PyObject* create_output_adapter_py(PyObject*, PyObject*) { - // TODO - uint64_t x = 42; - void* ptr = (void*) x; - return createCOutputAdapterCapsule( (OutputAdapter*) x ); + +/* + * _example_input_adapter(manager_or_interval, ts_type, properties, push_mode) -> capsule + * + * Create an example input adapter that generates incrementing integers. + * Can be used with or without an adapter manager. + */ +static PyObject * create_input_adapter_py( PyObject * self, PyObject * args, PyObject * kwargs ) +{ + ( void )self; + + 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 ); +} + +/* + * _example_output_adapter(prefix: str = None) -> capsule + * + * Create an example output adapter that prints values to stdout. + */ +static PyObject * create_output_adapter_py( PyObject * self, PyObject * args, PyObject * kwargs ) +{ + ( void )self; + + 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 ); +} + +/* + * _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_input_adapter", (PyCFunction)create_input_adapter_py, METH_VARARGS}, - {"_example_output_adapter", (PyCFunction)create_output_adapter_py, METH_VARARGS}, - {NULL, NULL, 0, NULL} + { "_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, - "exampleadapter", - "exampleadapter module", - -1, - exampleadapter_methods + PyModuleDef_HEAD_INIT, + "_exampleadapterimpl", + "Example C adapter implementations for CSP", + -1, + exampleadapter_methods }; - - -PyMODINIT_FUNC PyInit__exampleadapterimpl(void) { - Py_Initialize(); - return PyModule_Create(&exampleadapter_module); + +PyMODINIT_FUNC PyInit__exampleadapterimpl( void ) +{ + return PyModule_Create( &exampleadapter_module ); } \ No newline at end of file diff --git a/cpp/csp/python/c/PyAdapterManager.h b/cpp/csp/python/c/PyAdapterManager.h new file mode 100644 index 000000000..1fa6c9a9c --- /dev/null +++ b/cpp/csp/python/c/PyAdapterManager.h @@ -0,0 +1,113 @@ +/* + * 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..536e9d07f --- /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 index c45173302..25b81eb79 100644 --- a/cpp/csp/python/c/PyOutputAdapter.h +++ b/cpp/csp/python/c/PyOutputAdapter.h @@ -1,15 +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 -// Create a special python capsule type to indicate to the C++ code that this is an output adapter -// defined in an external language and communicating via the C ABI interface. +#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; -const char * const CSP_PYTHON_C_OUTPUT_ADAPTER_CAPSULE_NAME = "csp.python.c.OutputAdapterCapsule"; + 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 ); +} -PyObject * createCOutputAdapterCapsule( OutputAdapter * c_adapter_ptr ) { - return PyCapsule_New( c_adapter_ptr, CSP_PYTHON_C_OUTPUT_ADAPTER_CAPSULE_NAME, NULL ); +/* + * 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/csp/adapters/c_example.py b/csp/adapters/c_example.py index 3637dd4e9..291e8f3ac 100644 --- a/csp/adapters/c_example.py +++ b/csp/adapters/c_example.py @@ -1,15 +1,105 @@ +""" +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. It provides: +- ExampleAdapterManager: Coordinates multiple adapters, manages lifecycle +- Example input adapter: Generates incrementing integers +- Example output adapter: Prints values to stdout +""" + from typing import TypeVar +import csp from csp import ts from csp.impl.wiring import input_adapter_def, output_adapter_def from csp.lib import _exampleadapterimpl T = TypeVar("T") + +class ExampleAdapterManager: + """ + Example adapter manager that demonstrates the C API adapter manager pattern. + + Adapter managers coordinate the lifecycle of related adapters: + - Start: Called when the CSP graph starts + - Stop: Called when the CSP graph stops + - Sim time slicing: For replay/simulation mode + + Usage: + manager = ExampleAdapterManager(prefix="[MyApp]") + + @csp.graph + def my_graph(): + data = manager.subscribe(int, interval_ms=100) + manager.publish(data) + """ + + def __init__(self, prefix: str = None): + """ + Initialize the adapter manager. + + Args: + prefix: Optional prefix for output messages + """ + self._prefix = prefix + self._properties = { + "prefix": prefix or "", + } + + def subscribe( + self, + ts_type: type, + interval_ms: int = 100, + push_mode: csp.PushMode = csp.PushMode.NON_COLLAPSING, + ): + """ + Create an 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 + """ + properties = { + "interval_ms": interval_ms, + } + return _example_input_adapter_def(self, ts_type, properties, push_mode=push_mode) + + def publish(self, x: ts["T"]): + """ + Create an output adapter that prints values to stdout. + + Args: + x: The timeseries to publish + """ + return _example_output_adapter_def(self, x) + + def __hash__(self): + return hash(self._prefix) + + def __eq__(self, other): + return isinstance(other, ExampleAdapterManager) and self._prefix == other._prefix + + def _create(self, engine, memo): + """ + Create the underlying C++ adapter manager. + + This method is called by CSP when the graph is built. + """ + return _exampleadapterimpl._example_adapter_manager(engine, self._properties) + + +# Adapter definitions using the adapter manager pattern _example_input_adapter_def = input_adapter_def( "example_input_adapter", _exampleadapterimpl._example_input_adapter, ts["T"], + ExampleAdapterManager, typ="T", properties=dict, ) @@ -17,5 +107,155 @@ _example_output_adapter_def = output_adapter_def( "example_output_adapter", _exampleadapterimpl._example_output_adapter, + ExampleAdapterManager, + input=ts["T"], +) + + +# Standalone adapters (without manager) for simple use cases +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. + + This is a simpler API when you don't need a full adapter 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 _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. + + This is a simpler API when you don't need a full adapter manager. + + Args: + x: The timeseries to publish + prefix: Optional prefix for output messages + """ + return _standalone_output_adapter_def(x, prefix=prefix) + + +# Standalone adapter definitions (no manager required) +_standalone_input_adapter_def = input_adapter_def( + "example_input_adapter_standalone", + _exampleadapterimpl._example_input_adapter, + ts["T"], + typ="T", + interval_ms=int, +) + +_standalone_output_adapter_def = output_adapter_def( + "example_output_adapter_standalone", + _exampleadapterimpl._example_output_adapter, input=ts["T"], + prefix=str, ) + + +# ============================================================================ +# Struct C API Utilities (Phase 6 - Struct Access) +# ============================================================================ + +# The following functions demonstrate the C Struct API pattern. +# The actual struct inspection is done in C via CspStruct.h +# +# C API for Struct Access: +# - ccsp_struct_meta_name(meta) -> const char* +# - ccsp_struct_meta_field_count(meta) -> size_t +# - ccsp_struct_meta_field_by_index(meta, index) -> CCspStructFieldHandle +# - ccsp_struct_meta_field_by_name(meta, name) -> CCspStructFieldHandle +# - ccsp_struct_field_name(field) -> const char* +# - ccsp_struct_field_type(field) -> CCspType +# - ccsp_struct_field_is_optional(field) -> int +# - ccsp_struct_get_*(s, field, &out_value) -> CCspErrorCode +# - ccsp_struct_set_*(s, field, value) -> CCspErrorCode +# - ccsp_struct_create(meta) -> CCspStructHandle +# - ccsp_struct_destroy(s) +# - ccsp_struct_copy(s) -> CCspStructHandle + + +def inspect_struct_type(struct_type: type) -> dict: + """ + Inspect a csp.Struct type's fields using the C struct API. + + This demonstrates using the C Struct API to access type metadata. + The actual implementation calls into C code via _exampleadapterimpl. + + Note: This function requires the struct type to have a _struct_meta_capsule + attribute, which is set up for struct types that support C API access. + + Args: + struct_type: A csp.Struct subclass + + Returns: + A dict containing: + - name: The struct type name + - field_count: Number of fields + - is_strict: Whether the struct is strict + - fields: List of field info dicts (name, type, is_optional) + + Raises: + TypeError: If struct_type is not a valid struct type with C API support + + Example: + >>> import csp + >>> class MyStruct(csp.Struct): + ... x: int + ... y: float + ... name: str + ... + >>> # Note: This requires _struct_meta_capsule to be set up + >>> # info = inspect_struct_type(MyStruct) + """ + return _exampleadapterimpl._example_inspect_struct_type(struct_type) + + +# Type mapping for CCspType values (matches CspType.h enum) +CCSP_TYPE_NAMES = { + 0: "UNKNOWN", + 1: "BOOL", + 2: "INT8", + 3: "UINT8", + 4: "INT16", + 5: "UINT16", + 6: "INT32", + 7: "UINT32", + 8: "INT64", + 9: "UINT64", + 10: "DOUBLE", + 11: "DATETIME", + 12: "TIMEDELTA", + 13: "DATE", + 14: "TIME", + 15: "ENUM", + 16: "STRING", + 17: "STRUCT", + 18: "ARRAY", + 19: "DIALECT_GENERIC", +} + + +def get_type_name(ccsp_type: int) -> str: + """ + Get the string name for a CCspType enum value. + + Args: + ccsp_type: The integer type code from the C API + + Returns: + The string name of the type (e.g., "INT64", "STRING") + """ + return CCSP_TYPE_NAMES.get(ccsp_type, f"UNKNOWN({ccsp_type})") + diff --git a/docs/C_API_ROADMAP.md b/docs/C_API_ROADMAP.md new file mode 100644 index 000000000..996623f87 --- /dev/null +++ b/docs/C_API_ROADMAP.md @@ -0,0 +1,670 @@ +# CSP C API Roadmap + +## Overview + +This document outlines the roadmap for completing the C API that enables CSP adapters (Kafka, Parquet, WebSocket) to be compiled and distributed separately from CSP core. + +## Current State Analysis + +### What Has Been Done + +| File | Status | Description | +|------|--------|-------------| +| `cpp/csp/engine/c/CspType.h` | ✅ Basic | CCspType enum with all basic type definitions | +| `cpp/csp/engine/c/OutputAdapter.h` | 🔶 Stub | Empty opaque OutputAdapter struct | +| `cpp/csp/engine/OutputAdapterExtern.h` | 🔶 Stub | C++ wrapper class, no implementation | +| `cpp/csp/python/c/PyOutputAdapter.h` | ✅ Partial | Python capsule creation for C adapters | +| `cpp/csp/adapters/c/example/*` | 🔶 Stub | Empty example adapter files | +| `cpp/csp/python/adapters/c/exampleadapterimpl.c` | 🔶 Stub | Basic Python module structure | +| `csp/adapters/c_example.py` | ✅ Complete | Python wiring definitions | + +### Adapter Dependencies Analysis + +Analyzing the Kafka, Parquet, and WebSocket adapters reveals these CSP core dependencies: + +#### Output Adapter Dependencies +- `Engine*` - for scheduling +- `TimeSeriesProvider::lastValueTyped()` - reading input values +- `CspType` - type information +- `Dictionary` - configuration properties +- `Struct` - structured data access +- `DateTime/TimeDelta` - time handling + +#### Input Adapter Dependencies +- `Engine*` - for scheduling +- `PushInputAdapter::pushTick()` - pushing data into graph +- `PushBatch/PushGroup` - batching and synchronization +- `CspType` - type information +- `StructMeta/StructField` - struct metadata access +- `DateTime/TimeDelta` - time handling + +#### Adapter Manager Dependencies +- `AdapterManager` base class - lifecycle (start/stop) +- `RootEngine` - root engine access +- `StatusAdapter` - status reporting +- `Dictionary` - configuration + +--- + +## Phase 1: Core C Interface Types + +**Goal:** Define ABI-stable C representations of all CSP types + +### 1.1 Basic Value Types (`cpp/csp/engine/c/CspValue.h`) + +```c +// Already defined: CCspType enum + +// Value container for passing data across ABI +typedef struct { + CCspType type; + union { + int8_t bool_val; // CCSP_TYPE_BOOL + 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; + struct { const char* data; size_t length; } string_val; + int64_t datetime_val; // nanoseconds since epoch + int64_t timedelta_val; // nanoseconds + int32_t date_val; // days since epoch + int64_t time_val; // nanoseconds since midnight + int32_t enum_val; // enum ordinal + void* struct_ptr; // opaque pointer + metadata + void* dialect_ptr; // opaque dialect object + struct { void* data; size_t length; CCspType elem_type; } array_val; + }; +} CCspValue; +``` + +### 1.2 Time Types (`cpp/csp/engine/c/CspTime.h`) + +```c +typedef int64_t CCspDateTime; // nanoseconds since Unix epoch +typedef int64_t CCspTimeDelta; // nanoseconds duration +typedef int32_t CCspDate; // days since Unix epoch +typedef int64_t CCspTime; // nanoseconds since midnight + +// Conversion functions +CCspDateTime ccsp_datetime_now(); +CCspDateTime ccsp_datetime_from_parts(int year, int month, int day, int hour, int min, int sec, int nsec); +CCspTimeDelta ccsp_timedelta_from_seconds(double seconds); +``` + +### 1.3 String Type (`cpp/csp/engine/c/CspString.h`) + +```c +typedef struct { + const char* data; + size_t length; + int is_owned; // 1 if the C side owns the memory +} CCspString; + +CCspString ccsp_string_create(const char* data, size_t length); +void ccsp_string_free(CCspString* str); +``` + +### 1.4 Type Metadata (`cpp/csp/engine/c/CspTypeMeta.h`) + +```c +// Opaque handles +typedef struct CCspTypeInfo* CCspTypeHandle; +typedef struct CCspStructMeta* CCspStructMetaHandle; +typedef struct CCspStructField* CCspStructFieldHandle; + +// Type introspection +CCspType ccsp_type_get_kind(CCspTypeHandle type); +int ccsp_type_is_array(CCspTypeHandle type); +CCspTypeHandle ccsp_type_array_elem_type(CCspTypeHandle type); + +// Struct introspection +const char* ccsp_struct_meta_name(CCspStructMetaHandle meta); +size_t ccsp_struct_meta_field_count(CCspStructMetaHandle meta); +CCspStructFieldHandle ccsp_struct_meta_field_at(CCspStructMetaHandle meta, size_t index); +CCspStructFieldHandle ccsp_struct_meta_field_by_name(CCspStructMetaHandle meta, const char* name); + +// Field introspection +const char* ccsp_struct_field_name(CCspStructFieldHandle field); +CCspTypeHandle ccsp_struct_field_type(CCspStructFieldHandle field); +``` + +--- + +## Phase 2: Output Adapter C Interface + +**Goal:** Complete C interface for output adapters + +### 2.1 Output Adapter Interface (`cpp/csp/engine/c/OutputAdapter.h`) + +```c +// Forward declarations +typedef struct CCspEngine CCspEngine; +typedef struct CCspTimeSeriesProvider CCspTimeSeriesProvider; + +// Output adapter lifecycle callbacks (implemented by external adapter) +typedef struct { + void* user_data; + + // Called when adapter should execute (input has new value) + void (*execute)(void* user_data, CCspTimeSeriesProvider* input, CCspDateTime now); + + // Called on graph start + void (*start)(void* user_data, CCspDateTime start_time, CCspDateTime end_time); + + // Called on graph stop/cleanup + void (*stop)(void* user_data); + + // Destructor + void (*destroy)(void* user_data); + +} CCspOutputAdapterCallbacks; + +// Registration +typedef struct CCspOutputAdapter CCspOutputAdapter; + +CCspOutputAdapter* ccsp_output_adapter_create( + CCspEngine* engine, + CCspTypeHandle input_type, + CCspOutputAdapterCallbacks callbacks +); + +void ccsp_output_adapter_destroy(CCspOutputAdapter* adapter); +``` + +### 2.2 Input Value Access (`cpp/csp/engine/c/CspInput.h`) + +```c +// Get last value from input time series +int ccsp_input_get_value(CCspTimeSeriesProvider* input, CCspValue* out_value); + +// Check if input is valid (has ticked) +int ccsp_input_is_valid(CCspTimeSeriesProvider* input); + +// Get tick count +int ccsp_input_num_ticks(CCspTimeSeriesProvider* input); + +// Historical access +int ccsp_input_get_value_at_index(CCspTimeSeriesProvider* input, int32_t index, CCspValue* out_value); +CCspDateTime ccsp_input_get_time_at_index(CCspTimeSeriesProvider* input, int32_t index); +``` + +### 2.3 C++ Wrapper Implementation (`cpp/csp/engine/OutputAdapterExtern.cpp`) + +```cpp +class OutputAdapterExtern : public OutputAdapter { +public: + OutputAdapterExtern(Engine* engine, const CspTypePtr& type, + CCspOutputAdapterCallbacks callbacks); + ~OutputAdapterExtern() override; + + void executeImpl() override { + CCspTimeSeriesProvider* c_input = wrapTimeSeriesProvider(input()); + m_callbacks.execute(m_callbacks.user_data, c_input, now().asNanoseconds()); + } + + void start() override { + if (m_callbacks.start) { + m_callbacks.start(m_callbacks.user_data, + m_startTime.asNanoseconds(), + m_endTime.asNanoseconds()); + } + } + + void stop() override { + if (m_callbacks.stop) { + m_callbacks.stop(m_callbacks.user_data); + } + } + +private: + CCspOutputAdapterCallbacks m_callbacks; +}; +``` + +--- + +## Phase 3: Input Adapter C Interface + +**Goal:** Complete C interface for push input adapters + +### 3.1 Push Input Adapter Interface (`cpp/csp/engine/c/InputAdapter.h`) + +```c +typedef struct CCspPushInputAdapter CCspPushInputAdapter; +typedef struct CCspPushBatch CCspPushBatch; +typedef struct CCspPushGroup CCspPushGroup; + +// Input adapter lifecycle callbacks (implemented by external adapter) +typedef struct { + void* user_data; + + // Called on graph start + void (*start)(void* user_data, CCspDateTime start_time, CCspDateTime end_time); + + // Called on graph stop + void (*stop)(void* user_data); + + // Destructor + void (*destroy)(void* user_data); + +} CCspPushInputAdapterCallbacks; + +// Creation +CCspPushInputAdapter* ccsp_push_input_adapter_create( + CCspEngine* engine, + CCspTypeHandle type, + int push_mode, // 0=LAST_VALUE, 1=NON_COLLAPSING, 2=BURST + CCspPushGroup* group, // can be NULL + CCspPushInputAdapterCallbacks callbacks +); + +void ccsp_push_input_adapter_destroy(CCspPushInputAdapter* adapter); + +// Push data into the graph (thread-safe, called from adapter thread) +void ccsp_push_input_adapter_push_bool(CCspPushInputAdapter* adapter, int8_t value, CCspPushBatch* batch); +void ccsp_push_input_adapter_push_int64(CCspPushInputAdapter* adapter, int64_t value, CCspPushBatch* batch); +void ccsp_push_input_adapter_push_double(CCspPushInputAdapter* adapter, double value, CCspPushBatch* batch); +void ccsp_push_input_adapter_push_string(CCspPushInputAdapter* adapter, const char* data, size_t len, CCspPushBatch* batch); +void ccsp_push_input_adapter_push_datetime(CCspPushInputAdapter* adapter, CCspDateTime value, CCspPushBatch* batch); +void ccsp_push_input_adapter_push_struct(CCspPushInputAdapter* adapter, void* struct_ptr, CCspPushBatch* batch); +// ... additional type-specific push functions + +// Generic push with CCspValue +void ccsp_push_input_adapter_push_value(CCspPushInputAdapter* adapter, const CCspValue* value, CCspPushBatch* batch); + +// Batch management +CCspPushBatch* ccsp_push_batch_create(CCspEngine* engine); +void ccsp_push_batch_flush(CCspPushBatch* batch); +void ccsp_push_batch_destroy(CCspPushBatch* batch); + +// Group management +CCspPushGroup* ccsp_push_group_create(); +void ccsp_push_group_destroy(CCspPushGroup* group); +``` + +### 3.2 C++ Wrapper Implementation (`cpp/csp/engine/PushInputAdapterExtern.cpp`) + +```cpp +class PushInputAdapterExtern : public PushInputAdapter { +public: + PushInputAdapterExtern(Engine* engine, const CspTypePtr& type, + PushMode pushMode, PushGroup* group, + CCspPushInputAdapterCallbacks callbacks); + ~PushInputAdapterExtern() override; + + void start(DateTime start, DateTime end) override { + if (m_callbacks.start) { + m_callbacks.start(m_callbacks.user_data, + start.asNanoseconds(), + end.asNanoseconds()); + } + } + + void stop() override { + if (m_callbacks.stop) { + m_callbacks.stop(m_callbacks.user_data); + } + } + + // C API will call these methods + template + void pushFromC(const T& value, PushBatch* batch) { + this->pushTick(value, batch); + } + +private: + CCspPushInputAdapterCallbacks m_callbacks; +}; +``` + +--- + +## Phase 4: Adapter Manager C Interface + +**Goal:** Enable external adapter managers with proper lifecycle + +### 4.1 Adapter Manager Interface (`cpp/csp/engine/c/AdapterManager.h`) + +```c +typedef struct CCspAdapterManager CCspAdapterManager; + +// Adapter manager lifecycle callbacks +typedef struct { + void* user_data; + + // Required: name of the adapter manager + const char* (*get_name)(void* user_data); + + // Called when graph starts + void (*start)(void* user_data, CCspDateTime start_time, CCspDateTime end_time); + + // Called when graph stops + void (*stop)(void* user_data); + + // For sim adapters: return next sim time or 0 if none + CCspDateTime (*process_next_sim_time_slice)(void* user_data, CCspDateTime current_time); + + // Destructor + void (*destroy)(void* user_data); + +} CCspAdapterManagerCallbacks; + +// Creation +CCspAdapterManager* ccsp_adapter_manager_create( + CCspEngine* engine, + CCspAdapterManagerCallbacks callbacks +); + +void ccsp_adapter_manager_destroy(CCspAdapterManager* adapter_manager); + +// Get root engine for scheduling +CCspEngine* ccsp_adapter_manager_engine(CCspAdapterManager* manager); +``` + +--- + +## Phase 5: Dictionary/Configuration C Interface + +**Goal:** Enable passing configuration to external adapters + +### 5.1 Dictionary Interface (`cpp/csp/engine/c/CspDictionary.h`) + +```c +typedef struct CCspDictionary CCspDictionary; + +// Creation and destruction +CCspDictionary* ccsp_dictionary_create(); +void ccsp_dictionary_destroy(CCspDictionary* dict); + +// Check existence +int ccsp_dictionary_exists(const CCspDictionary* dict, const char* key); + +// Getters (return 0 on success, non-zero on error) +int ccsp_dictionary_get_bool(const CCspDictionary* dict, const char* key, int* out_value); +int ccsp_dictionary_get_int64(const CCspDictionary* dict, const char* key, int64_t* out_value); +int ccsp_dictionary_get_double(const CCspDictionary* dict, const char* key, double* out_value); +int ccsp_dictionary_get_string(const CCspDictionary* dict, const char* key, const char** out_data, size_t* out_len); +int ccsp_dictionary_get_datetime(const CCspDictionary* dict, const char* key, CCspDateTime* out_value); +int ccsp_dictionary_get_dict(const CCspDictionary* dict, const char* key, CCspDictionary** out_dict); + +// Getters with defaults +int64_t ccsp_dictionary_get_int64_or(const CCspDictionary* dict, const char* key, int64_t default_val); +double ccsp_dictionary_get_double_or(const CCspDictionary* dict, const char* key, double default_val); +const char* ccsp_dictionary_get_string_or(const CCspDictionary* dict, const char* key, const char* default_val); + +// Iteration +size_t ccsp_dictionary_size(const CCspDictionary* dict); +typedef struct CCspDictIterator CCspDictIterator; +CCspDictIterator* ccsp_dictionary_iter_create(const CCspDictionary* dict); +int ccsp_dictionary_iter_next(CCspDictIterator* iter, const char** out_key, CCspValue* out_value); +void ccsp_dictionary_iter_destroy(CCspDictIterator* iter); +``` + +--- + +## Phase 6: Struct Access C Interface + +**Goal:** Enable reading/writing struct fields from C + +### 6.1 Struct Access (`cpp/csp/engine/c/CspStruct.h`) + +```c +typedef struct CCspStruct CCspStruct; + +// Create a new struct instance +CCspStruct* ccsp_struct_create(CCspStructMetaHandle meta); +void ccsp_struct_destroy(CCspStruct* s); + +// Clone a struct +CCspStruct* ccsp_struct_clone(const CCspStruct* s); + +// Field access by name (slower but more convenient) +int ccsp_struct_get_field(const CCspStruct* s, const char* field_name, CCspValue* out_value); +int ccsp_struct_set_field(CCspStruct* s, const char* field_name, const CCspValue* value); + +// Field access by handle (faster, for hot paths) +int ccsp_struct_get_field_by_handle(const CCspStruct* s, CCspStructFieldHandle field, CCspValue* out_value); +int ccsp_struct_set_field_by_handle(CCspStruct* s, CCspStructFieldHandle field, const CCspValue* value); + +// Check if field is set +int ccsp_struct_is_field_set(const CCspStruct* s, CCspStructFieldHandle field); + +// Validation +int ccsp_struct_validate(const CCspStruct* s); +``` + +--- + +## Phase 7: Error Handling + +**Goal:** Consistent error reporting across the ABI + +### 7.1 Error Handling (`cpp/csp/engine/c/CspError.h`) + +```c +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_RUNTIME, + CCSP_ERROR_VALUE, + CCSP_ERROR_UNKNOWN +} CCspError; + +// Thread-local error state +CCspError ccsp_get_last_error(); +const char* ccsp_get_last_error_message(); +void ccsp_clear_error(); + +// Set error (for adapter implementations) +void ccsp_set_error(CCspError code, const char* message); +``` + +--- + +## Phase 8: Engine Access + +**Goal:** Allow adapters to interact with the engine + +### 8.1 Engine Interface (`cpp/csp/engine/c/CspEngine.h`) + +```c +// Get current time +CCspDateTime ccsp_engine_now(CCspEngine* engine); + +// Get cycle count +uint64_t ccsp_engine_cycle_count(CCspEngine* engine); + +// Schedule a callback +typedef void (*CCspCallback)(void* user_data); +void ccsp_engine_schedule_callback(CCspEngine* engine, CCspDateTime time, CCspCallback callback, void* user_data); + +// Status reporting +typedef struct CCspStatusAdapter CCspStatusAdapter; +void ccsp_status_adapter_push_status(CCspStatusAdapter* adapter, int level, const char* message); +``` + +--- + +## Phase 9: Build System and Packaging + +**Goal:** Enable separate compilation and distribution + +### 9.1 CMake Configuration + +```cmake +# cpp/csp/engine/c/CMakeLists.txt +add_library(csp_c_api SHARED + CspValue.cpp + CspTime.cpp + CspDictionary.cpp + CspStruct.cpp + CspError.cpp + CspEngine.cpp + OutputAdapter.cpp + InputAdapter.cpp + AdapterManager.cpp +) + +target_include_directories(csp_c_api PUBLIC + $ + $ +) + +# Install headers and library +install(TARGETS csp_c_api + EXPORT csp_c_api-targets + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include +) + +install(DIRECTORY include/csp/c + DESTINATION include/csp +) + +# Generate pkg-config file +configure_file(csp_c_api.pc.in csp_c_api.pc @ONLY) +install(FILES ${CMAKE_BINARY_DIR}/csp_c_api.pc DESTINATION lib/pkgconfig) +``` + +### 9.2 External Adapter Build Example + +```cmake +# External adapter CMakeLists.txt +find_package(csp_c_api REQUIRED) + +add_library(my_kafka_adapter SHARED + MyKafkaAdapter.c +) + +target_link_libraries(my_kafka_adapter + csp_c_api::csp_c_api + rdkafka +) +``` + +--- + +## Phase 10: Reference Implementation + +**Goal:** Implement one adapter (e.g., simple WebSocket output) to validate the API + +### 10.1 Complete Example Implementation + +See [cpp/csp/adapters/c/example/](../cpp/csp/adapters/c/example/) for a working example that demonstrates: + +1. Output adapter that logs to console +2. Push input adapter that reads from a simple source +3. Adapter manager with proper lifecycle + +--- + +## Implementation Priority + +### Must Have (Phase 1-4) +1. **Phase 1**: Core C types - foundation for everything +2. **Phase 2**: Output adapter - simplest case, validates design +3. **Phase 4**: Adapter manager - required for any real adapter +4. **Phase 5**: Dictionary - configuration is essential + +### Should Have (Phase 5-7) +5. **Phase 3**: Input adapter - needed for Kafka/WebSocket input +6. **Phase 6**: Struct access - needed for structured data +7. **Phase 7**: Error handling - production quality + +### Nice to Have (Phase 8-10) +8. **Phase 8**: Engine access - advanced functionality +9. **Phase 9**: Build system - distribution +10. **Phase 10**: Reference implementation - documentation + +--- + +## Estimated Effort + +| Phase | Effort (days) | Dependencies | +|-------|---------------|--------------| +| Phase 1 | 3-5 | None | +| Phase 2 | 5-7 | Phase 1 | +| Phase 3 | 5-7 | Phase 1 | +| Phase 4 | 3-5 | Phase 1, 2 | +| Phase 5 | 3-5 | Phase 1 | +| Phase 6 | 5-7 | Phase 1, 5 | +| Phase 7 | 2-3 | None | +| Phase 8 | 3-5 | Phase 1, 4 | +| Phase 9 | 2-3 | All above | +| Phase 10 | 5-7 | All above | + +**Total: ~35-50 days** + +--- + +## Success Criteria + +The C API is complete when: + +1. ✅ **Kafka adapter** can be compiled as a separate shared library that: + - Receives messages via push input adapter + - Sends messages via output adapter + - Manages its lifecycle via adapter manager + - Uses Dictionary for configuration + +2. ✅ **Parquet adapter** can be compiled separately with: + - Output adapter for writing + - Input adapter for reading (sim mode) + - Struct field access for column mapping + +3. ✅ **WebSocket adapter** can be compiled separately with: + - Bidirectional communication + - Connection lifecycle management + - String/binary message handling + +4. ✅ **ABI Stability**: + - Adapters compiled with version N work with CSP version N+1 + - No C++ types cross the ABI boundary + - All pointers are opaque handles + +5. ✅ **Documentation**: + - Complete API reference + - Migration guide for existing adapters + - Example code for new adapters + +--- + +## Open Questions + +1. **Struct Metadata Sharing**: How do we share struct definitions between CSP and external adapters? + - Option A: Adapters define their own structs, CSP marshals + - Option B: CSP provides struct creation API + - Option C: Use a common serialization format (e.g., Arrow) + +2. **Memory Ownership**: Who owns memory for strings, arrays, structs passed across the ABI? + - Proposed: CSP owns internal data; adapters must copy if they need to retain + +3. **Thread Safety**: Which functions are thread-safe? + - Proposed: Only push_* functions are thread-safe; all others require single-thread access + +4. **Versioning**: How do we version the C API? + - Proposed: Version number in header, runtime check function + +5. **Python Integration**: How do external adapters integrate with Python? + - Current: PyCapsule mechanism looks correct + - Need: Complete the Python wrapper layer + +--- + +## Next Steps + +1. Review this roadmap with stakeholders +2. Finalize decisions on open questions +3. Begin Phase 1 implementation +4. Set up CI for testing C API compatibility diff --git a/docs/wiki/api-references/C-APIs.md b/docs/wiki/api-references/C-APIs.md new file mode 100644 index 000000000..7d31503a2 --- /dev/null +++ b/docs/wiki/api-references/C-APIs.md @@ -0,0 +1,1158 @@ +# 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) + +______________________________________________________________________ + +## 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); +``` + +______________________________________________________________________ + +## 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 +- [Example Adapters](../../cpp/csp/adapters/c/example/) - Reference implementations 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..7803a9310 --- /dev/null +++ b/docs/wiki/how-tos/Write-C-API-Adapters.md @@ -0,0 +1,577 @@ +## 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](#create-python-wrapper) + - [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 create a Python extension module that wraps the C functions. + +#### Create Python Bindings (C code) + +```c +#include +#include +#include "my_adapter.h" + +static PyObject* create_log_adapter_py(PyObject* self, PyObject* args) +{ + const char* prefix = NULL; + if (!PyArg_ParseTuple(args, "|s", &prefix)) { + return NULL; + } + + CCspOutputAdapterVTable vtable = create_log_adapter(prefix); + if (!vtable.execute) { + PyErr_SetString(PyExc_MemoryError, "Failed to create adapter"); + return NULL; + } + + return ccsp_py_create_output_adapter_capsule(&vtable); +} + +static PyMethodDef methods[] = { + {"_log_adapter", create_log_adapter_py, METH_VARARGS, "Create log 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 + +```python +# my_adapter.py +from csp import ts +from csp.impl.wiring import output_adapter_def +from csp.lib import my_adapter as _impl + +LogAdapter = output_adapter_def( + "LogAdapter", + _impl._log_adapter, + input=ts["T"], + prefix=str, +) +``` + +#### Use in Your Graph + +```python +import csp +from my_adapter import LogAdapter + +@csp.graph +def my_graph(): + data = csp.timer(timedelta(seconds=1), "tick") + LogAdapter(data, prefix="[MyApp] ") + +csp.run(my_graph, starttime=datetime.utcnow(), 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 +- [Example: C API Adapter](../../examples/04_writing_adapters/e8_c_api_adapter.py) - Working example diff --git a/examples/04_writing_adapters/e8_c_api_adapter.py b/examples/04_writing_adapters/e8_c_api_adapter.py new file mode 100644 index 000000000..8c0d1b97b --- /dev/null +++ b/examples/04_writing_adapters/e8_c_api_adapter.py @@ -0,0 +1,152 @@ +""" +Example demonstrating the CSP C API adapters. + +This example shows how to use adapters written in C via the C API: +- ExampleOutputAdapter: Prints received values to stdout +- ExamplePushInputAdapter: Generates incrementing integers in a background thread + +These adapters are compiled separately from CSP and communicate via the +ABI-stable C interface defined in cpp/csp/engine/c/*.h headers. + +See docs/wiki/how-tos/Write-C-API-Adapters.md for how to write your own. +""" + +from datetime import timedelta + +import csp +from csp import ts +from csp.adapters.c_example import _example_input_adapter_def, _example_output_adapter_def +from csp.utils.datetime import utc_now + + +# Example 1: Using the C output adapter to print values +@csp.graph +def output_adapter_example(): + """ + Demonstrates the C output adapter which receives values and prints them. + The adapter is implemented in C in cpp/csp/adapters/c/example/ExampleOutputAdapter.c + """ + # Create a simple curve of values + data = csp.curve( + typ=int, + data=[ + (timedelta(milliseconds=100), 1), + (timedelta(milliseconds=200), 2), + (timedelta(milliseconds=300), 3), + (timedelta(milliseconds=400), 4), + (timedelta(milliseconds=500), 5), + ], + ) + + # Also print using Python's csp.print for comparison + csp.print("Python print", data) + + # Use the C output adapter - it will print values with a prefix + _example_output_adapter_def(data) + + +# Example 2: Using the C push input adapter to receive values from C +@csp.graph +def input_adapter_example() -> ts[int]: + """ + Demonstrates the C push input adapter which generates values in a background thread. + The adapter is implemented in C in cpp/csp/adapters/c/example/ExamplePushInputAdapter.c + """ + # Create input adapter that generates integers every 100ms + data = _example_input_adapter_def(typ=int, properties={"interval_ms": 100}) + + # Print the received values + csp.print("From C adapter", data) + + return data + + +# Example 3: Both adapters together - C input -> CSP -> C output +@csp.graph +def full_pipeline_example(): + """ + Demonstrates a full pipeline: C adapter produces data, CSP processes it, + and another C adapter consumes it. + """ + # Receive integers from C background thread + raw_data = _example_input_adapter_def(typ=int, properties={"interval_ms": 50}) + + # Process in CSP - double the values + processed = raw_data * 2 + + # Print using Python for debugging + csp.print("raw", raw_data) + csp.print("processed", processed) + + # Send to C output adapter + _example_output_adapter_def(processed) + + +def run_output_adapter_example(): + """Run the output adapter example.""" + print("\n" + "=" * 60) + print("Example 1: C Output Adapter") + print("=" * 60) + print("Running graph with C output adapter...") + print() + + csp.run( + output_adapter_example, + starttime=utc_now(), + endtime=timedelta(seconds=1), + realtime=False, # Sim mode + ) + + +def run_input_adapter_example(): + """Run the input adapter example.""" + print("\n" + "=" * 60) + print("Example 2: C Push Input Adapter") + print("=" * 60) + print("Running graph with C input adapter (realtime, 500ms)...") + print() + + csp.run( + input_adapter_example, + starttime=utc_now(), + endtime=timedelta(milliseconds=500), + realtime=True, # Need realtime for push adapters + ) + + +def run_full_pipeline_example(): + """Run the full pipeline example.""" + print("\n" + "=" * 60) + print("Example 3: Full C -> CSP -> C Pipeline") + print("=" * 60) + print("Running full pipeline (realtime, 300ms)...") + print() + + csp.run( + full_pipeline_example, + starttime=utc_now(), + endtime=timedelta(milliseconds=300), + realtime=True, + ) + + +def main(): + """Run all examples.""" + print("CSP C API Adapter Examples") + print("=" * 60) + + # Only run the output adapter example by default since the input + # adapter requires the C library to be compiled with threading support + run_output_adapter_example() + + # Uncomment to run input adapter examples: + # run_input_adapter_example() + # run_full_pipeline_example() + + print("\n" + "=" * 60) + print("Done!") + print("=" * 60) + + +if __name__ == "__main__": + main() From 2267d7045a5105e4f7abefa857fefb507e76f45d Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sun, 15 Feb 2026 18:14:57 -0500 Subject: [PATCH 03/12] Start refactoring examples Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- cpp/csp/adapters/CMakeLists.txt | 2 -- cpp/csp/adapters/c/CMakeLists.txt | 1 - cpp/csp/python/adapters/CMakeLists.txt | 2 -- cpp/csp/python/adapters/c/CMakeLists.txt | 28 ------------------ examples/05_cpp/1_cpp_node/README.md | 5 ++++ .../05_cpp/2_cpp_node_with_struct/README.md | 5 ++++ examples/05_cpp/3_cpp_adapter/README.md | 6 ++++ examples/05_cpp/4_c_api_adapter/README.md | 1 + .../4_c_api_adapter/cpp}/CMakeLists.txt | 29 +++++++++++++++++++ .../cpp}/ExampleManagedAdapter.c | 0 .../cpp}/ExampleManagedAdapter.h | 0 .../cpp}/ExampleOutputAdapter.c | 0 .../cpp}/ExampleOutputAdapter.h | 0 .../cpp}/ExamplePushInputAdapter.c | 0 .../cpp}/ExamplePushInputAdapter.h | 0 .../4_c_api_adapter/cpp}/exampleadapterimpl.c | 0 .../05_cpp/5_c_api_adapter_rust/README.md | 1 + 17 files changed, 47 insertions(+), 33 deletions(-) delete mode 100644 cpp/csp/adapters/c/CMakeLists.txt delete mode 100644 cpp/csp/python/adapters/c/CMakeLists.txt create mode 100644 examples/05_cpp/3_cpp_adapter/README.md create mode 100644 examples/05_cpp/4_c_api_adapter/README.md rename {cpp/csp/adapters/c/example => examples/05_cpp/4_c_api_adapter/cpp}/CMakeLists.txt (58%) rename {cpp/csp/adapters/c/example => examples/05_cpp/4_c_api_adapter/cpp}/ExampleManagedAdapter.c (100%) rename {cpp/csp/adapters/c/example => examples/05_cpp/4_c_api_adapter/cpp}/ExampleManagedAdapter.h (100%) rename {cpp/csp/adapters/c/example => examples/05_cpp/4_c_api_adapter/cpp}/ExampleOutputAdapter.c (100%) rename {cpp/csp/adapters/c/example => examples/05_cpp/4_c_api_adapter/cpp}/ExampleOutputAdapter.h (100%) rename {cpp/csp/adapters/c/example => examples/05_cpp/4_c_api_adapter/cpp}/ExamplePushInputAdapter.c (100%) rename {cpp/csp/adapters/c/example => examples/05_cpp/4_c_api_adapter/cpp}/ExamplePushInputAdapter.h (100%) rename {cpp/csp/python/adapters/c => examples/05_cpp/4_c_api_adapter/cpp}/exampleadapterimpl.c (100%) create mode 100644 examples/05_cpp/5_c_api_adapter_rust/README.md diff --git a/cpp/csp/adapters/CMakeLists.txt b/cpp/csp/adapters/CMakeLists.txt index c88069390..41a929dcf 100644 --- a/cpp/csp/adapters/CMakeLists.txt +++ b/cpp/csp/adapters/CMakeLists.txt @@ -11,6 +11,4 @@ if(CSP_BUILD_WS_CLIENT_ADAPTER) add_subdirectory(websocket) endif() - -add_subdirectory(c) add_subdirectory(utils) diff --git a/cpp/csp/adapters/c/CMakeLists.txt b/cpp/csp/adapters/c/CMakeLists.txt deleted file mode 100644 index cbce7e5ca..000000000 --- a/cpp/csp/adapters/c/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(example) \ No newline at end of file diff --git a/cpp/csp/python/adapters/CMakeLists.txt b/cpp/csp/python/adapters/CMakeLists.txt index 8022cfc47..62742a8b0 100644 --- a/cpp/csp/python/adapters/CMakeLists.txt +++ b/cpp/csp/python/adapters/CMakeLists.txt @@ -46,5 +46,3 @@ if(CSP_BUILD_WS_CLIENT_ADAPTER) target_link_libraries(websocketadapterimpl csp_core csp_engine cspimpl csp_websocket_client_adapter) install(TARGETS websocketadapterimpl RUNTIME DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR}) endif() - -add_subdirectory(c) diff --git a/cpp/csp/python/adapters/c/CMakeLists.txt b/cpp/csp/python/adapters/c/CMakeLists.txt deleted file mode 100644 index f2895923a..000000000 --- a/cpp/csp/python/adapters/c/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -# Python bindings for example C adapters - -# This creates a Python extension module -Python_add_library(_exampleadapterimpl MODULE exampleadapterimpl.c) - -# Link with the example adapter library -target_link_libraries(_exampleadapterimpl PRIVATE - csp_c_example_adapter -) - -# Include directories -target_include_directories(_exampleadapterimpl PRIVATE - ${CMAKE_SOURCE_DIR}/cpp - ${Python_INCLUDE_DIRS} -) - -# Set output name without lib prefix -set_target_properties(_exampleadapterimpl PROPERTIES - PREFIX "" - C_STANDARD 11 - C_STANDARD_REQUIRED ON -) - -# Install to the Python package location -install(TARGETS _exampleadapterimpl - LIBRARY DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR} - RUNTIME DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR} -) diff --git a/examples/05_cpp/1_cpp_node/README.md b/examples/05_cpp/1_cpp_node/README.md index 3c30ef50f..ada3ff6ec 100644 --- a/examples/05_cpp/1_cpp_node/README.md +++ b/examples/05_cpp/1_cpp_node/README.md @@ -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/2_cpp_node_with_struct/README.md b/examples/05_cpp/2_cpp_node_with_struct/README.md index d557af3c1..69e749371 100644 --- a/examples/05_cpp/2_cpp_node_with_struct/README.md +++ b/examples/05_cpp/2_cpp_node_with_struct/README.md @@ -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/3_cpp_adapter/README.md b/examples/05_cpp/3_cpp_adapter/README.md new file mode 100644 index 000000000..8411d0fbe --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/README.md @@ -0,0 +1,6 @@ +# Custom C++ Adapter + +> [!WARNING] +> This example is for demonstration, and is a pattern CSP uses internally for adapters. +> 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/4_c_api_adapter/README.md b/examples/05_cpp/4_c_api_adapter/README.md new file mode 100644 index 000000000..8ca3d943e --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/README.md @@ -0,0 +1 @@ +# Custom C++ Adapter via C API diff --git a/cpp/csp/adapters/c/example/CMakeLists.txt b/examples/05_cpp/4_c_api_adapter/cpp/CMakeLists.txt similarity index 58% rename from cpp/csp/adapters/c/example/CMakeLists.txt rename to examples/05_cpp/4_c_api_adapter/cpp/CMakeLists.txt index 06007539b..c2c9bacee 100644 --- a/cpp/csp/adapters/c/example/CMakeLists.txt +++ b/examples/05_cpp/4_c_api_adapter/cpp/CMakeLists.txt @@ -36,3 +36,32 @@ install(TARGETS csp_c_example_adapter RUNTIME DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR} LIBRARY DESTINATION lib/ ) + +# Python bindings for example C adapters + +# This creates a Python extension module +Python_add_library(_exampleadapterimpl MODULE exampleadapterimpl.c) + +# Link with the example adapter library +target_link_libraries(_exampleadapterimpl PRIVATE + csp_c_example_adapter +) + +# Include directories +target_include_directories(_exampleadapterimpl PRIVATE + ${CMAKE_SOURCE_DIR}/cpp + ${Python_INCLUDE_DIRS} +) + +# Set output name without lib prefix +set_target_properties(_exampleadapterimpl PROPERTIES + PREFIX "" + C_STANDARD 11 + C_STANDARD_REQUIRED ON +) + +# Install to the Python package location +install(TARGETS _exampleadapterimpl + LIBRARY DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR} + RUNTIME DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR} +) diff --git a/cpp/csp/adapters/c/example/ExampleManagedAdapter.c b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c similarity index 100% rename from cpp/csp/adapters/c/example/ExampleManagedAdapter.c rename to examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c diff --git a/cpp/csp/adapters/c/example/ExampleManagedAdapter.h b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h similarity index 100% rename from cpp/csp/adapters/c/example/ExampleManagedAdapter.h rename to examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h diff --git a/cpp/csp/adapters/c/example/ExampleOutputAdapter.c b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c similarity index 100% rename from cpp/csp/adapters/c/example/ExampleOutputAdapter.c rename to examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c diff --git a/cpp/csp/adapters/c/example/ExampleOutputAdapter.h b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.h similarity index 100% rename from cpp/csp/adapters/c/example/ExampleOutputAdapter.h rename to examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.h diff --git a/cpp/csp/adapters/c/example/ExamplePushInputAdapter.c b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.c similarity index 100% rename from cpp/csp/adapters/c/example/ExamplePushInputAdapter.c rename to examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.c diff --git a/cpp/csp/adapters/c/example/ExamplePushInputAdapter.h b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h similarity index 100% rename from cpp/csp/adapters/c/example/ExamplePushInputAdapter.h rename to examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h diff --git a/cpp/csp/python/adapters/c/exampleadapterimpl.c b/examples/05_cpp/4_c_api_adapter/cpp/exampleadapterimpl.c similarity index 100% rename from cpp/csp/python/adapters/c/exampleadapterimpl.c rename to examples/05_cpp/4_c_api_adapter/cpp/exampleadapterimpl.c 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..42a126080 --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/README.md @@ -0,0 +1 @@ +# Custom Rust Adapter via C API From 39e29299af12fb5e9ef20ab11134674a1529b3c6 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Tue, 17 Feb 2026 17:05:57 -0500 Subject: [PATCH 04/12] Working on examples and example tests Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- .github/actions/setup-rust/action.yml | 71 ++ .github/workflows/build.yml | 145 +++- Makefile | 4 +- cpp/csp/core/Platform.h | 14 +- cpp/csp/core/Time.h | 10 +- cpp/csp/engine/CMakeLists.txt | 3 + cpp/csp/engine/DictionaryExtern.cpp | 84 +-- cpp/csp/engine/PushInputAdapterExtern.cpp | 586 +++++++++++++++ cpp/csp/engine/PushInputAdapterExtern.h | 40 ++ cpp/csp/engine/c/AdapterManager.h | 29 +- cpp/csp/engine/c/CspDictionary.h | 75 +- cpp/csp/engine/c/CspError.h | 9 +- cpp/csp/engine/c/CspExport.h | 31 + cpp/csp/engine/c/CspStruct.h | 101 +-- cpp/csp/engine/c/InputAdapter.h | 45 +- cpp/csp/engine/c/OutputAdapter.h | 33 +- cpp/csp/python/CMakeLists.txt | 29 +- cpp/csp/python/PyCApiAdapters.cpp | 181 +++++ cpp/csp/python/PyStruct.cpp | 33 +- cpp/csp/python/c/CMakeLists.txt | 0 csp/adapters/c_example.py | 261 ------- docs/C_API_ROADMAP.md | 670 ------------------ docs/wiki/_Sidebar.md | 2 + docs/wiki/api-references/C-APIs.md | 186 ++++- docs/wiki/concepts/Adapters.md | 3 +- docs/wiki/how-tos/Write-C-API-Adapters.md | 259 ++++++- .../04_writing_adapters/e8_c_api_adapter.py | 152 ---- examples/05_cpp/1_cpp_node/CMakeLists.txt | 2 +- examples/05_cpp/1_cpp_node/README.md | 2 +- .../05_cpp/1_cpp_node/{ => cpp}/piglatin.cpp | 24 +- .../05_cpp/1_cpp_node/piglatin/__main__.py | 44 +- .../1_cpp_node/piglatin/test_piglatin.py | 26 + examples/05_cpp/1_cpp_node/pyproject.toml | 14 +- examples/05_cpp/1_cpp_node/setup.py | 21 - .../2_cpp_node_with_struct/CMakeLists.txt | 2 +- .../05_cpp/2_cpp_node_with_struct/README.md | 2 +- .../2_cpp_node_with_struct/cpp/struct.cpp | 108 +++ .../mystruct/__main__.py | 21 +- .../mystruct/test_mystruct.py | 55 ++ .../2_cpp_node_with_struct/pyproject.toml | 14 +- .../05_cpp/2_cpp_node_with_struct/setup.py | 21 - .../05_cpp/2_cpp_node_with_struct/struct.cpp | 99 --- examples/05_cpp/3_cpp_adapter/CMakeLists.txt | 101 +++ examples/05_cpp/3_cpp_adapter/README.md | 167 ++++- .../3_cpp_adapter/counteradapter/__init__.py | 93 +++ .../3_cpp_adapter/counteradapter/__main__.py | 101 +++ .../counteradapter/test_counteradapter.py | 19 + .../cpp/CounterAdapterManager.cpp | 108 +++ .../3_cpp_adapter/cpp/CounterAdapterManager.h | 67 ++ .../3_cpp_adapter/cpp/CounterInputAdapter.cpp | 11 + .../3_cpp_adapter/cpp/CounterInputAdapter.h | 27 + .../cpp/CounterOutputAdapter.cpp | 21 + .../3_cpp_adapter/cpp/CounterOutputAdapter.h | 32 + .../3_cpp_adapter/cpp/counteradapterimpl.cpp | 127 ++++ examples/05_cpp/3_cpp_adapter/pyproject.toml | 18 + .../05_cpp/4_c_api_adapter/CMakeLists.txt | 156 ++++ examples/05_cpp/4_c_api_adapter/README.md | 192 ++++- .../05_cpp/4_c_api_adapter/cpp/CMakeLists.txt | 67 -- .../cpp/ExampleManagedAdapter.c | 193 ++--- .../cpp/ExampleManagedAdapter.h | 14 +- .../cpp/ExampleOutputAdapter.c | 136 ++-- .../cpp/ExampleOutputAdapter.h | 6 +- .../cpp/ExamplePushInputAdapter.c | 189 ++--- .../cpp/ExamplePushInputAdapter.h | 10 +- .../4_c_api_adapter/cpp/exampleadapterimpl.c | 141 +++- .../exampleadapter/__init__.py | 276 ++++++++ .../exampleadapter/__main__.py | 68 ++ .../exampleadapter/test_example.py | 44 ++ .../05_cpp/4_c_api_adapter/pyproject.toml | 18 + .../05_cpp/5_c_api_adapter_rust/Cargo.lock | 181 +++++ .../05_cpp/5_c_api_adapter_rust/Cargo.toml | 22 + .../05_cpp/5_c_api_adapter_rust/README.md | 209 ++++++ examples/05_cpp/5_c_api_adapter_rust/build.rs | 24 + .../exampleadapter/__init__.py | 262 +++++++ .../exampleadapter/__main__.py | 68 ++ .../exampleadapter/test_exampleadapter.py | 22 + .../5_c_api_adapter_rust/pyproject.toml | 18 + .../src/adapter_manager.rs | 181 +++++ .../5_c_api_adapter_rust/src/bindings.rs | 286 ++++++++ .../5_c_api_adapter_rust/src/input_adapter.rs | 149 ++++ .../05_cpp/5_c_api_adapter_rust/src/lib.rs | 148 ++++ .../src/output_adapter.rs | 164 +++++ examples/05_cpp/README.md | 45 +- pyproject.toml | 4 + 84 files changed, 5820 insertions(+), 1876 deletions(-) create mode 100644 .github/actions/setup-rust/action.yml create mode 100644 cpp/csp/engine/PushInputAdapterExtern.cpp create mode 100644 cpp/csp/engine/PushInputAdapterExtern.h create mode 100644 cpp/csp/engine/c/CspExport.h create mode 100644 cpp/csp/python/PyCApiAdapters.cpp delete mode 100644 cpp/csp/python/c/CMakeLists.txt delete mode 100644 csp/adapters/c_example.py delete mode 100644 docs/C_API_ROADMAP.md delete mode 100644 examples/04_writing_adapters/e8_c_api_adapter.py rename examples/05_cpp/1_cpp_node/{ => cpp}/piglatin.cpp (58%) create mode 100644 examples/05_cpp/1_cpp_node/piglatin/test_piglatin.py delete mode 100644 examples/05_cpp/1_cpp_node/setup.py create mode 100644 examples/05_cpp/2_cpp_node_with_struct/cpp/struct.cpp create mode 100644 examples/05_cpp/2_cpp_node_with_struct/mystruct/test_mystruct.py delete mode 100644 examples/05_cpp/2_cpp_node_with_struct/setup.py delete mode 100644 examples/05_cpp/2_cpp_node_with_struct/struct.cpp create mode 100644 examples/05_cpp/3_cpp_adapter/CMakeLists.txt create mode 100644 examples/05_cpp/3_cpp_adapter/counteradapter/__init__.py create mode 100644 examples/05_cpp/3_cpp_adapter/counteradapter/__main__.py create mode 100644 examples/05_cpp/3_cpp_adapter/counteradapter/test_counteradapter.py create mode 100644 examples/05_cpp/3_cpp_adapter/cpp/CounterAdapterManager.cpp create mode 100644 examples/05_cpp/3_cpp_adapter/cpp/CounterAdapterManager.h create mode 100644 examples/05_cpp/3_cpp_adapter/cpp/CounterInputAdapter.cpp create mode 100644 examples/05_cpp/3_cpp_adapter/cpp/CounterInputAdapter.h create mode 100644 examples/05_cpp/3_cpp_adapter/cpp/CounterOutputAdapter.cpp create mode 100644 examples/05_cpp/3_cpp_adapter/cpp/CounterOutputAdapter.h create mode 100644 examples/05_cpp/3_cpp_adapter/cpp/counteradapterimpl.cpp create mode 100644 examples/05_cpp/3_cpp_adapter/pyproject.toml create mode 100644 examples/05_cpp/4_c_api_adapter/CMakeLists.txt delete mode 100644 examples/05_cpp/4_c_api_adapter/cpp/CMakeLists.txt create mode 100644 examples/05_cpp/4_c_api_adapter/exampleadapter/__init__.py create mode 100644 examples/05_cpp/4_c_api_adapter/exampleadapter/__main__.py create mode 100644 examples/05_cpp/4_c_api_adapter/exampleadapter/test_example.py create mode 100644 examples/05_cpp/4_c_api_adapter/pyproject.toml create mode 100644 examples/05_cpp/5_c_api_adapter_rust/Cargo.lock create mode 100644 examples/05_cpp/5_c_api_adapter_rust/Cargo.toml create mode 100644 examples/05_cpp/5_c_api_adapter_rust/build.rs create mode 100644 examples/05_cpp/5_c_api_adapter_rust/exampleadapter/__init__.py create mode 100644 examples/05_cpp/5_c_api_adapter_rust/exampleadapter/__main__.py create mode 100644 examples/05_cpp/5_c_api_adapter_rust/exampleadapter/test_exampleadapter.py create mode 100644 examples/05_cpp/5_c_api_adapter_rust/pyproject.toml create mode 100644 examples/05_cpp/5_c_api_adapter_rust/src/adapter_manager.rs create mode 100644 examples/05_cpp/5_c_api_adapter_rust/src/bindings.rs create mode 100644 examples/05_cpp/5_c_api_adapter_rust/src/input_adapter.rs create mode 100644 examples/05_cpp/5_c_api_adapter_rust/src/lib.rs create mode 100644 examples/05_cpp/5_c_api_adapter_rust/src/output_adapter.rs 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..94dc66303 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 . + ################################################################ #..............................................................# #..|########|..|########|..../####\....|########|..............# @@ -731,6 +851,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/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/cpp/csp/core/Platform.h b/cpp/csp/core/Platform.h index 37474faf6..00950bc8f 100644 --- a/cpp/csp/core/Platform.h +++ b/cpp/csp/core/Platform.h @@ -28,6 +28,15 @@ #define CSPIMPL_EXPORT __declspec(dllimport) #endif +// 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 START_PACKED __pragma( pack(push, 1) ) #define END_PACKED __pragma( pack(pop)) @@ -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,6 +102,9 @@ inline uint8_t ffs(uint64_t n) #define CSPIMPL_EXPORT #define CSPTYPESIMPL_EXPORT +// 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 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/CMakeLists.txt b/cpp/csp/engine/CMakeLists.txt index d4b3b025e..ba3c1287a 100644 --- a/cpp/csp/engine/CMakeLists.txt +++ b/cpp/csp/engine/CMakeLists.txt @@ -20,6 +20,7 @@ set(CSP_TYPES_SOURCE_FILES # C API headers (ABI-stable interface) set(CSP_C_API_HEADERS + c/CspExport.h c/CspError.h c/CspTime.h c/CspString.h @@ -61,6 +62,7 @@ set(ENGINE_PUBLIC_HEADERS PushEvent.h PullInputAdapter.h PushInputAdapter.h + PushInputAdapterExtern.h PushPullInputAdapter.h RootEngine.h Scheduler.h @@ -93,6 +95,7 @@ set(ENGINE_SOURCE_FILES AdapterManagerExtern.cpp DictionaryExtern.cpp StructExtern.cpp + PushInputAdapterExtern.cpp PendingPushEvents.cpp PushPullInputAdapter.cpp RootEngine.cpp diff --git a/cpp/csp/engine/DictionaryExtern.cpp b/cpp/csp/engine/DictionaryExtern.cpp index bfdca246a..065a1fbaf 100644 --- a/cpp/csp/engine/DictionaryExtern.cpp +++ b/cpp/csp/engine/DictionaryExtern.cpp @@ -18,7 +18,7 @@ struct CCspDictIteratorImpl 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() ) @@ -92,10 +92,10 @@ CCspDictValueType ccsp_dictionary_get_type( CCspDictionaryHandle dict, const cha { 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 ); @@ -118,7 +118,7 @@ CCspErrorCode ccsp_dictionary_get_bool( CCspDictionaryHandle dict, const char * ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); return CCSP_ERROR_NULL_POINTER; } - + auto * d = reinterpret_cast( dict ); try { @@ -149,7 +149,7 @@ CCspErrorCode ccsp_dictionary_get_int32( CCspDictionaryHandle dict, const char * ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); return CCSP_ERROR_NULL_POINTER; } - + auto * d = reinterpret_cast( dict ); try { @@ -180,7 +180,7 @@ CCspErrorCode ccsp_dictionary_get_uint32( CCspDictionaryHandle dict, const char ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); return CCSP_ERROR_NULL_POINTER; } - + auto * d = reinterpret_cast( dict ); try { @@ -211,7 +211,7 @@ CCspErrorCode ccsp_dictionary_get_int64( CCspDictionaryHandle dict, const char * ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); return CCSP_ERROR_NULL_POINTER; } - + auto * d = reinterpret_cast( dict ); try { @@ -242,7 +242,7 @@ CCspErrorCode ccsp_dictionary_get_uint64( CCspDictionaryHandle dict, const char ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); return CCSP_ERROR_NULL_POINTER; } - + auto * d = reinterpret_cast( dict ); try { @@ -273,7 +273,7 @@ CCspErrorCode ccsp_dictionary_get_double( CCspDictionaryHandle dict, const char ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); return CCSP_ERROR_NULL_POINTER; } - + auto * d = reinterpret_cast( dict ); try { @@ -304,7 +304,7 @@ CCspErrorCode ccsp_dictionary_get_datetime( CCspDictionaryHandle dict, const cha ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); return CCSP_ERROR_NULL_POINTER; } - + auto * d = reinterpret_cast( dict ); try { @@ -336,7 +336,7 @@ CCspErrorCode ccsp_dictionary_get_timedelta( CCspDictionaryHandle dict, const ch ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); return CCSP_ERROR_NULL_POINTER; } - + auto * d = reinterpret_cast( dict ); try { @@ -369,7 +369,7 @@ CCspErrorCode ccsp_dictionary_get_string( CCspDictionaryHandle dict, const char ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); return CCSP_ERROR_NULL_POINTER; } - + auto * d = reinterpret_cast( dict ); try { @@ -403,7 +403,7 @@ CCspErrorCode ccsp_dictionary_get_dict( CCspDictionaryHandle dict, const char * ccsp_set_error( CCSP_ERROR_NULL_POINTER, "null argument" ); return CCSP_ERROR_NULL_POINTER; } - + auto * d = reinterpret_cast( dict ); try { @@ -559,7 +559,7 @@ const char * ccsp_dictionary_get_string_or( CCspDictionaryHandle dict, const cha *out_length = 0; return default_value; } - + auto * d = reinterpret_cast( dict ); try { @@ -571,7 +571,7 @@ const char * ccsp_dictionary_get_string_or( CCspDictionaryHandle dict, const cha *out_length = 0; return default_value; } - + const std::string & str = d -> get( key ); if( out_length ) *out_length = str.size(); @@ -594,7 +594,7 @@ const char * ccsp_dictionary_get_string_or( CCspDictionaryHandle dict, const cha 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 ); @@ -610,9 +610,9 @@ void ccsp_dictionary_iter_destroy( CCspDictIteratorHandle iter ) 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; @@ -621,10 +621,10 @@ int ccsp_dictionary_iter_next( CCspDictIteratorHandle iter, const char ** out_ke { ++( impl -> current ); } - + if( impl -> current == impl -> end ) return 0; - + *out_key = impl -> current.key().c_str(); return 1; } @@ -632,11 +632,11 @@ int ccsp_dictionary_iter_next( CCspDictIteratorHandle iter, const char ** out_ke 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() ); } @@ -648,14 +648,14 @@ CCspErrorCode ccsp_dictionary_iter_get_bool( CCspDictIteratorHandle iter, int8_t 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; @@ -675,14 +675,14 @@ CCspErrorCode ccsp_dictionary_iter_get_int32( CCspDictIteratorHandle iter, int32 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(); @@ -702,14 +702,14 @@ CCspErrorCode ccsp_dictionary_iter_get_uint32( CCspDictIteratorHandle iter, uint 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(); @@ -729,14 +729,14 @@ CCspErrorCode ccsp_dictionary_iter_get_int64( CCspDictIteratorHandle iter, int64 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(); @@ -756,14 +756,14 @@ CCspErrorCode ccsp_dictionary_iter_get_uint64( CCspDictIteratorHandle iter, uint 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(); @@ -783,14 +783,14 @@ CCspErrorCode ccsp_dictionary_iter_get_double( CCspDictIteratorHandle iter, doub 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(); @@ -810,14 +810,14 @@ CCspErrorCode ccsp_dictionary_iter_get_datetime( CCspDictIteratorHandle iter, CC 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(); @@ -838,14 +838,14 @@ CCspErrorCode ccsp_dictionary_iter_get_timedelta( CCspDictIteratorHandle iter, C 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(); @@ -866,14 +866,14 @@ CCspErrorCode ccsp_dictionary_iter_get_string( CCspDictIteratorHandle iter, cons 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(); @@ -895,14 +895,14 @@ CCspErrorCode ccsp_dictionary_iter_get_dict( CCspDictIteratorHandle iter, CCspDi 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(); diff --git a/cpp/csp/engine/PushInputAdapterExtern.cpp b/cpp/csp/engine/PushInputAdapterExtern.cpp new file mode 100644 index 000000000..d0d92c924 --- /dev/null +++ b/cpp/csp/engine/PushInputAdapterExtern.cpp @@ -0,0 +1,586 @@ +/* + * 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/c/AdapterManager.h b/cpp/csp/engine/c/AdapterManager.h index 9bd7bd09c..914320dea 100644 --- a/cpp/csp/engine/c/AdapterManager.h +++ b/cpp/csp/engine/c/AdapterManager.h @@ -14,6 +14,7 @@ #ifndef _IN_CSP_ENGINE_C_ADAPTER_MANAGER_H #define _IN_CSP_ENGINE_C_ADAPTER_MANAGER_H +#include #include #include #include @@ -146,7 +147,7 @@ typedef struct CCspAdapterManagerVTable { * Returns: * Handle to the new adapter manager */ -CCspAdapterManagerHandle ccsp_adapter_manager_extern_create( +CSP_C_API_EXPORT CCspAdapterManagerHandle ccsp_adapter_manager_extern_create( CCspEngineHandle engine, const CCspAdapterManagerVTable* vtable); @@ -158,7 +159,7 @@ CCspAdapterManagerHandle ccsp_adapter_manager_extern_create( * Parameters: * manager - Handle to the adapter manager */ -void ccsp_adapter_manager_extern_destroy(CCspAdapterManagerHandle manager); +CSP_C_API_EXPORT void ccsp_adapter_manager_extern_destroy(CCspAdapterManagerHandle manager); /* ============================================================================ * Engine and Time Access @@ -173,7 +174,7 @@ void ccsp_adapter_manager_extern_destroy(CCspAdapterManagerHandle manager); * Returns: * Engine handle for use with other C API functions */ -CCspEngineHandle ccsp_adapter_manager_engine(CCspAdapterManagerHandle manager); +CSP_C_API_EXPORT CCspEngineHandle ccsp_adapter_manager_engine(CCspAdapterManagerHandle manager); /* * ccsp_adapter_manager_start_time - Get graph start time @@ -186,7 +187,7 @@ CCspEngineHandle ccsp_adapter_manager_engine(CCspAdapterManagerHandle manager); * Returns: * Start time in nanoseconds since epoch */ -CCspDateTime ccsp_adapter_manager_start_time(CCspAdapterManagerHandle manager); +CSP_C_API_EXPORT CCspDateTime ccsp_adapter_manager_start_time(CCspAdapterManagerHandle manager); /* * ccsp_adapter_manager_end_time - Get graph end time @@ -199,7 +200,7 @@ CCspDateTime ccsp_adapter_manager_start_time(CCspAdapterManagerHandle manager); * Returns: * End time in nanoseconds since epoch */ -CCspDateTime ccsp_adapter_manager_end_time(CCspAdapterManagerHandle manager); +CSP_C_API_EXPORT CCspDateTime ccsp_adapter_manager_end_time(CCspAdapterManagerHandle manager); /* ============================================================================ * Adapter Creation from Manager @@ -222,7 +223,7 @@ CCspDateTime ccsp_adapter_manager_end_time(CCspAdapterManagerHandle manager); * Returns: * Handle to the new output adapter */ -CCspOutputAdapterHandle ccsp_adapter_manager_create_output_adapter( +CSP_C_API_EXPORT CCspOutputAdapterHandle ccsp_adapter_manager_create_output_adapter( CCspAdapterManagerHandle manager, CCspType input_type, const CCspOutputAdapterVTable* vtable); @@ -241,7 +242,7 @@ CCspOutputAdapterHandle ccsp_adapter_manager_create_output_adapter( * Returns: * Handle to the new push input adapter */ -CCspPushInputAdapterHandle ccsp_adapter_manager_create_push_input_adapter( +CSP_C_API_EXPORT CCspPushInputAdapterHandle ccsp_adapter_manager_create_push_input_adapter( CCspAdapterManagerHandle manager, CCspType type, CCspPushMode push_mode, @@ -277,7 +278,7 @@ typedef enum { * Returns: * CCSP_OK on success, error code on failure */ -CCspErrorCode ccsp_adapter_manager_push_status( +CSP_C_API_EXPORT CCspErrorCode ccsp_adapter_manager_push_status( CCspAdapterManagerHandle manager, CCspStatusLevel level, int64_t err_code, @@ -305,7 +306,7 @@ CCspErrorCode ccsp_adapter_manager_push_status( * Returns: * Handle to the managed sim input adapter */ -CCspManagedSimInputAdapterHandle ccsp_adapter_manager_create_managed_sim_input_adapter( +CSP_C_API_EXPORT CCspManagedSimInputAdapterHandle ccsp_adapter_manager_create_managed_sim_input_adapter( CCspAdapterManagerHandle manager, CCspType type, CCspPushMode push_mode); @@ -323,15 +324,15 @@ CCspManagedSimInputAdapterHandle ccsp_adapter_manager_create_managed_sim_input_a * Returns: * CCSP_OK on success, error code on failure */ -CCspErrorCode ccsp_managed_sim_input_adapter_push_bool( +CSP_C_API_EXPORT CCspErrorCode ccsp_managed_sim_input_adapter_push_bool( CCspManagedSimInputAdapterHandle adapter, int8_t value); -CCspErrorCode ccsp_managed_sim_input_adapter_push_int64( +CSP_C_API_EXPORT CCspErrorCode ccsp_managed_sim_input_adapter_push_int64( CCspManagedSimInputAdapterHandle adapter, int64_t value); -CCspErrorCode ccsp_managed_sim_input_adapter_push_double( +CSP_C_API_EXPORT CCspErrorCode ccsp_managed_sim_input_adapter_push_double( CCspManagedSimInputAdapterHandle adapter, double value); -CCspErrorCode ccsp_managed_sim_input_adapter_push_string( +CSP_C_API_EXPORT CCspErrorCode ccsp_managed_sim_input_adapter_push_string( CCspManagedSimInputAdapterHandle adapter, const char* data, size_t length); -CCspErrorCode ccsp_managed_sim_input_adapter_push_datetime( +CSP_C_API_EXPORT CCspErrorCode ccsp_managed_sim_input_adapter_push_datetime( CCspManagedSimInputAdapterHandle adapter, CCspDateTime value); #ifdef __cplusplus diff --git a/cpp/csp/engine/c/CspDictionary.h b/cpp/csp/engine/c/CspDictionary.h index 2ed471b7a..1da60f224 100644 --- a/cpp/csp/engine/c/CspDictionary.h +++ b/cpp/csp/engine/c/CspDictionary.h @@ -11,6 +11,7 @@ #ifndef _IN_CSP_ENGINE_C_CSPDICTIONARY_H #define _IN_CSP_ENGINE_C_CSPDICTIONARY_H +#include #include #include #include @@ -69,7 +70,7 @@ typedef enum { * Returns: * 1 if the key exists, 0 if not (or if dict is NULL) */ -int ccsp_dictionary_exists( CCspDictionaryHandle dict, const char * key ); +CSP_C_API_EXPORT int ccsp_dictionary_exists( CCspDictionaryHandle dict, const char * key ); /* * ccsp_dictionary_size - Get the number of entries in the dictionary @@ -80,7 +81,7 @@ int ccsp_dictionary_exists( CCspDictionaryHandle dict, const char * key ); * Returns: * Number of entries, or 0 if dict is NULL */ -size_t ccsp_dictionary_size( CCspDictionaryHandle dict ); +CSP_C_API_EXPORT size_t ccsp_dictionary_size( CCspDictionaryHandle dict ); /* * ccsp_dictionary_is_empty - Check if the dictionary is empty @@ -91,7 +92,7 @@ size_t ccsp_dictionary_size( CCspDictionaryHandle dict ); * Returns: * 1 if empty (or NULL), 0 if has entries */ -int ccsp_dictionary_is_empty( CCspDictionaryHandle dict ); +CSP_C_API_EXPORT int ccsp_dictionary_is_empty( CCspDictionaryHandle dict ); /* * ccsp_dictionary_get_type - Get the type of a value in the dictionary @@ -103,20 +104,20 @@ int ccsp_dictionary_is_empty( CCspDictionaryHandle dict ); * Returns: * CCspDictValueType indicating the type, or CCSP_DICT_TYPE_NONE if not found */ -CCspDictValueType ccsp_dictionary_get_type( CCspDictionaryHandle dict, const char * key ); +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) * ============================================================================ */ -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 ); +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 @@ -132,7 +133,7 @@ CCspErrorCode ccsp_dictionary_get_timedelta( CCspDictionaryHandle dict, const ch * Returns: * CCSP_OK on success, error code on failure */ -CCspErrorCode ccsp_dictionary_get_string( CCspDictionaryHandle dict, const char * key, +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_get_string( CCspDictionaryHandle dict, const char * key, const char ** out_data, size_t * out_length ); /* @@ -148,7 +149,7 @@ CCspErrorCode ccsp_dictionary_get_string( CCspDictionaryHandle dict, const char * Returns: * CCSP_OK on success, error code on failure */ -CCspErrorCode ccsp_dictionary_get_dict( CCspDictionaryHandle dict, const char * key, +CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_get_dict( CCspDictionaryHandle dict, const char * key, CCspDictionaryHandle * out_dict ); /* ============================================================================ @@ -158,14 +159,14 @@ CCspErrorCode ccsp_dictionary_get_dict( CCspDictionaryHandle dict, const char * * These return the default value if the key doesn't exist or has the wrong type. */ -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 ); +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 @@ -182,7 +183,7 @@ CCspTimeDelta ccsp_dictionary_get_timedelta_or( CCspDictionaryHandle dict, const * Returns: * Pointer to string data (borrowed from dict or default_value) */ -const char * ccsp_dictionary_get_string_or( CCspDictionaryHandle dict, const char * key, +CSP_C_API_EXPORT const char * ccsp_dictionary_get_string_or( CCspDictionaryHandle dict, const char * key, const char * default_value, size_t * out_length ); /* ============================================================================ @@ -211,7 +212,7 @@ const char * ccsp_dictionary_get_string_or( CCspDictionaryHandle dict, const cha * Returns: * Iterator handle, or NULL on error */ -CCspDictIteratorHandle ccsp_dictionary_iter_create( CCspDictionaryHandle dict ); +CSP_C_API_EXPORT CCspDictIteratorHandle ccsp_dictionary_iter_create( CCspDictionaryHandle dict ); /* * ccsp_dictionary_iter_destroy - Destroy an iterator @@ -219,7 +220,7 @@ CCspDictIteratorHandle ccsp_dictionary_iter_create( CCspDictionaryHandle dict ); * Parameters: * iter - Iterator handle */ -void ccsp_dictionary_iter_destroy( CCspDictIteratorHandle iter ); +CSP_C_API_EXPORT void ccsp_dictionary_iter_destroy( CCspDictIteratorHandle iter ); /* * ccsp_dictionary_iter_next - Advance to the next entry @@ -231,7 +232,7 @@ void ccsp_dictionary_iter_destroy( CCspDictIteratorHandle iter ); * Returns: * 1 if there is a next entry, 0 if iteration is complete */ -int ccsp_dictionary_iter_next( CCspDictIteratorHandle iter, const char ** out_key ); +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 @@ -244,19 +245,19 @@ int ccsp_dictionary_iter_next( CCspDictIteratorHandle iter, const char ** out_ke * Returns: * Type of the current value */ -CCspDictValueType ccsp_dictionary_iter_value_type( CCspDictIteratorHandle iter ); +CSP_C_API_EXPORT CCspDictValueType ccsp_dictionary_iter_value_type( CCspDictIteratorHandle iter ); /* Type-safe value getters for current iterator position */ -CCspErrorCode ccsp_dictionary_iter_get_bool( CCspDictIteratorHandle iter, int8_t * out_value ); -CCspErrorCode ccsp_dictionary_iter_get_int32( CCspDictIteratorHandle iter, int32_t * out_value ); -CCspErrorCode ccsp_dictionary_iter_get_uint32( CCspDictIteratorHandle iter, uint32_t * out_value ); -CCspErrorCode ccsp_dictionary_iter_get_int64( CCspDictIteratorHandle iter, int64_t * out_value ); -CCspErrorCode ccsp_dictionary_iter_get_uint64( CCspDictIteratorHandle iter, uint64_t * out_value ); -CCspErrorCode ccsp_dictionary_iter_get_double( CCspDictIteratorHandle iter, double * out_value ); -CCspErrorCode ccsp_dictionary_iter_get_datetime( CCspDictIteratorHandle iter, CCspDateTime * out_value ); -CCspErrorCode ccsp_dictionary_iter_get_timedelta( CCspDictIteratorHandle iter, CCspTimeDelta * out_value ); -CCspErrorCode ccsp_dictionary_iter_get_string( CCspDictIteratorHandle iter, const char ** out_data, size_t * out_length ); -CCspErrorCode ccsp_dictionary_iter_get_dict( CCspDictIteratorHandle iter, CCspDictionaryHandle * out_dict ); +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 } diff --git a/cpp/csp/engine/c/CspError.h b/cpp/csp/engine/c/CspError.h index 962866e50..f277dc147 100644 --- a/cpp/csp/engine/c/CspError.h +++ b/cpp/csp/engine/c/CspError.h @@ -7,6 +7,7 @@ #ifndef _IN_CSP_ENGINE_C_CSPERROR_H #define _IN_CSP_ENGINE_C_CSPERROR_H +#include #include #ifdef __cplusplus @@ -29,19 +30,19 @@ typedef enum { } CCspErrorCode; /* Get the last error code for the current thread */ -CCspErrorCode ccsp_get_last_error(void); +CSP_C_API_EXPORT CCspErrorCode ccsp_get_last_error(void); /* Get the last error message for the current thread (may be NULL) */ -const char* ccsp_get_last_error_message(void); +CSP_C_API_EXPORT const char* ccsp_get_last_error_message(void); /* Clear the last error for the current thread */ -void ccsp_clear_error(void); +CSP_C_API_EXPORT void ccsp_clear_error(void); /* * Set an error (for adapter implementations). * The message is copied internally. */ -void ccsp_set_error(CCspErrorCode code, const char* message); +CSP_C_API_EXPORT void ccsp_set_error(CCspErrorCode code, const char* message); /* * Helper macro for checking and returning on error diff --git a/cpp/csp/engine/c/CspExport.h b/cpp/csp/engine/c/CspExport.h new file mode 100644 index 000000000..3db0fb1ac --- /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/CspStruct.h b/cpp/csp/engine/c/CspStruct.h index 0109ae26e..bada577fb 100644 --- a/cpp/csp/engine/c/CspStruct.h +++ b/cpp/csp/engine/c/CspStruct.h @@ -16,6 +16,7 @@ #ifndef _IN_CSP_ENGINE_C_CSPSTRUCT_H #define _IN_CSP_ENGINE_C_CSPSTRUCT_H +#include #include #include #include @@ -53,7 +54,7 @@ typedef void * CCspStructFieldHandle; * Pointer to null-terminated string (borrowed, do not free) * NULL if meta is NULL */ -const char * ccsp_struct_meta_name( CCspStructMetaHandle meta ); +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 @@ -64,7 +65,7 @@ const char * ccsp_struct_meta_name( CCspStructMetaHandle meta ); * Returns: * Number of fields, or 0 if meta is NULL */ -size_t ccsp_struct_meta_field_count( CCspStructMetaHandle meta ); +CSP_C_API_EXPORT size_t ccsp_struct_meta_field_count( CCspStructMetaHandle meta ); /* * ccsp_struct_meta_field_by_index - Get field handle by index @@ -76,7 +77,7 @@ size_t ccsp_struct_meta_field_count( CCspStructMetaHandle meta ); * Returns: * Handle to StructField, or NULL if index out of range */ -CCspStructFieldHandle ccsp_struct_meta_field_by_index( CCspStructMetaHandle meta, size_t index ); +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 @@ -88,7 +89,7 @@ CCspStructFieldHandle ccsp_struct_meta_field_by_index( CCspStructMetaHandle meta * Returns: * Handle to StructField, or NULL if not found */ -CCspStructFieldHandle ccsp_struct_meta_field_by_name( CCspStructMetaHandle meta, const char * name ); +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 @@ -101,7 +102,7 @@ CCspStructFieldHandle ccsp_struct_meta_field_by_name( CCspStructMetaHandle meta, * Pointer to null-terminated string (borrowed, do not free) * NULL if index out of range */ -const char * ccsp_struct_meta_field_name_by_index( CCspStructMetaHandle meta, size_t index ); +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 @@ -114,7 +115,7 @@ const char * ccsp_struct_meta_field_name_by_index( CCspStructMetaHandle meta, si * Returns: * 1 if strict, 0 otherwise */ -int ccsp_struct_meta_is_strict( CCspStructMetaHandle meta ); +CSP_C_API_EXPORT int ccsp_struct_meta_is_strict( CCspStructMetaHandle meta ); /* ============================================================================ * StructField Functions (Field Metadata) @@ -130,7 +131,7 @@ int ccsp_struct_meta_is_strict( CCspStructMetaHandle meta ); * Pointer to null-terminated string (borrowed, do not free) * NULL if field is NULL */ -const char * ccsp_struct_field_name( CCspStructFieldHandle field ); +CSP_C_API_EXPORT const char * ccsp_struct_field_name( CCspStructFieldHandle field ); /* * ccsp_struct_field_type - Get the CSP type of a field @@ -141,7 +142,7 @@ const char * ccsp_struct_field_name( CCspStructFieldHandle field ); * Returns: * CCspType enum value, or CCSP_TYPE_UNKNOWN if field is NULL */ -CCspType ccsp_struct_field_type( CCspStructFieldHandle field ); +CSP_C_API_EXPORT CCspType ccsp_struct_field_type( CCspStructFieldHandle field ); /* * ccsp_struct_field_is_optional - Check if a field is optional @@ -152,7 +153,7 @@ CCspType ccsp_struct_field_type( CCspStructFieldHandle field ); * Returns: * 1 if optional, 0 otherwise */ -int ccsp_struct_field_is_optional( CCspStructFieldHandle field ); +CSP_C_API_EXPORT int ccsp_struct_field_is_optional( CCspStructFieldHandle field ); /* ============================================================================ * Struct Instance Functions @@ -167,7 +168,7 @@ int ccsp_struct_field_is_optional( CCspStructFieldHandle field ); * Returns: * Handle to StructMeta, or NULL if s is NULL */ -CCspStructMetaHandle ccsp_struct_meta( CCspStructHandle s ); +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 @@ -179,7 +180,7 @@ CCspStructMetaHandle ccsp_struct_meta( CCspStructHandle s ); * Returns: * 1 if set, 0 if not set or on error */ -int ccsp_struct_field_is_set( CCspStructHandle s, CCspStructFieldHandle field ); +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 @@ -191,7 +192,7 @@ int ccsp_struct_field_is_set( CCspStructHandle s, CCspStructFieldHandle field ); * Returns: * 1 if None, 0 otherwise */ -int ccsp_struct_field_is_none( CCspStructHandle s, CCspStructFieldHandle field ); +CSP_C_API_EXPORT int ccsp_struct_field_is_none( CCspStructHandle s, CCspStructFieldHandle field ); /* ============================================================================ * Field Value Getters @@ -201,18 +202,18 @@ int ccsp_struct_field_is_none( CCspStructHandle s, CCspStructFieldHandle field ) * CCSP_ERROR_TYPE_MISMATCH is returned if the field type doesn't match. * ============================================================================ */ -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 ); -CCspErrorCode ccsp_struct_get_datetime( CCspStructHandle s, CCspStructFieldHandle field, CCspDateTime * out_value ); -CCspErrorCode ccsp_struct_get_timedelta( CCspStructHandle s, CCspStructFieldHandle field, CCspTimeDelta * out_value ); +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 @@ -226,7 +227,7 @@ CCspErrorCode ccsp_struct_get_timedelta( CCspStructHandle s, CCspStructFieldHand * Returns: * CCSP_OK on success, error code on failure */ -CCspErrorCode ccsp_struct_get_string( CCspStructHandle s, CCspStructFieldHandle field, +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_string( CCspStructHandle s, CCspStructFieldHandle field, const char ** out_data, size_t * out_length ); /* @@ -240,7 +241,7 @@ CCspErrorCode ccsp_struct_get_string( CCspStructHandle s, CCspStructFieldHandle * Returns: * CCSP_OK on success, error code on failure */ -CCspErrorCode ccsp_struct_get_enum( CCspStructHandle s, CCspStructFieldHandle field, int32_t * out_ordinal ); +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 @@ -253,7 +254,7 @@ CCspErrorCode ccsp_struct_get_enum( CCspStructHandle s, CCspStructFieldHandle fi * Returns: * CCSP_OK on success, error code on failure */ -CCspErrorCode ccsp_struct_get_struct( CCspStructHandle s, CCspStructFieldHandle field, +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_struct( CCspStructHandle s, CCspStructFieldHandle field, CCspStructHandle * out_struct ); /* ============================================================================ @@ -263,12 +264,12 @@ CCspErrorCode ccsp_struct_get_struct( CCspStructHandle s, CCspStructFieldHandle * Less efficient than caching the field handle for repeated access. * ============================================================================ */ -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, +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 ); /* ============================================================================ @@ -278,18 +279,18 @@ CCspErrorCode ccsp_struct_get_string_by_name( CCspStructHandle s, const char * n * CCSP_ERROR_TYPE_MISMATCH is returned if the field type doesn't match. * ============================================================================ */ -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 ); -CCspErrorCode ccsp_struct_set_datetime( CCspStructHandle s, CCspStructFieldHandle field, CCspDateTime value ); -CCspErrorCode ccsp_struct_set_timedelta( CCspStructHandle s, CCspStructFieldHandle field, CCspTimeDelta value ); +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 @@ -305,7 +306,7 @@ CCspErrorCode ccsp_struct_set_timedelta( CCspStructHandle s, CCspStructFieldHand * Returns: * CCSP_OK on success, error code on failure */ -CCspErrorCode ccsp_struct_set_string( CCspStructHandle s, CCspStructFieldHandle field, +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_string( CCspStructHandle s, CCspStructFieldHandle field, const char * data, size_t length ); /* @@ -319,7 +320,7 @@ CCspErrorCode ccsp_struct_set_string( CCspStructHandle s, CCspStructFieldHandle * Returns: * CCSP_OK on success, error code on failure */ -CCspErrorCode ccsp_struct_set_enum( CCspStructHandle s, CCspStructFieldHandle field, int32_t ordinal ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_enum( CCspStructHandle s, CCspStructFieldHandle field, int32_t ordinal ); /* ============================================================================ * Struct Creation (if needed by adapters) @@ -336,7 +337,7 @@ CCspErrorCode ccsp_struct_set_enum( CCspStructHandle s, CCspStructFieldHandle fi * Returns: * Handle to new Struct, or NULL on error */ -CCspStructHandle ccsp_struct_create( CCspStructMetaHandle meta ); +CSP_C_API_EXPORT CCspStructHandle ccsp_struct_create( CCspStructMetaHandle meta ); /* * ccsp_struct_destroy - Destroy a struct created with ccsp_struct_create @@ -347,7 +348,7 @@ CCspStructHandle ccsp_struct_create( CCspStructMetaHandle meta ); * Parameters: * s - Handle to Struct to destroy */ -void ccsp_struct_destroy( CCspStructHandle s ); +CSP_C_API_EXPORT void ccsp_struct_destroy( CCspStructHandle s ); /* * ccsp_struct_copy - Create a deep copy of a struct @@ -360,7 +361,7 @@ void ccsp_struct_destroy( CCspStructHandle s ); * Returns: * Handle to new Struct copy, or NULL on error */ -CCspStructHandle ccsp_struct_copy( CCspStructHandle s ); +CSP_C_API_EXPORT CCspStructHandle ccsp_struct_copy( CCspStructHandle s ); #ifdef __cplusplus } diff --git a/cpp/csp/engine/c/InputAdapter.h b/cpp/csp/engine/c/InputAdapter.h index 5ac31ba3c..36d519426 100644 --- a/cpp/csp/engine/c/InputAdapter.h +++ b/cpp/csp/engine/c/InputAdapter.h @@ -18,6 +18,7 @@ #ifndef _IN_CSP_ENGINE_C_INPUTADAPTER_H #define _IN_CSP_ENGINE_C_INPUTADAPTER_H +#include #include #include #include @@ -121,7 +122,7 @@ typedef struct CCspPushInputAdapterVTable { * @param vtable Callback table (copied, caller can free after this returns) * @return Handle to the adapter, or NULL on error */ -CCspPushInputAdapterHandle ccsp_push_input_adapter_extern_create( +CSP_C_API_EXPORT CCspPushInputAdapterHandle ccsp_push_input_adapter_extern_create( CCspEngineHandle engine, CCspType type, CCspPushMode push_mode, @@ -133,7 +134,7 @@ CCspPushInputAdapterHandle ccsp_push_input_adapter_extern_create( * Destroy an external push input adapter. * This is typically called by CSP when the graph is destroyed. */ -void ccsp_push_input_adapter_extern_destroy(CCspPushInputAdapterHandle adapter); +CSP_C_API_EXPORT void ccsp_push_input_adapter_extern_destroy(CCspPushInputAdapterHandle adapter); /* ============================================================================ * Push Functions (Thread-Safe) @@ -149,7 +150,7 @@ void ccsp_push_input_adapter_extern_destroy(CCspPushInputAdapterHandle adapter); * @param value Value to push * @param batch Optional batch handle (can be NULL for unbatched push) */ -CCspErrorCode ccsp_push_input_adapter_push_value( +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_value( CCspPushInputAdapterHandle adapter, const CCspValue* value, CCspPushBatchHandle batch @@ -157,47 +158,47 @@ CCspErrorCode ccsp_push_input_adapter_push_value( /* Type-specific push functions (more efficient, avoid CCspValue overhead) */ -CCspErrorCode ccsp_push_input_adapter_push_bool( +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_bool( CCspPushInputAdapterHandle adapter, int8_t value, CCspPushBatchHandle batch); -CCspErrorCode ccsp_push_input_adapter_push_int8( +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_int8( CCspPushInputAdapterHandle adapter, int8_t value, CCspPushBatchHandle batch); -CCspErrorCode ccsp_push_input_adapter_push_uint8( +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_uint8( CCspPushInputAdapterHandle adapter, uint8_t value, CCspPushBatchHandle batch); -CCspErrorCode ccsp_push_input_adapter_push_int16( +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_int16( CCspPushInputAdapterHandle adapter, int16_t value, CCspPushBatchHandle batch); -CCspErrorCode ccsp_push_input_adapter_push_uint16( +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_uint16( CCspPushInputAdapterHandle adapter, uint16_t value, CCspPushBatchHandle batch); -CCspErrorCode ccsp_push_input_adapter_push_int32( +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_int32( CCspPushInputAdapterHandle adapter, int32_t value, CCspPushBatchHandle batch); -CCspErrorCode ccsp_push_input_adapter_push_uint32( +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_uint32( CCspPushInputAdapterHandle adapter, uint32_t value, CCspPushBatchHandle batch); -CCspErrorCode ccsp_push_input_adapter_push_int64( +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_int64( CCspPushInputAdapterHandle adapter, int64_t value, CCspPushBatchHandle batch); -CCspErrorCode ccsp_push_input_adapter_push_uint64( +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_uint64( CCspPushInputAdapterHandle adapter, uint64_t value, CCspPushBatchHandle batch); -CCspErrorCode ccsp_push_input_adapter_push_double( +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_double( CCspPushInputAdapterHandle adapter, double value, CCspPushBatchHandle batch); -CCspErrorCode ccsp_push_input_adapter_push_datetime( +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_datetime( CCspPushInputAdapterHandle adapter, CCspDateTime value, CCspPushBatchHandle batch); -CCspErrorCode ccsp_push_input_adapter_push_timedelta( +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. */ -CCspErrorCode ccsp_push_input_adapter_push_string( +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_string( CCspPushInputAdapterHandle adapter, const char* data, size_t length, CCspPushBatchHandle batch @@ -207,7 +208,7 @@ CCspErrorCode ccsp_push_input_adapter_push_string( * Push a struct value. * The struct is copied internally. */ -CCspErrorCode ccsp_push_input_adapter_push_struct( +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_struct( CCspPushInputAdapterHandle adapter, CCspStructHandle value, CCspPushBatchHandle batch @@ -226,19 +227,19 @@ CCspErrorCode ccsp_push_input_adapter_push_struct( * @param engine Engine handle * @return Handle to the batch, or NULL on error */ -CCspPushBatchHandle ccsp_push_batch_create(CCspEngineHandle engine); +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. */ -void ccsp_push_batch_flush(CCspPushBatchHandle batch); +CSP_C_API_EXPORT void ccsp_push_batch_flush(CCspPushBatchHandle batch); /* * Destroy a push batch. * Any unflushed events are flushed before destruction. */ -void ccsp_push_batch_destroy(CCspPushBatchHandle batch); +CSP_C_API_EXPORT void ccsp_push_batch_destroy(CCspPushBatchHandle batch); /* ============================================================================ * Push Group Management @@ -251,12 +252,12 @@ void ccsp_push_batch_destroy(CCspPushBatchHandle batch); * * @return Handle to the group, or NULL on error */ -CCspPushGroupHandle ccsp_push_group_create(void); +CSP_C_API_EXPORT CCspPushGroupHandle ccsp_push_group_create(void); /* * Destroy a push group. */ -void ccsp_push_group_destroy(CCspPushGroupHandle group); +CSP_C_API_EXPORT void ccsp_push_group_destroy(CCspPushGroupHandle group); #ifdef __cplusplus } diff --git a/cpp/csp/engine/c/OutputAdapter.h b/cpp/csp/engine/c/OutputAdapter.h index 611fefa8f..c08d1e66b 100644 --- a/cpp/csp/engine/c/OutputAdapter.h +++ b/cpp/csp/engine/c/OutputAdapter.h @@ -14,6 +14,7 @@ #ifndef _IN_CSP_ENGINE_C_OUTPUTADAPTER_H #define _IN_CSP_ENGINE_C_OUTPUTADAPTER_H +#include #include #include #include @@ -45,39 +46,39 @@ typedef struct CCspOutputAdapterImpl* CCspOutputAdapterHandle; /* * Check if the input is valid (has ticked at least once) */ -int ccsp_input_is_valid(CCspInputHandle input); +CSP_C_API_EXPORT int ccsp_input_is_valid(CCspInputHandle input); /* * Get the number of ticks available in the input buffer */ -int32_t ccsp_input_num_ticks(CCspInputHandle input); +CSP_C_API_EXPORT int32_t ccsp_input_num_ticks(CCspInputHandle input); /* * Get the type of the input */ -CCspType ccsp_input_get_type(CCspInputHandle 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. */ -CCspErrorCode ccsp_input_get_last_value(CCspInputHandle input, CCspValue* out_value); +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. */ -CCspErrorCode ccsp_input_get_value_at(CCspInputHandle input, int32_t index, CCspValue* out_value); +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. */ -CCspErrorCode ccsp_input_get_time_at(CCspInputHandle input, int32_t index, CCspDateTime* out_time); +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. */ -CCspDateTime ccsp_input_get_last_time(CCspInputHandle input); +CSP_C_API_EXPORT CCspDateTime ccsp_input_get_last_time(CCspInputHandle input); /* ============================================================================ * Engine access functions @@ -86,12 +87,12 @@ CCspDateTime ccsp_input_get_last_time(CCspInputHandle input); /* * Get current engine time */ -CCspDateTime ccsp_engine_now(CCspEngineHandle engine); +CSP_C_API_EXPORT CCspDateTime ccsp_engine_now(CCspEngineHandle engine); /* * Get current cycle count */ -uint64_t ccsp_engine_cycle_count(CCspEngineHandle engine); +CSP_C_API_EXPORT uint64_t ccsp_engine_cycle_count(CCspEngineHandle engine); /* ============================================================================ * Output Adapter Callbacks (VTable) @@ -162,7 +163,7 @@ typedef struct CCspOutputAdapterVTable { * Note: The returned handle should be returned to Python via capsule, * which will then be registered with the CSP graph. */ -CCspOutputAdapterHandle ccsp_output_adapter_extern_create( +CSP_C_API_EXPORT CCspOutputAdapterHandle ccsp_output_adapter_extern_create( CCspEngineHandle engine, CCspType input_type, const CCspOutputAdapterVTable* vtable @@ -173,7 +174,7 @@ CCspOutputAdapterHandle ccsp_output_adapter_extern_create( * This is typically called by CSP when the graph is destroyed. * The destroy callback in the vtable will be invoked. */ -void ccsp_output_adapter_extern_destroy(CCspOutputAdapterHandle adapter); +CSP_C_API_EXPORT void ccsp_output_adapter_extern_destroy(CCspOutputAdapterHandle adapter); /* ============================================================================ * Convenience functions for common output patterns @@ -183,29 +184,29 @@ void ccsp_output_adapter_extern_destroy(CCspOutputAdapterHandle adapter); * Get last value as string (convenience for string outputs). * Returns borrowed pointer valid until next execute() call. */ -CCspErrorCode ccsp_input_get_last_string(CCspInputHandle input, +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) */ -CCspErrorCode ccsp_input_get_last_int64(CCspInputHandle input, int64_t* out_value); +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) */ -CCspErrorCode ccsp_input_get_last_double(CCspInputHandle input, double* out_value); +CSP_C_API_EXPORT CCspErrorCode ccsp_input_get_last_double(CCspInputHandle input, double* out_value); /* * Get last value as bool (convenience for bool outputs) */ -CCspErrorCode ccsp_input_get_last_bool(CCspInputHandle input, int8_t* out_value); +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) */ -CCspErrorCode ccsp_input_get_last_datetime(CCspInputHandle input, CCspDateTime* out_value); +CSP_C_API_EXPORT CCspErrorCode ccsp_input_get_last_datetime(CCspInputHandle input, CCspDateTime* out_value); #ifdef __cplusplus } 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..c0d662886 --- /dev/null +++ b/cpp/csp/python/PyCApiAdapters.cpp @@ -0,0 +1,181 @@ +/* + * 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/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/CMakeLists.txt b/cpp/csp/python/c/CMakeLists.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/csp/adapters/c_example.py b/csp/adapters/c_example.py deleted file mode 100644 index 291e8f3ac..000000000 --- a/csp/adapters/c_example.py +++ /dev/null @@ -1,261 +0,0 @@ -""" -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. It provides: -- ExampleAdapterManager: Coordinates multiple adapters, manages lifecycle -- Example input adapter: Generates incrementing integers -- Example output adapter: Prints values to stdout -""" - -from typing import TypeVar - -import csp -from csp import ts -from csp.impl.wiring import input_adapter_def, output_adapter_def -from csp.lib import _exampleadapterimpl - -T = TypeVar("T") - - -class ExampleAdapterManager: - """ - Example adapter manager that demonstrates the C API adapter manager pattern. - - Adapter managers coordinate the lifecycle of related adapters: - - Start: Called when the CSP graph starts - - Stop: Called when the CSP graph stops - - Sim time slicing: For replay/simulation mode - - Usage: - manager = ExampleAdapterManager(prefix="[MyApp]") - - @csp.graph - def my_graph(): - data = manager.subscribe(int, interval_ms=100) - manager.publish(data) - """ - - def __init__(self, prefix: str = None): - """ - Initialize the adapter manager. - - Args: - prefix: Optional prefix for output messages - """ - self._prefix = prefix - self._properties = { - "prefix": prefix or "", - } - - def subscribe( - self, - ts_type: type, - interval_ms: int = 100, - push_mode: csp.PushMode = csp.PushMode.NON_COLLAPSING, - ): - """ - Create an 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 - """ - properties = { - "interval_ms": interval_ms, - } - return _example_input_adapter_def(self, ts_type, properties, push_mode=push_mode) - - def publish(self, x: ts["T"]): - """ - Create an output adapter that prints values to stdout. - - Args: - x: The timeseries to publish - """ - return _example_output_adapter_def(self, x) - - def __hash__(self): - return hash(self._prefix) - - def __eq__(self, other): - return isinstance(other, ExampleAdapterManager) and self._prefix == other._prefix - - def _create(self, engine, memo): - """ - Create the underlying C++ adapter manager. - - This method is called by CSP when the graph is built. - """ - return _exampleadapterimpl._example_adapter_manager(engine, self._properties) - - -# Adapter definitions using the adapter manager pattern -_example_input_adapter_def = input_adapter_def( - "example_input_adapter", - _exampleadapterimpl._example_input_adapter, - ts["T"], - ExampleAdapterManager, - typ="T", - properties=dict, -) - -_example_output_adapter_def = output_adapter_def( - "example_output_adapter", - _exampleadapterimpl._example_output_adapter, - ExampleAdapterManager, - input=ts["T"], -) - - -# Standalone adapters (without manager) for simple use cases -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. - - This is a simpler API when you don't need a full adapter 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 _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. - - This is a simpler API when you don't need a full adapter manager. - - Args: - x: The timeseries to publish - prefix: Optional prefix for output messages - """ - return _standalone_output_adapter_def(x, prefix=prefix) - - -# Standalone adapter definitions (no manager required) -_standalone_input_adapter_def = input_adapter_def( - "example_input_adapter_standalone", - _exampleadapterimpl._example_input_adapter, - ts["T"], - typ="T", - interval_ms=int, -) - -_standalone_output_adapter_def = output_adapter_def( - "example_output_adapter_standalone", - _exampleadapterimpl._example_output_adapter, - input=ts["T"], - prefix=str, -) - - -# ============================================================================ -# Struct C API Utilities (Phase 6 - Struct Access) -# ============================================================================ - -# The following functions demonstrate the C Struct API pattern. -# The actual struct inspection is done in C via CspStruct.h -# -# C API for Struct Access: -# - ccsp_struct_meta_name(meta) -> const char* -# - ccsp_struct_meta_field_count(meta) -> size_t -# - ccsp_struct_meta_field_by_index(meta, index) -> CCspStructFieldHandle -# - ccsp_struct_meta_field_by_name(meta, name) -> CCspStructFieldHandle -# - ccsp_struct_field_name(field) -> const char* -# - ccsp_struct_field_type(field) -> CCspType -# - ccsp_struct_field_is_optional(field) -> int -# - ccsp_struct_get_*(s, field, &out_value) -> CCspErrorCode -# - ccsp_struct_set_*(s, field, value) -> CCspErrorCode -# - ccsp_struct_create(meta) -> CCspStructHandle -# - ccsp_struct_destroy(s) -# - ccsp_struct_copy(s) -> CCspStructHandle - - -def inspect_struct_type(struct_type: type) -> dict: - """ - Inspect a csp.Struct type's fields using the C struct API. - - This demonstrates using the C Struct API to access type metadata. - The actual implementation calls into C code via _exampleadapterimpl. - - Note: This function requires the struct type to have a _struct_meta_capsule - attribute, which is set up for struct types that support C API access. - - Args: - struct_type: A csp.Struct subclass - - Returns: - A dict containing: - - name: The struct type name - - field_count: Number of fields - - is_strict: Whether the struct is strict - - fields: List of field info dicts (name, type, is_optional) - - Raises: - TypeError: If struct_type is not a valid struct type with C API support - - Example: - >>> import csp - >>> class MyStruct(csp.Struct): - ... x: int - ... y: float - ... name: str - ... - >>> # Note: This requires _struct_meta_capsule to be set up - >>> # info = inspect_struct_type(MyStruct) - """ - return _exampleadapterimpl._example_inspect_struct_type(struct_type) - - -# Type mapping for CCspType values (matches CspType.h enum) -CCSP_TYPE_NAMES = { - 0: "UNKNOWN", - 1: "BOOL", - 2: "INT8", - 3: "UINT8", - 4: "INT16", - 5: "UINT16", - 6: "INT32", - 7: "UINT32", - 8: "INT64", - 9: "UINT64", - 10: "DOUBLE", - 11: "DATETIME", - 12: "TIMEDELTA", - 13: "DATE", - 14: "TIME", - 15: "ENUM", - 16: "STRING", - 17: "STRUCT", - 18: "ARRAY", - 19: "DIALECT_GENERIC", -} - - -def get_type_name(ccsp_type: int) -> str: - """ - Get the string name for a CCspType enum value. - - Args: - ccsp_type: The integer type code from the C API - - Returns: - The string name of the type (e.g., "INT64", "STRING") - """ - return CCSP_TYPE_NAMES.get(ccsp_type, f"UNKNOWN({ccsp_type})") - diff --git a/docs/C_API_ROADMAP.md b/docs/C_API_ROADMAP.md deleted file mode 100644 index 996623f87..000000000 --- a/docs/C_API_ROADMAP.md +++ /dev/null @@ -1,670 +0,0 @@ -# CSP C API Roadmap - -## Overview - -This document outlines the roadmap for completing the C API that enables CSP adapters (Kafka, Parquet, WebSocket) to be compiled and distributed separately from CSP core. - -## Current State Analysis - -### What Has Been Done - -| File | Status | Description | -|------|--------|-------------| -| `cpp/csp/engine/c/CspType.h` | ✅ Basic | CCspType enum with all basic type definitions | -| `cpp/csp/engine/c/OutputAdapter.h` | 🔶 Stub | Empty opaque OutputAdapter struct | -| `cpp/csp/engine/OutputAdapterExtern.h` | 🔶 Stub | C++ wrapper class, no implementation | -| `cpp/csp/python/c/PyOutputAdapter.h` | ✅ Partial | Python capsule creation for C adapters | -| `cpp/csp/adapters/c/example/*` | 🔶 Stub | Empty example adapter files | -| `cpp/csp/python/adapters/c/exampleadapterimpl.c` | 🔶 Stub | Basic Python module structure | -| `csp/adapters/c_example.py` | ✅ Complete | Python wiring definitions | - -### Adapter Dependencies Analysis - -Analyzing the Kafka, Parquet, and WebSocket adapters reveals these CSP core dependencies: - -#### Output Adapter Dependencies -- `Engine*` - for scheduling -- `TimeSeriesProvider::lastValueTyped()` - reading input values -- `CspType` - type information -- `Dictionary` - configuration properties -- `Struct` - structured data access -- `DateTime/TimeDelta` - time handling - -#### Input Adapter Dependencies -- `Engine*` - for scheduling -- `PushInputAdapter::pushTick()` - pushing data into graph -- `PushBatch/PushGroup` - batching and synchronization -- `CspType` - type information -- `StructMeta/StructField` - struct metadata access -- `DateTime/TimeDelta` - time handling - -#### Adapter Manager Dependencies -- `AdapterManager` base class - lifecycle (start/stop) -- `RootEngine` - root engine access -- `StatusAdapter` - status reporting -- `Dictionary` - configuration - ---- - -## Phase 1: Core C Interface Types - -**Goal:** Define ABI-stable C representations of all CSP types - -### 1.1 Basic Value Types (`cpp/csp/engine/c/CspValue.h`) - -```c -// Already defined: CCspType enum - -// Value container for passing data across ABI -typedef struct { - CCspType type; - union { - int8_t bool_val; // CCSP_TYPE_BOOL - 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; - struct { const char* data; size_t length; } string_val; - int64_t datetime_val; // nanoseconds since epoch - int64_t timedelta_val; // nanoseconds - int32_t date_val; // days since epoch - int64_t time_val; // nanoseconds since midnight - int32_t enum_val; // enum ordinal - void* struct_ptr; // opaque pointer + metadata - void* dialect_ptr; // opaque dialect object - struct { void* data; size_t length; CCspType elem_type; } array_val; - }; -} CCspValue; -``` - -### 1.2 Time Types (`cpp/csp/engine/c/CspTime.h`) - -```c -typedef int64_t CCspDateTime; // nanoseconds since Unix epoch -typedef int64_t CCspTimeDelta; // nanoseconds duration -typedef int32_t CCspDate; // days since Unix epoch -typedef int64_t CCspTime; // nanoseconds since midnight - -// Conversion functions -CCspDateTime ccsp_datetime_now(); -CCspDateTime ccsp_datetime_from_parts(int year, int month, int day, int hour, int min, int sec, int nsec); -CCspTimeDelta ccsp_timedelta_from_seconds(double seconds); -``` - -### 1.3 String Type (`cpp/csp/engine/c/CspString.h`) - -```c -typedef struct { - const char* data; - size_t length; - int is_owned; // 1 if the C side owns the memory -} CCspString; - -CCspString ccsp_string_create(const char* data, size_t length); -void ccsp_string_free(CCspString* str); -``` - -### 1.4 Type Metadata (`cpp/csp/engine/c/CspTypeMeta.h`) - -```c -// Opaque handles -typedef struct CCspTypeInfo* CCspTypeHandle; -typedef struct CCspStructMeta* CCspStructMetaHandle; -typedef struct CCspStructField* CCspStructFieldHandle; - -// Type introspection -CCspType ccsp_type_get_kind(CCspTypeHandle type); -int ccsp_type_is_array(CCspTypeHandle type); -CCspTypeHandle ccsp_type_array_elem_type(CCspTypeHandle type); - -// Struct introspection -const char* ccsp_struct_meta_name(CCspStructMetaHandle meta); -size_t ccsp_struct_meta_field_count(CCspStructMetaHandle meta); -CCspStructFieldHandle ccsp_struct_meta_field_at(CCspStructMetaHandle meta, size_t index); -CCspStructFieldHandle ccsp_struct_meta_field_by_name(CCspStructMetaHandle meta, const char* name); - -// Field introspection -const char* ccsp_struct_field_name(CCspStructFieldHandle field); -CCspTypeHandle ccsp_struct_field_type(CCspStructFieldHandle field); -``` - ---- - -## Phase 2: Output Adapter C Interface - -**Goal:** Complete C interface for output adapters - -### 2.1 Output Adapter Interface (`cpp/csp/engine/c/OutputAdapter.h`) - -```c -// Forward declarations -typedef struct CCspEngine CCspEngine; -typedef struct CCspTimeSeriesProvider CCspTimeSeriesProvider; - -// Output adapter lifecycle callbacks (implemented by external adapter) -typedef struct { - void* user_data; - - // Called when adapter should execute (input has new value) - void (*execute)(void* user_data, CCspTimeSeriesProvider* input, CCspDateTime now); - - // Called on graph start - void (*start)(void* user_data, CCspDateTime start_time, CCspDateTime end_time); - - // Called on graph stop/cleanup - void (*stop)(void* user_data); - - // Destructor - void (*destroy)(void* user_data); - -} CCspOutputAdapterCallbacks; - -// Registration -typedef struct CCspOutputAdapter CCspOutputAdapter; - -CCspOutputAdapter* ccsp_output_adapter_create( - CCspEngine* engine, - CCspTypeHandle input_type, - CCspOutputAdapterCallbacks callbacks -); - -void ccsp_output_adapter_destroy(CCspOutputAdapter* adapter); -``` - -### 2.2 Input Value Access (`cpp/csp/engine/c/CspInput.h`) - -```c -// Get last value from input time series -int ccsp_input_get_value(CCspTimeSeriesProvider* input, CCspValue* out_value); - -// Check if input is valid (has ticked) -int ccsp_input_is_valid(CCspTimeSeriesProvider* input); - -// Get tick count -int ccsp_input_num_ticks(CCspTimeSeriesProvider* input); - -// Historical access -int ccsp_input_get_value_at_index(CCspTimeSeriesProvider* input, int32_t index, CCspValue* out_value); -CCspDateTime ccsp_input_get_time_at_index(CCspTimeSeriesProvider* input, int32_t index); -``` - -### 2.3 C++ Wrapper Implementation (`cpp/csp/engine/OutputAdapterExtern.cpp`) - -```cpp -class OutputAdapterExtern : public OutputAdapter { -public: - OutputAdapterExtern(Engine* engine, const CspTypePtr& type, - CCspOutputAdapterCallbacks callbacks); - ~OutputAdapterExtern() override; - - void executeImpl() override { - CCspTimeSeriesProvider* c_input = wrapTimeSeriesProvider(input()); - m_callbacks.execute(m_callbacks.user_data, c_input, now().asNanoseconds()); - } - - void start() override { - if (m_callbacks.start) { - m_callbacks.start(m_callbacks.user_data, - m_startTime.asNanoseconds(), - m_endTime.asNanoseconds()); - } - } - - void stop() override { - if (m_callbacks.stop) { - m_callbacks.stop(m_callbacks.user_data); - } - } - -private: - CCspOutputAdapterCallbacks m_callbacks; -}; -``` - ---- - -## Phase 3: Input Adapter C Interface - -**Goal:** Complete C interface for push input adapters - -### 3.1 Push Input Adapter Interface (`cpp/csp/engine/c/InputAdapter.h`) - -```c -typedef struct CCspPushInputAdapter CCspPushInputAdapter; -typedef struct CCspPushBatch CCspPushBatch; -typedef struct CCspPushGroup CCspPushGroup; - -// Input adapter lifecycle callbacks (implemented by external adapter) -typedef struct { - void* user_data; - - // Called on graph start - void (*start)(void* user_data, CCspDateTime start_time, CCspDateTime end_time); - - // Called on graph stop - void (*stop)(void* user_data); - - // Destructor - void (*destroy)(void* user_data); - -} CCspPushInputAdapterCallbacks; - -// Creation -CCspPushInputAdapter* ccsp_push_input_adapter_create( - CCspEngine* engine, - CCspTypeHandle type, - int push_mode, // 0=LAST_VALUE, 1=NON_COLLAPSING, 2=BURST - CCspPushGroup* group, // can be NULL - CCspPushInputAdapterCallbacks callbacks -); - -void ccsp_push_input_adapter_destroy(CCspPushInputAdapter* adapter); - -// Push data into the graph (thread-safe, called from adapter thread) -void ccsp_push_input_adapter_push_bool(CCspPushInputAdapter* adapter, int8_t value, CCspPushBatch* batch); -void ccsp_push_input_adapter_push_int64(CCspPushInputAdapter* adapter, int64_t value, CCspPushBatch* batch); -void ccsp_push_input_adapter_push_double(CCspPushInputAdapter* adapter, double value, CCspPushBatch* batch); -void ccsp_push_input_adapter_push_string(CCspPushInputAdapter* adapter, const char* data, size_t len, CCspPushBatch* batch); -void ccsp_push_input_adapter_push_datetime(CCspPushInputAdapter* adapter, CCspDateTime value, CCspPushBatch* batch); -void ccsp_push_input_adapter_push_struct(CCspPushInputAdapter* adapter, void* struct_ptr, CCspPushBatch* batch); -// ... additional type-specific push functions - -// Generic push with CCspValue -void ccsp_push_input_adapter_push_value(CCspPushInputAdapter* adapter, const CCspValue* value, CCspPushBatch* batch); - -// Batch management -CCspPushBatch* ccsp_push_batch_create(CCspEngine* engine); -void ccsp_push_batch_flush(CCspPushBatch* batch); -void ccsp_push_batch_destroy(CCspPushBatch* batch); - -// Group management -CCspPushGroup* ccsp_push_group_create(); -void ccsp_push_group_destroy(CCspPushGroup* group); -``` - -### 3.2 C++ Wrapper Implementation (`cpp/csp/engine/PushInputAdapterExtern.cpp`) - -```cpp -class PushInputAdapterExtern : public PushInputAdapter { -public: - PushInputAdapterExtern(Engine* engine, const CspTypePtr& type, - PushMode pushMode, PushGroup* group, - CCspPushInputAdapterCallbacks callbacks); - ~PushInputAdapterExtern() override; - - void start(DateTime start, DateTime end) override { - if (m_callbacks.start) { - m_callbacks.start(m_callbacks.user_data, - start.asNanoseconds(), - end.asNanoseconds()); - } - } - - void stop() override { - if (m_callbacks.stop) { - m_callbacks.stop(m_callbacks.user_data); - } - } - - // C API will call these methods - template - void pushFromC(const T& value, PushBatch* batch) { - this->pushTick(value, batch); - } - -private: - CCspPushInputAdapterCallbacks m_callbacks; -}; -``` - ---- - -## Phase 4: Adapter Manager C Interface - -**Goal:** Enable external adapter managers with proper lifecycle - -### 4.1 Adapter Manager Interface (`cpp/csp/engine/c/AdapterManager.h`) - -```c -typedef struct CCspAdapterManager CCspAdapterManager; - -// Adapter manager lifecycle callbacks -typedef struct { - void* user_data; - - // Required: name of the adapter manager - const char* (*get_name)(void* user_data); - - // Called when graph starts - void (*start)(void* user_data, CCspDateTime start_time, CCspDateTime end_time); - - // Called when graph stops - void (*stop)(void* user_data); - - // For sim adapters: return next sim time or 0 if none - CCspDateTime (*process_next_sim_time_slice)(void* user_data, CCspDateTime current_time); - - // Destructor - void (*destroy)(void* user_data); - -} CCspAdapterManagerCallbacks; - -// Creation -CCspAdapterManager* ccsp_adapter_manager_create( - CCspEngine* engine, - CCspAdapterManagerCallbacks callbacks -); - -void ccsp_adapter_manager_destroy(CCspAdapterManager* adapter_manager); - -// Get root engine for scheduling -CCspEngine* ccsp_adapter_manager_engine(CCspAdapterManager* manager); -``` - ---- - -## Phase 5: Dictionary/Configuration C Interface - -**Goal:** Enable passing configuration to external adapters - -### 5.1 Dictionary Interface (`cpp/csp/engine/c/CspDictionary.h`) - -```c -typedef struct CCspDictionary CCspDictionary; - -// Creation and destruction -CCspDictionary* ccsp_dictionary_create(); -void ccsp_dictionary_destroy(CCspDictionary* dict); - -// Check existence -int ccsp_dictionary_exists(const CCspDictionary* dict, const char* key); - -// Getters (return 0 on success, non-zero on error) -int ccsp_dictionary_get_bool(const CCspDictionary* dict, const char* key, int* out_value); -int ccsp_dictionary_get_int64(const CCspDictionary* dict, const char* key, int64_t* out_value); -int ccsp_dictionary_get_double(const CCspDictionary* dict, const char* key, double* out_value); -int ccsp_dictionary_get_string(const CCspDictionary* dict, const char* key, const char** out_data, size_t* out_len); -int ccsp_dictionary_get_datetime(const CCspDictionary* dict, const char* key, CCspDateTime* out_value); -int ccsp_dictionary_get_dict(const CCspDictionary* dict, const char* key, CCspDictionary** out_dict); - -// Getters with defaults -int64_t ccsp_dictionary_get_int64_or(const CCspDictionary* dict, const char* key, int64_t default_val); -double ccsp_dictionary_get_double_or(const CCspDictionary* dict, const char* key, double default_val); -const char* ccsp_dictionary_get_string_or(const CCspDictionary* dict, const char* key, const char* default_val); - -// Iteration -size_t ccsp_dictionary_size(const CCspDictionary* dict); -typedef struct CCspDictIterator CCspDictIterator; -CCspDictIterator* ccsp_dictionary_iter_create(const CCspDictionary* dict); -int ccsp_dictionary_iter_next(CCspDictIterator* iter, const char** out_key, CCspValue* out_value); -void ccsp_dictionary_iter_destroy(CCspDictIterator* iter); -``` - ---- - -## Phase 6: Struct Access C Interface - -**Goal:** Enable reading/writing struct fields from C - -### 6.1 Struct Access (`cpp/csp/engine/c/CspStruct.h`) - -```c -typedef struct CCspStruct CCspStruct; - -// Create a new struct instance -CCspStruct* ccsp_struct_create(CCspStructMetaHandle meta); -void ccsp_struct_destroy(CCspStruct* s); - -// Clone a struct -CCspStruct* ccsp_struct_clone(const CCspStruct* s); - -// Field access by name (slower but more convenient) -int ccsp_struct_get_field(const CCspStruct* s, const char* field_name, CCspValue* out_value); -int ccsp_struct_set_field(CCspStruct* s, const char* field_name, const CCspValue* value); - -// Field access by handle (faster, for hot paths) -int ccsp_struct_get_field_by_handle(const CCspStruct* s, CCspStructFieldHandle field, CCspValue* out_value); -int ccsp_struct_set_field_by_handle(CCspStruct* s, CCspStructFieldHandle field, const CCspValue* value); - -// Check if field is set -int ccsp_struct_is_field_set(const CCspStruct* s, CCspStructFieldHandle field); - -// Validation -int ccsp_struct_validate(const CCspStruct* s); -``` - ---- - -## Phase 7: Error Handling - -**Goal:** Consistent error reporting across the ABI - -### 7.1 Error Handling (`cpp/csp/engine/c/CspError.h`) - -```c -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_RUNTIME, - CCSP_ERROR_VALUE, - CCSP_ERROR_UNKNOWN -} CCspError; - -// Thread-local error state -CCspError ccsp_get_last_error(); -const char* ccsp_get_last_error_message(); -void ccsp_clear_error(); - -// Set error (for adapter implementations) -void ccsp_set_error(CCspError code, const char* message); -``` - ---- - -## Phase 8: Engine Access - -**Goal:** Allow adapters to interact with the engine - -### 8.1 Engine Interface (`cpp/csp/engine/c/CspEngine.h`) - -```c -// Get current time -CCspDateTime ccsp_engine_now(CCspEngine* engine); - -// Get cycle count -uint64_t ccsp_engine_cycle_count(CCspEngine* engine); - -// Schedule a callback -typedef void (*CCspCallback)(void* user_data); -void ccsp_engine_schedule_callback(CCspEngine* engine, CCspDateTime time, CCspCallback callback, void* user_data); - -// Status reporting -typedef struct CCspStatusAdapter CCspStatusAdapter; -void ccsp_status_adapter_push_status(CCspStatusAdapter* adapter, int level, const char* message); -``` - ---- - -## Phase 9: Build System and Packaging - -**Goal:** Enable separate compilation and distribution - -### 9.1 CMake Configuration - -```cmake -# cpp/csp/engine/c/CMakeLists.txt -add_library(csp_c_api SHARED - CspValue.cpp - CspTime.cpp - CspDictionary.cpp - CspStruct.cpp - CspError.cpp - CspEngine.cpp - OutputAdapter.cpp - InputAdapter.cpp - AdapterManager.cpp -) - -target_include_directories(csp_c_api PUBLIC - $ - $ -) - -# Install headers and library -install(TARGETS csp_c_api - EXPORT csp_c_api-targets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin - INCLUDES DESTINATION include -) - -install(DIRECTORY include/csp/c - DESTINATION include/csp -) - -# Generate pkg-config file -configure_file(csp_c_api.pc.in csp_c_api.pc @ONLY) -install(FILES ${CMAKE_BINARY_DIR}/csp_c_api.pc DESTINATION lib/pkgconfig) -``` - -### 9.2 External Adapter Build Example - -```cmake -# External adapter CMakeLists.txt -find_package(csp_c_api REQUIRED) - -add_library(my_kafka_adapter SHARED - MyKafkaAdapter.c -) - -target_link_libraries(my_kafka_adapter - csp_c_api::csp_c_api - rdkafka -) -``` - ---- - -## Phase 10: Reference Implementation - -**Goal:** Implement one adapter (e.g., simple WebSocket output) to validate the API - -### 10.1 Complete Example Implementation - -See [cpp/csp/adapters/c/example/](../cpp/csp/adapters/c/example/) for a working example that demonstrates: - -1. Output adapter that logs to console -2. Push input adapter that reads from a simple source -3. Adapter manager with proper lifecycle - ---- - -## Implementation Priority - -### Must Have (Phase 1-4) -1. **Phase 1**: Core C types - foundation for everything -2. **Phase 2**: Output adapter - simplest case, validates design -3. **Phase 4**: Adapter manager - required for any real adapter -4. **Phase 5**: Dictionary - configuration is essential - -### Should Have (Phase 5-7) -5. **Phase 3**: Input adapter - needed for Kafka/WebSocket input -6. **Phase 6**: Struct access - needed for structured data -7. **Phase 7**: Error handling - production quality - -### Nice to Have (Phase 8-10) -8. **Phase 8**: Engine access - advanced functionality -9. **Phase 9**: Build system - distribution -10. **Phase 10**: Reference implementation - documentation - ---- - -## Estimated Effort - -| Phase | Effort (days) | Dependencies | -|-------|---------------|--------------| -| Phase 1 | 3-5 | None | -| Phase 2 | 5-7 | Phase 1 | -| Phase 3 | 5-7 | Phase 1 | -| Phase 4 | 3-5 | Phase 1, 2 | -| Phase 5 | 3-5 | Phase 1 | -| Phase 6 | 5-7 | Phase 1, 5 | -| Phase 7 | 2-3 | None | -| Phase 8 | 3-5 | Phase 1, 4 | -| Phase 9 | 2-3 | All above | -| Phase 10 | 5-7 | All above | - -**Total: ~35-50 days** - ---- - -## Success Criteria - -The C API is complete when: - -1. ✅ **Kafka adapter** can be compiled as a separate shared library that: - - Receives messages via push input adapter - - Sends messages via output adapter - - Manages its lifecycle via adapter manager - - Uses Dictionary for configuration - -2. ✅ **Parquet adapter** can be compiled separately with: - - Output adapter for writing - - Input adapter for reading (sim mode) - - Struct field access for column mapping - -3. ✅ **WebSocket adapter** can be compiled separately with: - - Bidirectional communication - - Connection lifecycle management - - String/binary message handling - -4. ✅ **ABI Stability**: - - Adapters compiled with version N work with CSP version N+1 - - No C++ types cross the ABI boundary - - All pointers are opaque handles - -5. ✅ **Documentation**: - - Complete API reference - - Migration guide for existing adapters - - Example code for new adapters - ---- - -## Open Questions - -1. **Struct Metadata Sharing**: How do we share struct definitions between CSP and external adapters? - - Option A: Adapters define their own structs, CSP marshals - - Option B: CSP provides struct creation API - - Option C: Use a common serialization format (e.g., Arrow) - -2. **Memory Ownership**: Who owns memory for strings, arrays, structs passed across the ABI? - - Proposed: CSP owns internal data; adapters must copy if they need to retain - -3. **Thread Safety**: Which functions are thread-safe? - - Proposed: Only push_* functions are thread-safe; all others require single-thread access - -4. **Versioning**: How do we version the C API? - - Proposed: Version number in header, runtime check function - -5. **Python Integration**: How do external adapters integrate with Python? - - Current: PyCapsule mechanism looks correct - - Need: Complete the Python wrapper layer - ---- - -## Next Steps - -1. Review this roadmap with stakeholders -2. Finalize decisions on open questions -3. Begin Phase 1 implementation -4. Set up CI for testing C API compatibility 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 index 7d31503a2..251fcb2b0 100644 --- a/docs/wiki/api-references/C-APIs.md +++ b/docs/wiki/api-references/C-APIs.md @@ -17,6 +17,7 @@ This document provides a complete reference for the CSP C API, which allows adap - [Engine Access](#engine-access) - [Input Access](#input-access) - [Adapter Managers](#adapter-managers) +- [Python Bridge Functions](#python-bridge-functions) ______________________________________________________________________ @@ -486,7 +487,7 @@ CCspErrorCode ccsp_dictionary_get_datetime(CCspDictionaryHandle dict, const char 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, +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) @@ -544,14 +545,14 @@ 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; @@ -722,20 +723,20 @@ 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: { @@ -754,7 +755,7 @@ void process_struct(CCspStructHandle s) // ... handle other types } } - + // Direct access by name double price; if (ccsp_struct_get_double_by_name(s, "price", &price) == CCSP_OK) { @@ -770,16 +771,16 @@ void create_order(CCspStructMetaHandle order_meta) // 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); } @@ -1129,6 +1130,166 @@ CCspErrorCode ccsp_managed_sim_input_adapter_push_datetime( ______________________________________________________________________ +## 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 | @@ -1155,4 +1316,5 @@ ______________________________________________________________________ ## See Also - [Write C API Adapters](../how-tos/Write-C-API-Adapters.md) - How-to guide -- [Example Adapters](../../cpp/csp/adapters/c/example/) - Reference implementations +- [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/how-tos/Write-C-API-Adapters.md b/docs/wiki/how-tos/Write-C-API-Adapters.md index 7803a9310..2620104dc 100644 --- a/docs/wiki/how-tos/Write-C-API-Adapters.md +++ b/docs/wiki/how-tos/Write-C-API-Adapters.md @@ -21,7 +21,10 @@ - [Building and Linking](#building-and-linking) - [Python Integration](#python-integration) - [Create Python Bindings (C code)](#create-python-bindings-c-code) - - [Create Python Wrapper](#create-python-wrapper) + - [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) @@ -500,19 +503,29 @@ make ### Python Integration -To use your C adapter from Python, you need to create a Python extension module that wraps the C functions. +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" -static PyObject* create_log_adapter_py(PyObject* self, PyObject* args) +// Output adapter - returns a capsule +static PyObject* create_log_adapter_py(PyObject* self, PyObject* args, PyObject* kwargs) { - const char* prefix = NULL; - if (!PyArg_ParseTuple(args, "|s", &prefix)) { + static char* kwlist[] = {"prefix", NULL}; + const char* prefix = ""; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|s", kwlist, &prefix)) { return NULL; } @@ -522,11 +535,32 @@ static PyObject* create_log_adapter_py(PyObject* self, PyObject* args) return NULL; } - return ccsp_py_create_output_adapter_capsule(&vtable); + // 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", create_log_adapter_py, METH_VARARGS, "Create log adapter"}, + {"_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} }; @@ -539,31 +573,228 @@ PyMODINIT_FUNC PyInit_my_adapter(void) { } ``` -#### Create Python Wrapper +#### 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.wiring import output_adapter_def -from csp.lib import my_adapter as _impl +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", - _impl._log_adapter, + _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 +import csp +from datetime import datetime, timedelta +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=datetime.utcnow(), endtime=timedelta(seconds=10)) +``` + #### Use in Your Graph ```python import csp -from my_adapter import LogAdapter +from datetime import datetime, timedelta +from my_adapter import my_input, LogAdapter @csp.graph def my_graph(): - data = csp.timer(timedelta(seconds=1), "tick") + data = my_input(int, interval_ms=100) LogAdapter(data, prefix="[MyApp] ") csp.run(my_graph, starttime=datetime.utcnow(), endtime=timedelta(seconds=10)) @@ -574,4 +805,4 @@ csp.run(my_graph, starttime=datetime.utcnow(), endtime=timedelta(seconds=10)) - [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 -- [Example: C API Adapter](../../examples/04_writing_adapters/e8_c_api_adapter.py) - Working example +- [C API Adapter Example](../../../examples/05_cpp/4_c_api_adapter/) - Working example diff --git a/examples/04_writing_adapters/e8_c_api_adapter.py b/examples/04_writing_adapters/e8_c_api_adapter.py deleted file mode 100644 index 8c0d1b97b..000000000 --- a/examples/04_writing_adapters/e8_c_api_adapter.py +++ /dev/null @@ -1,152 +0,0 @@ -""" -Example demonstrating the CSP C API adapters. - -This example shows how to use adapters written in C via the C API: -- ExampleOutputAdapter: Prints received values to stdout -- ExamplePushInputAdapter: Generates incrementing integers in a background thread - -These adapters are compiled separately from CSP and communicate via the -ABI-stable C interface defined in cpp/csp/engine/c/*.h headers. - -See docs/wiki/how-tos/Write-C-API-Adapters.md for how to write your own. -""" - -from datetime import timedelta - -import csp -from csp import ts -from csp.adapters.c_example import _example_input_adapter_def, _example_output_adapter_def -from csp.utils.datetime import utc_now - - -# Example 1: Using the C output adapter to print values -@csp.graph -def output_adapter_example(): - """ - Demonstrates the C output adapter which receives values and prints them. - The adapter is implemented in C in cpp/csp/adapters/c/example/ExampleOutputAdapter.c - """ - # Create a simple curve of values - data = csp.curve( - typ=int, - data=[ - (timedelta(milliseconds=100), 1), - (timedelta(milliseconds=200), 2), - (timedelta(milliseconds=300), 3), - (timedelta(milliseconds=400), 4), - (timedelta(milliseconds=500), 5), - ], - ) - - # Also print using Python's csp.print for comparison - csp.print("Python print", data) - - # Use the C output adapter - it will print values with a prefix - _example_output_adapter_def(data) - - -# Example 2: Using the C push input adapter to receive values from C -@csp.graph -def input_adapter_example() -> ts[int]: - """ - Demonstrates the C push input adapter which generates values in a background thread. - The adapter is implemented in C in cpp/csp/adapters/c/example/ExamplePushInputAdapter.c - """ - # Create input adapter that generates integers every 100ms - data = _example_input_adapter_def(typ=int, properties={"interval_ms": 100}) - - # Print the received values - csp.print("From C adapter", data) - - return data - - -# Example 3: Both adapters together - C input -> CSP -> C output -@csp.graph -def full_pipeline_example(): - """ - Demonstrates a full pipeline: C adapter produces data, CSP processes it, - and another C adapter consumes it. - """ - # Receive integers from C background thread - raw_data = _example_input_adapter_def(typ=int, properties={"interval_ms": 50}) - - # Process in CSP - double the values - processed = raw_data * 2 - - # Print using Python for debugging - csp.print("raw", raw_data) - csp.print("processed", processed) - - # Send to C output adapter - _example_output_adapter_def(processed) - - -def run_output_adapter_example(): - """Run the output adapter example.""" - print("\n" + "=" * 60) - print("Example 1: C Output Adapter") - print("=" * 60) - print("Running graph with C output adapter...") - print() - - csp.run( - output_adapter_example, - starttime=utc_now(), - endtime=timedelta(seconds=1), - realtime=False, # Sim mode - ) - - -def run_input_adapter_example(): - """Run the input adapter example.""" - print("\n" + "=" * 60) - print("Example 2: C Push Input Adapter") - print("=" * 60) - print("Running graph with C input adapter (realtime, 500ms)...") - print() - - csp.run( - input_adapter_example, - starttime=utc_now(), - endtime=timedelta(milliseconds=500), - realtime=True, # Need realtime for push adapters - ) - - -def run_full_pipeline_example(): - """Run the full pipeline example.""" - print("\n" + "=" * 60) - print("Example 3: Full C -> CSP -> C Pipeline") - print("=" * 60) - print("Running full pipeline (realtime, 300ms)...") - print() - - csp.run( - full_pipeline_example, - starttime=utc_now(), - endtime=timedelta(milliseconds=300), - realtime=True, - ) - - -def main(): - """Run all examples.""" - print("CSP C API Adapter Examples") - print("=" * 60) - - # Only run the output adapter example by default since the input - # adapter requires the C library to be compiled with threading support - run_output_adapter_example() - - # Uncomment to run input adapter examples: - # run_input_adapter_example() - # run_full_pipeline_example() - - print("\n" + "=" * 60) - print("Done!") - print("=" * 60) - - -if __name__ == "__main__": - main() diff --git a/examples/05_cpp/1_cpp_node/CMakeLists.txt b/examples/05_cpp/1_cpp_node/CMakeLists.txt index 3558941a5..f2081229e 100644 --- a/examples/05_cpp/1_cpp_node/CMakeLists.txt +++ b/examples/05_cpp/1_cpp_node/CMakeLists.txt @@ -100,7 +100,7 @@ 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}) -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 ada3ff6ec..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: diff --git a/examples/05_cpp/1_cpp_node/piglatin.cpp b/examples/05_cpp/1_cpp_node/cpp/piglatin.cpp similarity index 58% rename from examples/05_cpp/1_cpp_node/piglatin.cpp rename to examples/05_cpp/1_cpp_node/cpp/piglatin.cpp index 6b4327c82..44865619e 100644 --- a/examples/05_cpp/1_cpp_node/piglatin.cpp +++ b/examples/05_cpp/1_cpp_node/cpp/piglatin.cpp @@ -22,15 +22,15 @@ DECLARE_CPPNODE(piglatin) INVOKE() { - if(csp.ticked(x) && csp.valid(x)) - { - std::string str = x.lastValue(); - if (capitalize) + if( csp.ticked( x ) && csp.valid( x ) ) { - std::transform(str.begin(), str.end(), str.begin(), ::toupper); + 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" ) ); } - RETURN(str.substr(1, std::string::npos) + str[0] + (capitalize ? "AY" : "ay")); - } } }; @@ -47,15 +47,15 @@ static PyModuleDef _piglatin_module = { NULL, NULL, NULL, NULL, NULL }; -PyMODINIT_FUNC PyInit__piglatin(void) +PyMODINIT_FUNC PyInit__piglatin( void ) { - PyObject* m; + PyObject * m; - m = PyModule_Create(&_piglatin_module); - if(m == NULL) + m = PyModule_Create( &_piglatin_module ); + if( m == NULL ) return NULL; - if(!csp::python::InitHelper::instance().execute(m)) + 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..e01726600 100644 --- a/examples/05_cpp/2_cpp_node_with_struct/CMakeLists.txt +++ b/examples/05_cpp/2_cpp_node_with_struct/CMakeLists.txt @@ -104,7 +104,7 @@ include_directories(${CSP_INCLUDE_DIR}) find_package(csp_autogen REQUIRED) csp_autogen(mystruct.struct mystruct STRUCT_AUTOGEN_HEADER STRUCT_AUTOGEN_SOURCE) -add_library(mystruct SHARED struct.cpp ${STRUCT_AUTOGEN_SOURCE}) +add_library(mystruct SHARED cpp/struct.cpp ${STRUCT_AUTOGEN_SOURCE}) 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}") 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 69e749371..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: 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..fadf87648 --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/CMakeLists.txt @@ -0,0 +1,101 @@ +cmake_minimum_required(VERSION 3.20.0) +project(csp-example-counter-adapter VERSION "0.0.1") +set(CMAKE_CXX_STANDARD 17) + +include(CheckCCompilerFlag) + +if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(MACOS ON) + set(LINUX OFF) +else() + set(MACOS OFF) + set(LINUX ON) +endif() + +list(PREPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../cpp/cmake/modules") +set(ENV{PYTHONPATH} "${CMAKE_SOURCE_DIR}/../../../:$ENV{PYTHONPATH}") + +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") +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 + 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 + 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) + 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") + + 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) + 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 +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}) + +# 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 index 8411d0fbe..421508b65 100644 --- a/examples/05_cpp/3_cpp_adapter/README.md +++ b/examples/05_cpp/3_cpp_adapter/README.md @@ -1,6 +1,167 @@ # Custom C++ Adapter > [!WARNING] -> This example is for demonstration, and is a pattern CSP uses internally for adapters. -> 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. +> **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 datetime import datetime, 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=datetime.utcnow(), + endtime=datetime.utcnow() + 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..6d0bfeaf6 --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/counteradapter/__main__.py @@ -0,0 +1,101 @@ +#!/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 +""" + +import sys +from datetime import datetime, timedelta + +import csp +from csp import ts + + +# For development/testing, add the build directory to path +# In production, you would install the module properly +def setup_path(): + """Add the build directory to the Python path if needed.""" + import os + + build_dir = os.path.join(os.path.dirname(__file__), "build") + if os.path.exists(build_dir) and build_dir not in sys.path: + sys.path.insert(0, build_dir) + + +setup_path() + +# Import the adapter after setting up the path +try: + from counteradapter import CounterAdapterManager +except ImportError: + print("Error: Could not import counteradapter.") + print("Make sure you have built the C++ extension module.") + print("See the build instructions in README.md") + sys.exit(1) + + +@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 = datetime.utcnow() + 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..7081f7268 --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/counteradapter/test_counteradapter.py @@ -0,0 +1,19 @@ +from datetime import datetime, timedelta + +import csp + +from .__main__ import counter_graph + + +def test_counteradapter(): + start = datetime.utcnow() + 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..667dbeccf --- /dev/null +++ b/examples/05_cpp/3_cpp_adapter/cpp/counteradapterimpl.cpp @@ -0,0 +1,127 @@ +/** + * 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..d9cb7af26 --- /dev/null +++ b/examples/05_cpp/4_c_api_adapter/CMakeLists.txt @@ -0,0 +1,156 @@ +cmake_minimum_required(VERSION 3.20.0) +project(csp-example-counter-adapter VERSION "0.0.1") +set(CMAKE_CXX_STANDARD 17) + +include(CheckCCompilerFlag) + +if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(MACOS ON) + set(LINUX OFF) +else() + set(MACOS OFF) + set(LINUX ON) +endif() + +list(PREPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../cpp/cmake/modules") +set(ENV{PYTHONPATH} "${CMAKE_SOURCE_DIR}/../../../:$ENV{PYTHONPATH}") + +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") +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 + 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 + 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) + 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") + + 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) + 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 +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}) + + + +# Example C adapters demonstrating the C ABI interface + +set(C_EXAMPLE_HEADER_FILES + cpp/ExamplePushInputAdapter.h + cpp/ExampleOutputAdapter.h + cpp/ExampleManagedAdapter.h +) + +set(C_EXAMPLE_SOURCE_FILES + cpp/ExamplePushInputAdapter.c + cpp/ExampleOutputAdapter.c + cpp/ExampleManagedAdapter.c +) + +add_library(csp_c_example_adapter STATIC ${C_EXAMPLE_SOURCE_FILES} ${C_EXAMPLE_HEADER_FILES}) + +# Include the engine c headers +target_include_directories(csp_c_example_adapter PUBLIC + ${CMAKE_SOURCE_DIR}/cpp +) + +# Set C standard +set_target_properties(csp_c_example_adapter PROPERTIES + PUBLIC_HEADER "${C_EXAMPLE_HEADER_FILES}" + C_STANDARD 11 + C_STANDARD_REQUIRED ON +) + +# On non-Windows, we need pthread for the example threaded adapters +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 + +# This creates a Python extension module +Python_add_library(_exampleadapterimpl MODULE cpp/exampleadapterimpl.c) + +# Link with the example adapter library +target_link_libraries(_exampleadapterimpl PRIVATE + csp_c_example_adapter +) + +# Include directories +target_include_directories(_exampleadapterimpl PRIVATE + ${CMAKE_SOURCE_DIR}/cpp + ${Python_INCLUDE_DIRS} +) + +# Set output name without lib prefix +set_target_properties(_exampleadapterimpl PROPERTIES + PREFIX "" + C_STANDARD 11 + C_STANDARD_REQUIRED ON +) + +# Install to the Python package location +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 index 8ca3d943e..fce571e1d 100644 --- a/examples/05_cpp/4_c_api_adapter/README.md +++ b/examples/05_cpp/4_c_api_adapter/README.md @@ -1 +1,191 @@ -# Custom C++ Adapter via C API +# 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 +import csp +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=datetime.utcnow(), 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/CMakeLists.txt b/examples/05_cpp/4_c_api_adapter/cpp/CMakeLists.txt deleted file mode 100644 index c2c9bacee..000000000 --- a/examples/05_cpp/4_c_api_adapter/cpp/CMakeLists.txt +++ /dev/null @@ -1,67 +0,0 @@ -# Example C adapters demonstrating the C ABI interface - -set(C_EXAMPLE_HEADER_FILES - ExamplePushInputAdapter.h - ExampleOutputAdapter.h - ExampleManagedAdapter.h -) - -set(C_EXAMPLE_SOURCE_FILES - ExamplePushInputAdapter.c - ExampleOutputAdapter.c - ExampleManagedAdapter.c -) - -add_library(csp_c_example_adapter STATIC ${C_EXAMPLE_SOURCE_FILES} ${C_EXAMPLE_HEADER_FILES}) - -# Include the engine c headers -target_include_directories(csp_c_example_adapter PUBLIC - ${CMAKE_SOURCE_DIR}/cpp -) - -# Set C standard -set_target_properties(csp_c_example_adapter PROPERTIES - PUBLIC_HEADER "${C_EXAMPLE_HEADER_FILES}" - C_STANDARD 11 - C_STANDARD_REQUIRED ON -) - -# On non-Windows, we need pthread for the example threaded adapters -if(NOT WIN32) - target_link_libraries(csp_c_example_adapter pthread) -endif() - -install(TARGETS csp_c_example_adapter - PUBLIC_HEADER DESTINATION include/csp/adapters/c/example - RUNTIME DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR} - LIBRARY DESTINATION lib/ -) - -# Python bindings for example C adapters - -# This creates a Python extension module -Python_add_library(_exampleadapterimpl MODULE exampleadapterimpl.c) - -# Link with the example adapter library -target_link_libraries(_exampleadapterimpl PRIVATE - csp_c_example_adapter -) - -# Include directories -target_include_directories(_exampleadapterimpl PRIVATE - ${CMAKE_SOURCE_DIR}/cpp - ${Python_INCLUDE_DIRS} -) - -# Set output name without lib prefix -set_target_properties(_exampleadapterimpl PROPERTIES - PREFIX "" - C_STANDARD 11 - C_STANDARD_REQUIRED ON -) - -# Install to the Python package location -install(TARGETS _exampleadapterimpl - LIBRARY DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR} - RUNTIME DESTINATION ${CSP_RUNTIME_INSTALL_SUBDIR} -) diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c index 8610956b2..472eee723 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c @@ -14,71 +14,75 @@ * Adapter Manager Callbacks * ============================================================================ */ -static const char* managed_adapter_name(void* user_data) +static const char * managed_adapter_name( void * user_data ) { - ManagedAdapterState* state = (ManagedAdapterState*)user_data; - return state->name; + ManagedAdapterState * state = ( ManagedAdapterState * )user_data; + return state -> name; } -static void managed_adapter_start(void* user_data, CCspAdapterManagerHandle manager, - CCspDateTime start_time, CCspDateTime end_time) +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; + 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; + 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); + 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"); + ccsp_adapter_manager_push_status( manager, CCSP_STATUS_LEVEL_INFO, 0, + "Manager started successfully" ); } -static void managed_adapter_stop(void* user_data) +static void managed_adapter_stop( void * user_data ) { - ManagedAdapterState* state = (ManagedAdapterState*)user_data; + ManagedAdapterState * state = ( ManagedAdapterState * )user_data; - printf("[%s] Manager stopped. Total messages: %d\n", - state->name, state->message_count); + printf( "[%s] Manager stopped. Total messages: %d\n", + state -> name, state -> message_count ); - state->is_started = 0; + state -> is_started = 0; } -static CCspDateTime managed_adapter_process_next_sim_time_slice(void* user_data, - CCspDateTime time) +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; + ( void )user_data; + ( void )time; return 0; } -static void managed_adapter_destroy(void* user_data) +static void managed_adapter_destroy( void * user_data ) { - ManagedAdapterState* state = (ManagedAdapterState*)user_data; - printf("[%s] Manager destroyed\n", state->name); - free(state); + ManagedAdapterState * state = ( ManagedAdapterState * )user_data; + printf( "[%s] Manager destroyed\n", state -> name ); + free( state ); } -CCspAdapterManagerVTable example_managed_adapter_create(const char* name) +CCspAdapterManagerVTable example_managed_adapter_create( const char * name ) { - ManagedAdapterState* state = (ManagedAdapterState*)malloc(sizeof(ManagedAdapterState)); - if (!state) { + 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); + 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; @@ -96,105 +100,118 @@ CCspAdapterManagerVTable example_managed_adapter_create(const char* name) * Managed Output Adapter Callbacks * ============================================================================ */ -static void managed_output_start(void* user_data, CCspEngineHandle engine, - CCspDateTime start_time, CCspDateTime end_time) +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; + 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) +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); + 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) +static void managed_output_execute( void * user_data, CCspEngineHandle engine, + CCspInputHandle input ) { - ManagedOutputState* state = (ManagedOutputState*)user_data; + ManagedOutputState * state = ( ManagedOutputState * )user_data; - if (!ccsp_input_is_valid(input)) { + if( !ccsp_input_is_valid( input ) ) + { return; } /* Get current engine time */ - CCspDateTime now = ccsp_engine_now(engine); - double now_sec = (double)now / 1000000000.0; + 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); + CCspType type = ccsp_input_get_type( input ); - printf(" [%s/%s] t=%.3f -> ", state->shared->name, state->topic, now_sec); + printf( " [%s/%s] t=%.3f -> ", state -> shared -> name, state -> topic, now_sec ); - switch (type) { - case CCSP_TYPE_INT64: { + 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); + if( ccsp_input_get_last_int64( input, &value ) == CCSP_OK ) + { + printf( "int64: %lld\n", ( long long )value ); } break; } - case CCSP_TYPE_DOUBLE: { + case CCSP_TYPE_DOUBLE: + { double value; - if (ccsp_input_get_last_double(input, &value) == CCSP_OK) { - printf("double: %.6f\n", value); + if( ccsp_input_get_last_double( input, &value ) == CCSP_OK ) + { + printf( "double: %.6f\n", value ); } break; } - case CCSP_TYPE_STRING: { - const char* data; + 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); + 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); + printf( "(type %d)\n", type ); break; } - state->messages_sent++; - state->shared->message_count++; + state -> messages_sent++; + state -> shared -> message_count++; } -static void managed_output_destroy(void* user_data) +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); + 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) + ManagedAdapterState * shared_state, + const char * topic ) { CCspOutputAdapterVTable vtable = {0}; - if (!shared_state) { + if( !shared_state ) + { return vtable; } - ManagedOutputState* state = (ManagedOutputState*)malloc(sizeof(ManagedOutputState)); - if (!state) { + 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); + 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; diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h index ca7a57291..4e2733f7c 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h @@ -40,7 +40,7 @@ typedef struct ManagedAdapterState { * ManagedOutputState - State for a single output adapter in the manager */ typedef struct ManagedOutputState { - ManagedAdapterState* shared; + ManagedAdapterState * shared; char topic[64]; /* e.g., Kafka topic name */ int messages_sent; } ManagedOutputState; @@ -57,7 +57,7 @@ typedef struct ManagedOutputState { * Returns: * VTable for use with ccsp_adapter_manager_extern_create */ -CCspAdapterManagerVTable example_managed_adapter_create(const char* name); +CCspAdapterManagerVTable example_managed_adapter_create( const char * name ); /* * example_managed_output_adapter_create - Create output adapter for manager @@ -72,8 +72,14 @@ CCspAdapterManagerVTable example_managed_adapter_create(const char* name); * VTable for use with ccsp_adapter_manager_create_output_adapter */ CCspOutputAdapterVTable example_managed_output_adapter_create( - ManagedAdapterState* shared_state, - const char* topic); + ManagedAdapterState * shared_state, + const char * topic ); + +#ifdef __cplusplus +} +#endif + +#endif /* _IN_CSP_ADAPTERS_C_EXAMPLE_MANAGED_ADAPTER_H */ #ifdef __cplusplus } diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c index 12bd0cb1b..e6c7e1527 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c @@ -14,96 +14,109 @@ /* 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 */ + 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) +static void example_output_start( void * user_data, CCspEngineHandle engine, + CCspDateTime start_time, CCspDateTime end_time ) { - ExampleOutputAdapterState* state = (ExampleOutputAdapterState*)user_data; - (void)engine; + 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); + 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) +static void example_output_stop( void * user_data ) { - ExampleOutputAdapterState* state = (ExampleOutputAdapterState*)user_data; - dprintf(state->fd, "[ExampleOutputAdapter] Stopped.\n"); + ExampleOutputAdapterState * state = ( ExampleOutputAdapterState * )user_data; + dprintf( state -> fd, "[ExampleOutputAdapter] Stopped.\n" ); } -static void example_output_execute(void* user_data, CCspEngineHandle engine, - CCspInputHandle input) +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); + 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 : ""; + const char * prefix = state -> prefix ? state -> prefix : ""; /* Print based on type */ - switch (type) { - case CCSP_TYPE_BOOL: { + 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"); + 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: { + 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); + 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: { + 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); + 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; + 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); + 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: { + 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); + 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); + dprintf( state -> fd, "%s[%lld] \n", prefix, ( long long )now, ( int )type ); break; } } -static void example_output_destroy(void* user_data) +static void example_output_destroy( void * user_data ) { - ExampleOutputAdapterState* state = (ExampleOutputAdapterState*)user_data; - if (state) { - if (state->owns_prefix && state->prefix) { - free(state->prefix); + ExampleOutputAdapterState * state = ( ExampleOutputAdapterState * )user_data; + if( state ) + { + if( state -> owns_prefix && state -> prefix ) + { + free( state -> prefix ); } - free(state); + free( state ); } } @@ -111,33 +124,36 @@ static void example_output_destroy(void* user_data) * Public API * ============================================================================ */ -CCspOutputAdapterVTable example_output_adapter_create(const char* prefix) +CCspOutputAdapterVTable example_output_adapter_create( const char * prefix ) { - return example_output_adapter_create_fd(STDOUT_FILENO, prefix); + return example_output_adapter_create_fd( STDOUT_FILENO, prefix ); } -CCspOutputAdapterVTable example_output_adapter_create_fd(int fd, const char* 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) { + 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; + 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; + 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; } } diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.h b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.h index 15ff26a86..3c8362782 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.h +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.h @@ -19,7 +19,7 @@ extern "C" { * @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); +CCspOutputAdapterVTable example_output_adapter_create( const char * prefix ); /* * Alternative: Get an adapter that logs to a specific file descriptor. @@ -28,7 +28,7 @@ CCspOutputAdapterVTable example_output_adapter_create(const char* prefix); * @param prefix Prefix string (can be NULL) * @return VTable structure */ -CCspOutputAdapterVTable example_output_adapter_create_fd(int fd, const char* prefix); +CCspOutputAdapterVTable example_output_adapter_create_fd( int fd, const char * prefix ); #ifdef __cplusplus } @@ -36,3 +36,5 @@ CCspOutputAdapterVTable example_output_adapter_create_fd(int fd, const char* pre #endif /* _IN_CSP_ADAPTERS_C_EXAMPLE_OUTPUT_ADAPTER_H */ +#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 index 02f2b742c..fa30b741a 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.c +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.c @@ -34,83 +34,85 @@ typedef struct { #endif } IntAdapterState; -static void* int_adapter_thread(void* arg) +static void * int_adapter_thread( void * arg ) { - IntAdapterState* state = (IntAdapterState*)arg; + IntAdapterState * state = ( IntAdapterState * )arg; - while (state->running) { + while( state -> running ) + { /* Push the current counter value */ - ccsp_push_input_adapter_push_int64(state->adapter, state->counter, NULL); - state->counter++; + ccsp_push_input_adapter_push_int64( state -> adapter, state -> counter, NULL ); + state -> counter++; /* Sleep for the interval */ #ifdef _WIN32 - Sleep(state->interval_ms); + Sleep( state -> interval_ms ); #else - usleep(state->interval_ms * 1000); + usleep( state -> interval_ms * 1000 ); #endif } return NULL; } -static void int_adapter_start(void* user_data, CCspEngineHandle engine, +static void int_adapter_start( void * user_data, CCspEngineHandle engine, CCspPushInputAdapterHandle adapter, - CCspDateTime start_time, CCspDateTime end_time) + CCspDateTime start_time, CCspDateTime end_time ) { - IntAdapterState* state = (IntAdapterState*)user_data; - (void)engine; - (void)start_time; - (void)end_time; + IntAdapterState * state = ( IntAdapterState * )user_data; + ( void )engine; + ( void )start_time; + ( void )end_time; - state->adapter = adapter; - state->running = 1; - state->counter = 0; + 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); + state -> thread = CreateThread( NULL, 0, ( LPTHREAD_START_ROUTINE )int_adapter_thread, + state, 0, NULL ); #else - pthread_create(&state->thread, NULL, int_adapter_thread, state); + pthread_create( &state -> thread, NULL, int_adapter_thread, state ); #endif - fprintf(stdout, "[ExampleIntInputAdapter] Started with interval %d ms\n", - state->interval_ms); + fprintf( stdout, "[ExampleIntInputAdapter] Started with interval %d ms\n", + state -> interval_ms ); } -static void int_adapter_stop(void* user_data) +static void int_adapter_stop( void * user_data ) { - IntAdapterState* state = (IntAdapterState*)user_data; - state->running = 0; + IntAdapterState * state = ( IntAdapterState * )user_data; + state -> running = 0; #ifdef _WIN32 - WaitForSingleObject(state->thread, INFINITE); - CloseHandle(state->thread); + WaitForSingleObject( state -> thread, INFINITE ); + CloseHandle( state -> thread ); #else - pthread_join(state->thread, NULL); + pthread_join( state -> thread, NULL ); #endif - fprintf(stdout, "[ExampleIntInputAdapter] Stopped after %lld values\n", - (long long)state->counter); + fprintf( stdout, "[ExampleIntInputAdapter] Stopped after %lld values\n", + ( long long )state -> counter ); } -static void int_adapter_destroy(void* user_data) +static void int_adapter_destroy( void * user_data ) { - IntAdapterState* state = (IntAdapterState*)user_data; - free(state); + IntAdapterState * state = ( IntAdapterState * )user_data; + free( state ); } -CCspPushInputAdapterVTable example_push_input_adapter_create_int(int interval_ms) +CCspPushInputAdapterVTable example_push_input_adapter_create_int( int interval_ms ) { CCspPushInputAdapterVTable vtable = {0}; - IntAdapterState* state = (IntAdapterState*)malloc(sizeof(IntAdapterState)); - if (!state) { + IntAdapterState * state = ( IntAdapterState * )malloc( sizeof( IntAdapterState ) ); + if( !state ) + { return vtable; } - memset(state, 0, sizeof(IntAdapterState)); - state->interval_ms = interval_ms > 0 ? interval_ms : 100; + memset( state, 0, sizeof( IntAdapterState ) ); + state -> interval_ms = interval_ms > 0 ? interval_ms : 100; vtable.user_data = state; vtable.start = int_adapter_start; @@ -135,79 +137,81 @@ typedef struct { #endif } DoubleAdapterState; -static void* double_adapter_thread(void* arg) +static void * double_adapter_thread( void * arg ) { - DoubleAdapterState* state = (DoubleAdapterState*)arg; + DoubleAdapterState * state = ( DoubleAdapterState * )arg; - while (state->running) { + 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); + double value = ( double )rand() / ( double )RAND_MAX; + ccsp_push_input_adapter_push_double( state -> adapter, value, NULL ); #ifdef _WIN32 - Sleep(state->interval_ms); + Sleep( state -> interval_ms ); #else - usleep(state->interval_ms * 1000); + usleep( state -> interval_ms * 1000 ); #endif } return NULL; } -static void double_adapter_start(void* user_data, CCspEngineHandle engine, +static void double_adapter_start( void * user_data, CCspEngineHandle engine, CCspPushInputAdapterHandle adapter, - CCspDateTime start_time, CCspDateTime end_time) + CCspDateTime start_time, CCspDateTime end_time ) { - DoubleAdapterState* state = (DoubleAdapterState*)user_data; - (void)engine; - (void)start_time; - (void)end_time; + DoubleAdapterState * state = ( DoubleAdapterState * )user_data; + ( void )engine; + ( void )start_time; + ( void )end_time; - state->adapter = adapter; - state->running = 1; + state -> adapter = adapter; + state -> running = 1; #ifdef _WIN32 - state->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)double_adapter_thread, - state, 0, NULL); + state -> thread = CreateThread( NULL, 0, ( LPTHREAD_START_ROUTINE )double_adapter_thread, + state, 0, NULL ); #else - pthread_create(&state->thread, NULL, double_adapter_thread, state); + pthread_create( &state -> thread, NULL, double_adapter_thread, state ); #endif - fprintf(stdout, "[ExampleDoubleInputAdapter] Started\n"); + fprintf( stdout, "[ExampleDoubleInputAdapter] Started\n" ); } -static void double_adapter_stop(void* user_data) +static void double_adapter_stop( void * user_data ) { - DoubleAdapterState* state = (DoubleAdapterState*)user_data; - state->running = 0; + DoubleAdapterState * state = ( DoubleAdapterState * )user_data; + state -> running = 0; #ifdef _WIN32 - WaitForSingleObject(state->thread, INFINITE); - CloseHandle(state->thread); + WaitForSingleObject( state -> thread, INFINITE ); + CloseHandle( state -> thread ); #else - pthread_join(state->thread, NULL); + pthread_join( state -> thread, NULL ); #endif - fprintf(stdout, "[ExampleDoubleInputAdapter] Stopped\n"); + fprintf( stdout, "[ExampleDoubleInputAdapter] Stopped\n" ); } -static void double_adapter_destroy(void* user_data) +static void double_adapter_destroy( void * user_data ) { - DoubleAdapterState* state = (DoubleAdapterState*)user_data; - free(state); + DoubleAdapterState * state = ( DoubleAdapterState * )user_data; + free( state ); } -CCspPushInputAdapterVTable example_push_input_adapter_create_double(int interval_ms) +CCspPushInputAdapterVTable example_push_input_adapter_create_double( int interval_ms ) { CCspPushInputAdapterVTable vtable = {0}; - DoubleAdapterState* state = (DoubleAdapterState*)malloc(sizeof(DoubleAdapterState)); - if (!state) { + DoubleAdapterState * state = ( DoubleAdapterState * )malloc( sizeof( DoubleAdapterState ) ); + if( !state ) + { return vtable; } - memset(state, 0, sizeof(DoubleAdapterState)); - state->interval_ms = interval_ms > 0 ? interval_ms : 100; + memset( state, 0, sizeof( DoubleAdapterState ) ); + state -> interval_ms = interval_ms > 0 ? interval_ms : 100; vtable.user_data = state; vtable.start = double_adapter_start; @@ -223,48 +227,49 @@ CCspPushInputAdapterVTable example_push_input_adapter_create_double(int interval typedef struct { ExampleStringCallback callback; - void* callback_data; + void * callback_data; CCspPushInputAdapterHandle adapter; } StringAdapterState; -static void string_adapter_start(void* user_data, CCspEngineHandle engine, +static void string_adapter_start( void * user_data, CCspEngineHandle engine, CCspPushInputAdapterHandle adapter, - CCspDateTime start_time, CCspDateTime end_time) + CCspDateTime start_time, CCspDateTime end_time ) { - StringAdapterState* state = (StringAdapterState*)user_data; - (void)engine; - (void)start_time; - (void)end_time; + StringAdapterState * state = ( StringAdapterState * )user_data; + ( void )engine; + ( void )start_time; + ( void )end_time; - state->adapter = adapter; - fprintf(stdout, "[ExampleStringInputAdapter] Started\n"); + state -> adapter = adapter; + fprintf( stdout, "[ExampleStringInputAdapter] Started\n" ); } -static void string_adapter_stop(void* user_data) +static void string_adapter_stop( void * user_data ) { - (void)user_data; - fprintf(stdout, "[ExampleStringInputAdapter] Stopped\n"); + ( void )user_data; + fprintf( stdout, "[ExampleStringInputAdapter] Stopped\n" ); } -static void string_adapter_destroy(void* user_data) +static void string_adapter_destroy( void * user_data ) { - StringAdapterState* state = (StringAdapterState*)user_data; - free(state); + StringAdapterState * state = ( StringAdapterState * )user_data; + free( state ); } CCspPushInputAdapterVTable example_push_input_adapter_create_string( - ExampleStringCallback get_string, void* user_data) + ExampleStringCallback get_string, void * user_data ) { CCspPushInputAdapterVTable vtable = {0}; - StringAdapterState* state = (StringAdapterState*)malloc(sizeof(StringAdapterState)); - if (!state) { + StringAdapterState * state = ( StringAdapterState * )malloc( sizeof( StringAdapterState ) ); + if( !state ) + { return vtable; } - memset(state, 0, sizeof(StringAdapterState)); - state->callback = get_string; - state->callback_data = user_data; + memset( state, 0, sizeof( StringAdapterState ) ); + state -> callback = get_string; + state -> callback_data = user_data; vtable.user_data = state; vtable.start = string_adapter_start; diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h index fbe41fbbf..7b7f77c9a 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h @@ -19,7 +19,7 @@ extern "C" { * @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); +CCspPushInputAdapterVTable example_push_input_adapter_create_int( int interval_ms ); /* * Create an example push input adapter that generates random doubles. @@ -27,7 +27,7 @@ CCspPushInputAdapterVTable example_push_input_adapter_create_int(int interval_ms * @param interval_ms Interval between pushes in milliseconds * @return VTable structure */ -CCspPushInputAdapterVTable example_push_input_adapter_create_double(int interval_ms); +CCspPushInputAdapterVTable example_push_input_adapter_create_double( int interval_ms ); /* * Create an example push input adapter that echoes strings from a callback. @@ -36,12 +36,14 @@ CCspPushInputAdapterVTable example_push_input_adapter_create_double(int interval * @param user_data User data passed to the callback * @return VTable structure */ -typedef const char* (*ExampleStringCallback)(void* user_data); +typedef const char * ( *ExampleStringCallback )( void * user_data ); CCspPushInputAdapterVTable example_push_input_adapter_create_string( - ExampleStringCallback get_string, void* user_data); + ExampleStringCallback get_string, void * user_data ); #ifdef __cplusplus } #endif #endif /* _IN_CSP_ADAPTERS_C_EXAMPLE_PUSH_INPUT_ADAPTER_H */ + +#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 index e9fe9395c..2b72e65fe 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/exampleadapterimpl.c +++ b/examples/05_cpp/4_c_api_adapter/cpp/exampleadapterimpl.c @@ -28,12 +28,12 @@ static PyObject * create_adapter_manager_py( PyObject * self, PyObject * args ) 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 ) ) @@ -56,21 +56,57 @@ static PyObject * create_adapter_manager_py( PyObject * self, PyObject * args ) } /* - * _example_input_adapter(manager_or_interval, ts_type, properties, push_mode) -> capsule + * _example_input_adapter(mgr, engine, pytype, push_mode, scalars) -> capsule * * Create an example input adapter that generates incrementing integers. - * Can be used with or without an adapter manager. + * Called by input_adapter_def wiring layer with standard arguments. */ static PyObject * create_input_adapter_py( PyObject * self, PyObject * args, PyObject * kwargs ) { ( void )self; - - static char * kwlist[] = { "interval_ms", NULL }; + ( 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( !PyArg_ParseTupleAndKeywords( args, kwargs, "|i", kwlist, &interval_ms ) ) + if( scalars && PyTuple_Check( scalars ) && PyTuple_Size( scalars ) > 0 ) { - return NULL; + 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 ); @@ -84,20 +120,55 @@ static PyObject * create_input_adapter_py( PyObject * self, PyObject * args, PyO } /* - * _example_output_adapter(prefix: str = None) -> capsule + * _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; - - static char * kwlist[] = { "prefix", NULL }; + ( 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( !PyArg_ParseTupleAndKeywords( args, kwargs, "|s", kwlist, &prefix ) ) + if( scalars && PyTuple_Check( scalars ) && PyTuple_Size( scalars ) > 0 ) { - return NULL; + 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 ); @@ -119,21 +190,21 @@ static PyObject * create_output_adapter_py( PyObject * self, PyObject * args, Py 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 ) @@ -143,30 +214,30 @@ static PyObject * inspect_struct_type_py( PyObject * self, PyObject * args ) 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 ) @@ -175,17 +246,17 @@ static PyObject * inspect_struct_type_py( PyObject * self, PyObject * args ) 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 ) @@ -193,7 +264,7 @@ static PyObject * inspect_struct_type_py( PyObject * self, PyObject * args ) Py_DECREF( result ); return NULL; } - + for( size_t i = 0; i < field_count; i++ ) { CCspStructFieldHandle field = ccsp_struct_meta_field_by_index( meta, i ); @@ -201,7 +272,7 @@ static PyObject * inspect_struct_type_py( PyObject * self, PyObject * args ) { continue; } - + PyObject * field_dict = PyDict_New(); if( !field_dict ) { @@ -209,7 +280,7 @@ static PyObject * inspect_struct_type_py( PyObject * self, PyObject * args ) Py_DECREF( result ); return NULL; } - + const char * field_name = ccsp_struct_field_name( field ); if( field_name ) { @@ -217,21 +288,21 @@ static PyObject * inspect_struct_type_py( PyObject * self, PyObject * args ) 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; } 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 index 42a126080..408046f53 100644 --- a/examples/05_cpp/5_c_api_adapter_rust/README.md +++ b/examples/05_cpp/5_c_api_adapter_rust/README.md @@ -1 +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..6c1781669 --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/src/bindings.rs @@ -0,0 +1,286 @@ +//! 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. +// ============================================================================ + +// Function pointer types for CSP C API +pub type FnCcspEngineNow = unsafe extern "C" fn(engine: CCspEngineHandle) -> CCspDateTime; +pub type FnCcspInputGetType = unsafe extern "C" fn(input: CCspInputHandle) -> CCspType; +pub type FnCcspInputGetLastInt64 = unsafe extern "C" fn(input: CCspInputHandle, out: *mut i64) -> CCspErrorCode; +pub type FnCcspPushInt64 = unsafe extern "C" fn(adapter: CCspPushInputAdapterHandle, value: i64, 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..b55a523ba --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/src/input_adapter.rs @@ -0,0 +1,149 @@ +//! Input Adapter implementation in Rust +//! +//! This module demonstrates how to implement a CSP push input adapter in Rust +//! using the C API FFI bindings. +//! +//! Note: The actual data pushing requires the CSP C API symbols to be available +//! at runtime. When built standalone, the callbacks are implemented but the +//! push functionality is stubbed. + +use std::ffi::c_void; +use std::sync::atomic::{AtomicBool, AtomicI64, Ordering}; +use std::sync::Arc; + +use crate::bindings::{ + CCspDateTime, CCspEngineHandle, CCspPushInputAdapterHandle, +}; + +/// Push input adapter that generates incrementing counter values. +/// +/// This adapter would spawn 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 (set by start callback) + #[allow(dead_code)] + adapter_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: None, + } + } + + /// Start the adapter. + /// + /// In a full implementation with CSP C API symbols linked, this would + /// spawn a background thread that calls ccsp_push_input_adapter_push_int64. + pub fn start(&mut self, adapter: CCspPushInputAdapterHandle) { + self.adapter_handle = Some(adapter); + self.running.store(true, Ordering::SeqCst); + + eprintln!( + "[RustInputAdapter] Started with interval {} ms (adapter handle: {:?})", + self.interval_ms, adapter + ); + + // Note: In a full implementation, we would spawn a thread here that + // calls ccsp_push_input_adapter_push_int64 to push values. + // This requires the CSP C API symbols to be available at runtime. + } + + /// Stop the adapter. + pub fn stop(&mut self) { + eprintln!( + "[RustInputAdapter] Stopped after {} values", + self.counter.load(Ordering::SeqCst) + ); + + self.running.store(false, Ordering::SeqCst); + } +} + +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..6f878d597 --- /dev/null +++ b/examples/05_cpp/5_c_api_adapter_rust/src/output_adapter.rs @@ -0,0 +1,164 @@ +//! Output Adapter implementation in Rust +//! +//! This module demonstrates how to implement a CSP output adapter in Rust +//! using the C API FFI bindings. +//! +//! Note: The actual value retrieval requires the CSP C API symbols to be available +//! at runtime. When built standalone, the execute callback logs that it was invoked +//! but cannot access the actual values. + +use std::ffi::c_void; + +use crate::bindings::{ + CCspDateTime, CCspEngineHandle, CCspInputHandle, +}; + +/// Output adapter that prints values to stdout. +/// +/// 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. + /// + /// Note: In a full implementation with CSP C API symbols linked, this would + /// call ccsp_engine_now, ccsp_input_get_type, and ccsp_input_get_last_* to + /// retrieve and print the actual values. + pub unsafe fn execute(&mut self, engine: CCspEngineHandle, input: CCspInputHandle) { + let prefix = self.prefix.as_deref().unwrap_or(""); + + self.count += 1; + + // Note: Without CSP C API symbols, we can only log that execute was called. + // In a full implementation, we would call: + // let now = ccsp_engine_now(engine); + // let input_type = ccsp_input_get_type(input); + // ccsp_input_get_last_int64/double/bool/string(input, &mut value) + eprintln!( + "{}[RustOutputAdapter] execute called (count={}, engine={:?}, input={:?})", + prefix, self.count, engine, input + ); + } +} + +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/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", From e3eb6f74536fdf4178eb9cf46df49941ed08bee1 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Wed, 18 Feb 2026 10:10:00 -0500 Subject: [PATCH 05/12] Formatting Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- cpp/csp/engine/c/AdapterManager.h | 69 +++++++------------ cpp/csp/engine/c/CspDictionary.h | 9 +-- cpp/csp/engine/c/CspError.h | 20 +++--- cpp/csp/engine/c/CspExport.h | 8 +-- cpp/csp/engine/c/CspString.h | 28 ++++---- cpp/csp/engine/c/CspStruct.h | 12 ++-- cpp/csp/engine/c/CspTime.h | 50 +++++++------- cpp/csp/engine/c/CspValue.h | 98 +++++++++++++------------- cpp/csp/engine/c/InputAdapter.h | 110 +++++++++++++----------------- cpp/csp/engine/c/OutputAdapter.h | 60 ++++++++-------- 10 files changed, 209 insertions(+), 255 deletions(-) diff --git a/cpp/csp/engine/c/AdapterManager.h b/cpp/csp/engine/c/AdapterManager.h index 914320dea..c9f035796 100644 --- a/cpp/csp/engine/c/AdapterManager.h +++ b/cpp/csp/engine/c/AdapterManager.h @@ -28,9 +28,9 @@ extern "C" { * Opaque Handles * ============================================================================ */ -typedef struct CCspAdapterManagerImpl* CCspAdapterManagerHandle; -typedef struct CCspStatusAdapterImpl* CCspStatusAdapterHandle; -typedef struct CCspManagedSimInputAdapterImpl* CCspManagedSimInputAdapterHandle; +typedef struct CCspAdapterManagerImpl * CCspAdapterManagerHandle; +typedef struct CCspStatusAdapterImpl * CCspStatusAdapterHandle; +typedef struct CCspManagedSimInputAdapterImpl * CCspManagedSimInputAdapterHandle; /* ============================================================================ * Adapter Manager VTable @@ -49,7 +49,7 @@ typedef struct CCspManagedSimInputAdapterImpl* CCspManagedSimInputAdapterHandle; typedef struct CCspAdapterManagerVTable { /* User-defined data pointer passed to all callbacks */ - void* user_data; + void * user_data; /* ======================================================================== * Required Callbacks @@ -67,7 +67,7 @@ typedef struct CCspAdapterManagerVTable { * Returns: * A null-terminated string naming this adapter manager */ - const char* (*name)(void* user_data); + const char * ( * name ) ( void * user_data ); /* * process_next_sim_time_slice - Process simulation data for a time slice @@ -88,7 +88,7 @@ typedef struct CCspAdapterManagerVTable { * Returns: * Next timestamp with available data, or 0 if no more data */ - CCspDateTime (*process_next_sim_time_slice)(void* user_data, CCspDateTime time); + CCspDateTime ( * process_next_sim_time_slice )( void * user_data, CCspDateTime time ); /* * destroy - Clean up adapter manager resources @@ -99,7 +99,7 @@ typedef struct CCspAdapterManagerVTable { * Parameters: * user_data - The user_data pointer from this vtable */ - void (*destroy)(void* user_data); + void ( * destroy )( void * user_data ); /* ======================================================================== * Optional Callbacks (can be NULL) @@ -116,8 +116,7 @@ typedef struct CCspAdapterManagerVTable { * start_time - Graph start time * end_time - Graph end time */ - void (*start)(void* user_data, CCspAdapterManagerHandle manager, - CCspDateTime start_time, CCspDateTime end_time); + void ( * start )( void * user_data, CCspAdapterManagerHandle manager, CCspDateTime start_time, CCspDateTime end_time ); /* * stop - Called when the graph stops @@ -127,7 +126,7 @@ typedef struct CCspAdapterManagerVTable { * Parameters: * user_data - The user_data pointer from this vtable */ - void (*stop)(void* user_data); + void ( * stop )( void * user_data ); } CCspAdapterManagerVTable; @@ -147,9 +146,7 @@ typedef struct CCspAdapterManagerVTable { * Returns: * Handle to the new adapter manager */ -CSP_C_API_EXPORT CCspAdapterManagerHandle ccsp_adapter_manager_extern_create( - CCspEngineHandle engine, - const CCspAdapterManagerVTable* vtable); +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 @@ -159,7 +156,7 @@ CSP_C_API_EXPORT CCspAdapterManagerHandle ccsp_adapter_manager_extern_create( * Parameters: * manager - Handle to the adapter manager */ -CSP_C_API_EXPORT void ccsp_adapter_manager_extern_destroy(CCspAdapterManagerHandle manager); +CSP_C_API_EXPORT void ccsp_adapter_manager_extern_destroy( CCspAdapterManagerHandle manager ); /* ============================================================================ * Engine and Time Access @@ -174,7 +171,7 @@ CSP_C_API_EXPORT void ccsp_adapter_manager_extern_destroy(CCspAdapterManagerHand * Returns: * Engine handle for use with other C API functions */ -CSP_C_API_EXPORT CCspEngineHandle ccsp_adapter_manager_engine(CCspAdapterManagerHandle manager); +CSP_C_API_EXPORT CCspEngineHandle ccsp_adapter_manager_engine( CCspAdapterManagerHandle manager ); /* * ccsp_adapter_manager_start_time - Get graph start time @@ -187,7 +184,7 @@ CSP_C_API_EXPORT CCspEngineHandle ccsp_adapter_manager_engine(CCspAdapterManager * Returns: * Start time in nanoseconds since epoch */ -CSP_C_API_EXPORT CCspDateTime ccsp_adapter_manager_start_time(CCspAdapterManagerHandle manager); +CSP_C_API_EXPORT CCspDateTime ccsp_adapter_manager_start_time( CCspAdapterManagerHandle manager ); /* * ccsp_adapter_manager_end_time - Get graph end time @@ -200,7 +197,7 @@ CSP_C_API_EXPORT CCspDateTime ccsp_adapter_manager_start_time(CCspAdapterManager * Returns: * End time in nanoseconds since epoch */ -CSP_C_API_EXPORT CCspDateTime ccsp_adapter_manager_end_time(CCspAdapterManagerHandle manager); +CSP_C_API_EXPORT CCspDateTime ccsp_adapter_manager_end_time( CCspAdapterManagerHandle manager ); /* ============================================================================ * Adapter Creation from Manager @@ -223,10 +220,7 @@ CSP_C_API_EXPORT CCspDateTime ccsp_adapter_manager_end_time(CCspAdapterManagerHa * 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); +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 @@ -242,11 +236,7 @@ CSP_C_API_EXPORT CCspOutputAdapterHandle ccsp_adapter_manager_create_output_adap * 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); +CSP_C_API_EXPORT CCspPushInputAdapterHandle ccsp_adapter_manager_create_push_input_adapter( CCspAdapterManagerHandle manager, CCspType type, CCspPushMode push_mode, const CCspPushInputAdapterVTable * vtable ); /* ============================================================================ * Status Reporting @@ -278,11 +268,7 @@ typedef enum { * 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); +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 @@ -306,10 +292,7 @@ CSP_C_API_EXPORT CCspErrorCode ccsp_adapter_manager_push_status( * 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); +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 @@ -324,17 +307,11 @@ CSP_C_API_EXPORT CCspManagedSimInputAdapterHandle ccsp_adapter_manager_create_ma * 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); - +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 diff --git a/cpp/csp/engine/c/CspDictionary.h b/cpp/csp/engine/c/CspDictionary.h index 1da60f224..3c298b42a 100644 --- a/cpp/csp/engine/c/CspDictionary.h +++ b/cpp/csp/engine/c/CspDictionary.h @@ -133,8 +133,7 @@ CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_get_timedelta( CCspDictionaryHand * 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 ); +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 @@ -149,8 +148,7 @@ CSP_C_API_EXPORT CCspErrorCode ccsp_dictionary_get_string( CCspDictionaryHandle * 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 ); +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) @@ -183,8 +181,7 @@ CSP_C_API_EXPORT CCspTimeDelta ccsp_dictionary_get_timedelta_or( CCspDictionaryH * 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 ); +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 diff --git a/cpp/csp/engine/c/CspError.h b/cpp/csp/engine/c/CspError.h index f277dc147..c5bfb2823 100644 --- a/cpp/csp/engine/c/CspError.h +++ b/cpp/csp/engine/c/CspError.h @@ -30,33 +30,33 @@ typedef enum { } CCspErrorCode; /* Get the last error code for the current thread */ -CSP_C_API_EXPORT CCspErrorCode ccsp_get_last_error(void); +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); +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); +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); +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) \ +#define CCSP_RETURN_IF_ERROR( expr ) \ do { \ - CCspErrorCode _err = (expr); \ - if (_err != CCSP_OK) return _err; \ + CCspErrorCode _err = ( expr ); \ + if ( _err != CCSP_OK ) return _err; \ } while(0) -#define CCSP_RETURN_NULL_IF_ERROR(expr) \ +#define CCSP_RETURN_NULL_IF_ERROR( expr ) \ do { \ - CCspErrorCode _err = (expr); \ - if (_err != CCSP_OK) return NULL; \ + CCspErrorCode _err = ( expr ); \ + if ( _err != CCSP_OK ) return NULL; \ } while(0) #ifdef __cplusplus diff --git a/cpp/csp/engine/c/CspExport.h b/cpp/csp/engine/c/CspExport.h index 3db0fb1ac..640be067a 100644 --- a/cpp/csp/engine/c/CspExport.h +++ b/cpp/csp/engine/c/CspExport.h @@ -17,15 +17,15 @@ * 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) +#if defined( _WIN32 ) || defined( _WIN64 ) #ifdef CSPIMPL_EXPORTS - #define CSP_C_API_EXPORT __declspec(dllexport) + #define CSP_C_API_EXPORT __declspec( dllexport ) #else - #define CSP_C_API_EXPORT __declspec(dllimport) + #define CSP_C_API_EXPORT __declspec( dllimport ) #endif #else /* Unix/Linux/macOS - ensure default visibility */ - #define CSP_C_API_EXPORT __attribute__((visibility("default"))) + #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 index 70d5dbe38..a13e11ebc 100644 --- a/cpp/csp/engine/c/CspString.h +++ b/cpp/csp/engine/c/CspString.h @@ -21,7 +21,7 @@ extern "C" { * 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) */ + const char * data; /* Pointer to string data (may contain embedded nulls) */ size_t length; /* Length in bytes (not including any null terminator) */ } CCspStringView; @@ -31,65 +31,65 @@ typedef struct { * 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) */ + 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); +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); +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); +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); +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); +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); +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); +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) { +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; +static inline int ccsp_string_is_empty( const CCspString * str ) { + return str == NULL || str -> length == 0 || str -> data == NULL; } #ifdef __cplusplus diff --git a/cpp/csp/engine/c/CspStruct.h b/cpp/csp/engine/c/CspStruct.h index bada577fb..57f2fe9c7 100644 --- a/cpp/csp/engine/c/CspStruct.h +++ b/cpp/csp/engine/c/CspStruct.h @@ -227,8 +227,7 @@ CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_timedelta( CCspStructHandle s, CC * 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 ); +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) @@ -254,8 +253,7 @@ CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_enum( CCspStructHandle s, CCspStr * 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 ); +CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_struct( CCspStructHandle s, CCspStructFieldHandle field, CCspStructHandle * out_struct ); /* ============================================================================ * Field Value Getters by Name (Convenience) @@ -269,8 +267,7 @@ CSP_C_API_EXPORT CCspErrorCode ccsp_struct_get_int32_by_name( CCspStructHandle s 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 ); +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 @@ -306,8 +303,7 @@ CSP_C_API_EXPORT CCspErrorCode ccsp_struct_set_timedelta( CCspStructHandle s, CC * 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 ); +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) diff --git a/cpp/csp/engine/c/CspTime.h b/cpp/csp/engine/c/CspTime.h index b95d8a9e0..4c5a4e3c1 100644 --- a/cpp/csp/engine/c/CspTime.h +++ b/cpp/csp/engine/c/CspTime.h @@ -49,9 +49,9 @@ typedef int64_t CCspTime; #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_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, @@ -59,9 +59,9 @@ CCspDateTime ccsp_datetime_from_parts( ); /* 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); +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, @@ -70,37 +70,37 @@ void ccsp_datetime_to_parts( ); /* DateTime arithmetic */ -CCspDateTime ccsp_datetime_add(CCspDateTime dt, CCspTimeDelta delta); -CCspTimeDelta ccsp_datetime_diff(CCspDateTime a, CCspDateTime b); +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); +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); +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); +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); +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); +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); +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 } diff --git a/cpp/csp/engine/c/CspValue.h b/cpp/csp/engine/c/CspValue.h index 7d875745c..f365be6a1 100644 --- a/cpp/csp/engine/c/CspValue.h +++ b/cpp/csp/engine/c/CspValue.h @@ -33,7 +33,7 @@ typedef struct CCspTypeInfoImpl* CCspTypeHandle; * String value with ownership flag */ typedef struct { - const char* data; + const char * data; size_t length; int is_owned; /* 1 if CCspValue owns the memory, 0 if borrowed */ } CCspStringValue; @@ -42,7 +42,7 @@ typedef struct { * Array value (for CCSP_TYPE_ARRAY) */ typedef struct { - void* data; /* Pointer to array data */ + 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 */ @@ -61,7 +61,7 @@ typedef struct { * 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 */ + void * ptr; /* Opaque pointer to dialect-specific object */ int type_id; /* Dialect-specific type identifier */ } CCspDialectValue; @@ -96,122 +96,122 @@ typedef struct { /* * Initialize a CCspValue to unknown/invalid state */ -void ccsp_value_init(CCspValue* value); +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); +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); +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); +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); +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); +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); +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); +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); +void ccsp_value_set_struct( CCspValue * value, CCspStructHandle s ); /* * Set enum value */ -void ccsp_value_set_enum(CCspValue* value, int32_t ordinal, CCspEnumMetaHandle meta); +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); +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); +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); +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); +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; +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; +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); +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); +int ccsp_value_is_integer( const CCspValue * value ); #ifdef __cplusplus } diff --git a/cpp/csp/engine/c/InputAdapter.h b/cpp/csp/engine/c/InputAdapter.h index 36d519426..e7981a685 100644 --- a/cpp/csp/engine/c/InputAdapter.h +++ b/cpp/csp/engine/c/InputAdapter.h @@ -35,16 +35,16 @@ extern "C" { * ============================================================================ */ /* Handle to the CSP engine (opaque) - same as in OutputAdapter.h */ -typedef struct CCspEngineImpl* CCspEngineHandle; +typedef struct CCspEngineImpl * CCspEngineHandle; /* Handle to the internal C++ PushInputAdapter wrapper (opaque) */ -typedef struct CCspPushInputAdapterImpl* CCspPushInputAdapterHandle; +typedef struct CCspPushInputAdapterImpl * CCspPushInputAdapterHandle; /* Handle to a push batch for grouping events (opaque) */ -typedef struct CCspPushBatchImpl* CCspPushBatchHandle; +typedef struct CCspPushBatchImpl * CCspPushBatchHandle; /* Handle to a push group for synchronizing adapters (opaque) */ -typedef struct CCspPushGroupImpl* CCspPushGroupHandle; +typedef struct CCspPushGroupImpl * CCspPushGroupHandle; /* ============================================================================ * Push Mode @@ -72,7 +72,7 @@ typedef struct CCspPushInputAdapterVTable { * User data pointer passed to all callbacks. * This is typically a pointer to your adapter's state structure. */ - void* user_data; + void * user_data; /* * Called when the graph starts. @@ -85,9 +85,9 @@ typedef struct CCspPushInputAdapterVTable { * @param start_time Graph start time * @param end_time Graph end time */ - void (*start)(void* user_data, CCspEngineHandle engine, + void ( * start ) ( void * user_data, CCspEngineHandle engine, CCspPushInputAdapterHandle adapter, - CCspDateTime start_time, CCspDateTime end_time); + CCspDateTime start_time, CCspDateTime end_time ); /* * Called when the graph stops. @@ -96,7 +96,7 @@ typedef struct CCspPushInputAdapterVTable { * * @param user_data Your adapter's state */ - void (*stop)(void* user_data); + void ( * stop ) ( void * user_data ); /* * Called to destroy the adapter and free resources. @@ -104,7 +104,7 @@ typedef struct CCspPushInputAdapterVTable { * * @param user_data Your adapter's state */ - void (*destroy)(void* user_data); + void ( * destroy ) ( void * user_data ); } CCspPushInputAdapterVTable; @@ -122,19 +122,15 @@ typedef struct CCspPushInputAdapterVTable { * @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 -); +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); +CSP_C_API_EXPORT void ccsp_push_input_adapter_extern_destroy( CCspPushInputAdapterHandle adapter ); /* ============================================================================ * Push Functions (Thread-Safe) @@ -150,69 +146,61 @@ CSP_C_API_EXPORT void ccsp_push_input_adapter_extern_destroy(CCspPushInputAdapte * @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 -); +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_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_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_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_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_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_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_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_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_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_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_datetime( CCspPushInputAdapterHandle adapter, CCspDateTime value, + CCspPushBatchHandle batch ); -CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_timedelta( - CCspPushInputAdapterHandle adapter, CCspTimeDelta 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 -); +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 -); +CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_struct( CCspPushInputAdapterHandle adapter, + CCspStructHandle value, + CCspPushBatchHandle batch ); /* ============================================================================ * Push Batch Management @@ -227,19 +215,19 @@ CSP_C_API_EXPORT CCspErrorCode ccsp_push_input_adapter_push_struct( * @param engine Engine handle * @return Handle to the batch, or NULL on error */ -CSP_C_API_EXPORT CCspPushBatchHandle ccsp_push_batch_create(CCspEngineHandle engine); +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); +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); +CSP_C_API_EXPORT void ccsp_push_batch_destroy( CCspPushBatchHandle batch ); /* ============================================================================ * Push Group Management @@ -252,12 +240,12 @@ CSP_C_API_EXPORT void ccsp_push_batch_destroy(CCspPushBatchHandle batch); * * @return Handle to the group, or NULL on error */ -CSP_C_API_EXPORT CCspPushGroupHandle ccsp_push_group_create(void); +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); +CSP_C_API_EXPORT void ccsp_push_group_destroy( CCspPushGroupHandle group ); #ifdef __cplusplus } diff --git a/cpp/csp/engine/c/OutputAdapter.h b/cpp/csp/engine/c/OutputAdapter.h index c08d1e66b..ac7b04b89 100644 --- a/cpp/csp/engine/c/OutputAdapter.h +++ b/cpp/csp/engine/c/OutputAdapter.h @@ -31,13 +31,13 @@ extern "C" { * ============================================================================ */ /* Handle to the CSP engine (opaque) */ -typedef struct CCspEngineImpl* CCspEngineHandle; +typedef struct CCspEngineImpl * CCspEngineHandle; /* Handle to a time series input (opaque) */ -typedef struct CCspInputImpl* CCspInputHandle; +typedef struct CCspInputImpl * CCspInputHandle; /* Handle to the internal C++ OutputAdapter wrapper (opaque) */ -typedef struct CCspOutputAdapterImpl* CCspOutputAdapterHandle; +typedef struct CCspOutputAdapterImpl * CCspOutputAdapterHandle; /* ============================================================================ * Input access functions (for use in execute callback) @@ -46,39 +46,39 @@ typedef struct CCspOutputAdapterImpl* CCspOutputAdapterHandle; /* * Check if the input is valid (has ticked at least once) */ -CSP_C_API_EXPORT int ccsp_input_is_valid(CCspInputHandle input); +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); +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); +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); +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); +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); +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); +CSP_C_API_EXPORT CCspDateTime ccsp_input_get_last_time( CCspInputHandle input ); /* ============================================================================ * Engine access functions @@ -87,12 +87,12 @@ CSP_C_API_EXPORT CCspDateTime ccsp_input_get_last_time(CCspInputHandle input); /* * Get current engine time */ -CSP_C_API_EXPORT CCspDateTime ccsp_engine_now(CCspEngineHandle engine); +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); +CSP_C_API_EXPORT uint64_t ccsp_engine_cycle_count( CCspEngineHandle engine ); /* ============================================================================ * Output Adapter Callbacks (VTable) @@ -105,7 +105,7 @@ typedef struct CCspOutputAdapterVTable { * User data pointer passed to all callbacks. * This is typically a pointer to your adapter's state structure. */ - void* user_data; + void * user_data; /* * Called when the graph starts. @@ -116,8 +116,8 @@ typedef struct CCspOutputAdapterVTable { * @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); + void ( * start ) ( void * user_data, CCspEngineHandle engine, + CCspDateTime start_time, CCspDateTime end_time ); /* * Called when the graph stops. @@ -126,8 +126,8 @@ typedef struct CCspOutputAdapterVTable { * * @param user_data Your adapter's state */ - void (*stop)(void* user_data); - + void ( * stop ) ( void * user_data ); + /* * Called each time the input has a new value. * REQUIRED - must not be NULL. @@ -136,7 +136,7 @@ typedef struct CCspOutputAdapterVTable { * @param engine Handle to the engine * @param input Handle to the input time series */ - void (*execute)(void* user_data, CCspEngineHandle engine, CCspInputHandle input); + void ( * execute ) ( void * user_data, CCspEngineHandle engine, CCspInputHandle input ); /* * Called to destroy the adapter and free resources. @@ -144,7 +144,7 @@ typedef struct CCspOutputAdapterVTable { * * @param user_data Your adapter's state */ - void (*destroy)(void* user_data); + void ( * destroy ) ( void * user_data ); } CCspOutputAdapterVTable; @@ -163,18 +163,15 @@ typedef struct CCspOutputAdapterVTable { * 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 -); +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); +CSP_C_API_EXPORT void ccsp_output_adapter_extern_destroy( CCspOutputAdapterHandle adapter ); /* ============================================================================ * Convenience functions for common output patterns @@ -184,29 +181,28 @@ CSP_C_API_EXPORT void ccsp_output_adapter_extern_destroy(CCspOutputAdapterHandle * 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); +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); +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); +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); +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); +CSP_C_API_EXPORT CCspErrorCode ccsp_input_get_last_datetime( CCspInputHandle input, CCspDateTime * out_value ); #ifdef __cplusplus } From 104e1ad636e1f8f1b64b6c65228644e5348a9c14 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Wed, 18 Feb 2026 10:31:53 -0500 Subject: [PATCH 06/12] More formatting Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- cpp/csp/engine/AdapterManagerExtern.cpp | 28 ++---- cpp/csp/engine/AdapterManagerExtern.h | 6 +- cpp/csp/engine/DictionaryExtern.cpp | 9 +- cpp/csp/engine/OutputAdapterExtern.cpp | 44 +++++----- cpp/csp/engine/OutputAdapterExtern.h | 3 +- cpp/csp/engine/PushInputAdapterExtern.cpp | 88 +++++++------------ cpp/csp/engine/StructExtern.cpp | 47 +++++----- cpp/csp/python/PyCApiAdapters.cpp | 23 ++--- cpp/csp/python/c/PyAdapterManager.h | 10 ++- cpp/csp/python/c/PyInputAdapter.h | 8 +- cpp/csp/python/c/PyOutputAdapter.h | 8 +- examples/05_cpp/1_cpp_node/cpp/piglatin.cpp | 14 +-- .../3_cpp_adapter/cpp/counteradapterimpl.cpp | 4 +- .../cpp/ExampleManagedAdapter.c | 37 ++++---- .../cpp/ExampleManagedAdapter.h | 4 +- .../cpp/ExampleOutputAdapter.c | 35 +++----- .../cpp/ExamplePushInputAdapter.c | 57 ++++++------ .../cpp/ExamplePushInputAdapter.h | 3 +- .../4_c_api_adapter/cpp/exampleadapterimpl.c | 4 +- 19 files changed, 179 insertions(+), 253 deletions(-) diff --git a/cpp/csp/engine/AdapterManagerExtern.cpp b/cpp/csp/engine/AdapterManagerExtern.cpp index 7f134664c..2f0d068ab 100644 --- a/cpp/csp/engine/AdapterManagerExtern.cpp +++ b/cpp/csp/engine/AdapterManagerExtern.cpp @@ -93,9 +93,7 @@ DateTime AdapterManagerExtern::processNextSimTimeSlice( DateTime time ) extern "C" { -CCspAdapterManagerHandle ccsp_adapter_manager_extern_create( - CCspEngineHandle engine, - const CCspAdapterManagerVTable * vtable ) +CCspAdapterManagerHandle ccsp_adapter_manager_extern_create( CCspEngineHandle engine, const CCspAdapterManagerVTable * vtable ) { if( !engine || !vtable ) { @@ -144,10 +142,7 @@ CCspDateTime ccsp_adapter_manager_end_time( CCspAdapterManagerHandle manager ) return m -> endtime().asNanoseconds(); } -CCspOutputAdapterHandle ccsp_adapter_manager_create_output_adapter( - CCspAdapterManagerHandle manager, - CCspType input_type, - const CCspOutputAdapterVTable * vtable ) +CCspOutputAdapterHandle ccsp_adapter_manager_create_output_adapter( CCspAdapterManagerHandle manager, CCspType input_type, const CCspOutputAdapterVTable * vtable ) { if( !manager || !vtable ) { @@ -193,11 +188,7 @@ CCspOutputAdapterHandle ccsp_adapter_manager_create_output_adapter( } } -CCspErrorCode ccsp_adapter_manager_push_status( - CCspAdapterManagerHandle manager, - CCspStatusLevel level, - int64_t err_code, - const char * message ) +CCspErrorCode ccsp_adapter_manager_push_status( CCspAdapterManagerHandle manager, CCspStatusLevel level, int64_t err_code, const char * message ) { if( !manager ) { @@ -220,10 +211,7 @@ CCspErrorCode ccsp_adapter_manager_push_status( } // 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 ) +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; @@ -260,11 +248,7 @@ CCspErrorCode ccsp_managed_sim_input_adapter_push_datetime( CCspManagedSimInputA } // Push input adapter creation from manager -CCspPushInputAdapterHandle ccsp_adapter_manager_create_push_input_adapter( - CCspAdapterManagerHandle manager, - CCspType type, - CCspPushMode push_mode, - const CCspPushInputAdapterVTable * vtable ) +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 @@ -275,7 +259,7 @@ CCspPushInputAdapterHandle ccsp_adapter_manager_create_push_input_adapter( } auto * m = reinterpret_cast( manager ); - (void)m; // Currently unused - will be used when implemented + ( 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 diff --git a/cpp/csp/engine/AdapterManagerExtern.h b/cpp/csp/engine/AdapterManagerExtern.h index dcb3eebe4..c8a9209b2 100644 --- a/cpp/csp/engine/AdapterManagerExtern.h +++ b/cpp/csp/engine/AdapterManagerExtern.h @@ -20,11 +20,11 @@ class AdapterManagerExtern final : public AdapterManager AdapterManagerExtern( Engine * engine, const CCspAdapterManagerVTable & vtable ); ~AdapterManagerExtern() override; - const char* name() const override; + const char * name() const override; - void start(DateTime startTime, DateTime endTime) override; + void start( DateTime startTime, DateTime endTime ) override; void stop() override; - DateTime processNextSimTimeSlice(DateTime time) override; + DateTime processNextSimTimeSlice( DateTime time ) override; // Access for C API const CCspAdapterManagerVTable & vtable() const { return m_vtable; } diff --git a/cpp/csp/engine/DictionaryExtern.cpp b/cpp/csp/engine/DictionaryExtern.cpp index 065a1fbaf..a1bf8a208 100644 --- a/cpp/csp/engine/DictionaryExtern.cpp +++ b/cpp/csp/engine/DictionaryExtern.cpp @@ -361,8 +361,7 @@ CCspErrorCode ccsp_dictionary_get_timedelta( CCspDictionaryHandle dict, const ch } } -CCspErrorCode ccsp_dictionary_get_string( CCspDictionaryHandle dict, const char * key, - const char ** out_data, size_t * out_length ) +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 ) { @@ -395,8 +394,7 @@ CCspErrorCode ccsp_dictionary_get_string( CCspDictionaryHandle dict, const char } } -CCspErrorCode ccsp_dictionary_get_dict( CCspDictionaryHandle dict, const char * key, - CCspDictionaryHandle * out_dict ) +CCspErrorCode ccsp_dictionary_get_dict( CCspDictionaryHandle dict, const char * key, CCspDictionaryHandle * out_dict ) { if( !dict || !key || !out_dict ) { @@ -548,8 +546,7 @@ CCspTimeDelta ccsp_dictionary_get_timedelta_or( CCspDictionaryHandle dict, const } } -const char * ccsp_dictionary_get_string_or( CCspDictionaryHandle dict, const char * key, - const char * default_value, size_t * out_length ) +const char * ccsp_dictionary_get_string_or( CCspDictionaryHandle dict, const char * key, const char * default_value, size_t * out_length ) { if( !dict || !key ) { diff --git a/cpp/csp/engine/OutputAdapterExtern.cpp b/cpp/csp/engine/OutputAdapterExtern.cpp index 52a71fd05..d5896737f 100644 --- a/cpp/csp/engine/OutputAdapterExtern.cpp +++ b/cpp/csp/engine/OutputAdapterExtern.cpp @@ -89,7 +89,7 @@ CCspErrorCode ccsp_get_last_error(void) return s_lastError; } -const char* ccsp_get_last_error_message(void) +const char * ccsp_get_last_error_message(void) { return s_lastErrorMessage[0] ? s_lastErrorMessage : nullptr; } @@ -140,26 +140,26 @@ CCspType ccsp_input_get_type( CCspInputHandle 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::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; + default: return CCSP_TYPE_UNKNOWN; } } @@ -171,8 +171,8 @@ CCspDateTime ccsp_input_get_last_time( CCspInputHandle input ) } CCspErrorCode ccsp_input_get_last_string( CCspInputHandle input, - const char ** out_data, - size_t * out_length ) + const char ** out_data, + size_t * out_length ) { if( !input || !out_data || !out_length ) { diff --git a/cpp/csp/engine/OutputAdapterExtern.h b/cpp/csp/engine/OutputAdapterExtern.h index 2670f8871..d814713ed 100644 --- a/cpp/csp/engine/OutputAdapterExtern.h +++ b/cpp/csp/engine/OutputAdapterExtern.h @@ -17,8 +17,7 @@ namespace csp class OutputAdapterExtern final : public OutputAdapter { public: - OutputAdapterExtern( Engine * engine, const CspTypePtr & type, - const CCspOutputAdapterVTable & vtable ); + OutputAdapterExtern( Engine * engine, const CspTypePtr & type, const CCspOutputAdapterVTable & vtable ); ~OutputAdapterExtern() override; const char* name() const override { return "OutputAdapterExtern"; } diff --git a/cpp/csp/engine/PushInputAdapterExtern.cpp b/cpp/csp/engine/PushInputAdapterExtern.cpp index d0d92c924..a32a2830c 100644 --- a/cpp/csp/engine/PushInputAdapterExtern.cpp +++ b/cpp/csp/engine/PushInputAdapterExtern.cpp @@ -75,12 +75,9 @@ 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 ) +CCspPushInputAdapterHandle ccsp_push_input_adapter_extern_create( CCspEngineHandle engine, CCspType type, + CCspPushMode push_mode, CCspPushGroupHandle group, + const CCspPushInputAdapterVTable* vtable ) { if( !engine || !vtable ) { @@ -97,19 +94,19 @@ CCspPushInputAdapterHandle ccsp_push_input_adapter_extern_create( 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_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; + 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; @@ -119,16 +116,15 @@ CCspPushInputAdapterHandle ccsp_push_input_adapter_extern_create( csp::PushMode cspPushMode; switch( push_mode ) { - case CCSP_PUSH_MODE_LAST_VALUE: cspPushMode = csp::PushMode::LAST_VALUE; break; + 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; + 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 ); + auto * adapter = eng -> createOwnedObject( cspType, cspPushMode, grp, *vtable ); return reinterpret_cast( adapter ); } @@ -142,15 +138,14 @@ CCspPushInputAdapterHandle ccsp_push_input_adapter_extern_create( void ccsp_push_input_adapter_extern_destroy( CCspPushInputAdapterHandle adapter ) { // The adapter is owned by the engine, destruction is handled there - (void)adapter; + ( void ) adapter; } // ============================================================================ // Type-specific push functions // ============================================================================ -CCspErrorCode ccsp_push_input_adapter_push_bool( - CCspPushInputAdapterHandle adapter, int8_t value, CCspPushBatchHandle batch ) +CCspErrorCode ccsp_push_input_adapter_push_bool( CCspPushInputAdapterHandle adapter, int8_t value, CCspPushBatchHandle batch ) { if( !adapter ) { @@ -173,8 +168,7 @@ CCspErrorCode ccsp_push_input_adapter_push_bool( } } -CCspErrorCode ccsp_push_input_adapter_push_int8( - CCspPushInputAdapterHandle adapter, int8_t value, CCspPushBatchHandle batch ) +CCspErrorCode ccsp_push_input_adapter_push_int8( CCspPushInputAdapterHandle adapter, int8_t value, CCspPushBatchHandle batch ) { if( !adapter ) { @@ -197,8 +191,7 @@ CCspErrorCode ccsp_push_input_adapter_push_int8( } } -CCspErrorCode ccsp_push_input_adapter_push_uint8( - CCspPushInputAdapterHandle adapter, uint8_t value, CCspPushBatchHandle batch ) +CCspErrorCode ccsp_push_input_adapter_push_uint8( CCspPushInputAdapterHandle adapter, uint8_t value, CCspPushBatchHandle batch ) { if( !adapter ) { @@ -221,8 +214,7 @@ CCspErrorCode ccsp_push_input_adapter_push_uint8( } } -CCspErrorCode ccsp_push_input_adapter_push_int16( - CCspPushInputAdapterHandle adapter, int16_t value, CCspPushBatchHandle batch ) +CCspErrorCode ccsp_push_input_adapter_push_int16( CCspPushInputAdapterHandle adapter, int16_t value, CCspPushBatchHandle batch ) { if( !adapter ) { @@ -245,8 +237,7 @@ CCspErrorCode ccsp_push_input_adapter_push_int16( } } -CCspErrorCode ccsp_push_input_adapter_push_uint16( - CCspPushInputAdapterHandle adapter, uint16_t value, CCspPushBatchHandle batch ) +CCspErrorCode ccsp_push_input_adapter_push_uint16( CCspPushInputAdapterHandle adapter, uint16_t value, CCspPushBatchHandle batch ) { if( !adapter ) { @@ -269,8 +260,7 @@ CCspErrorCode ccsp_push_input_adapter_push_uint16( } } -CCspErrorCode ccsp_push_input_adapter_push_int32( - CCspPushInputAdapterHandle adapter, int32_t value, CCspPushBatchHandle batch ) +CCspErrorCode ccsp_push_input_adapter_push_int32( CCspPushInputAdapterHandle adapter, int32_t value, CCspPushBatchHandle batch ) { if( !adapter ) { @@ -293,8 +283,7 @@ CCspErrorCode ccsp_push_input_adapter_push_int32( } } -CCspErrorCode ccsp_push_input_adapter_push_uint32( - CCspPushInputAdapterHandle adapter, uint32_t value, CCspPushBatchHandle batch ) +CCspErrorCode ccsp_push_input_adapter_push_uint32( CCspPushInputAdapterHandle adapter, uint32_t value, CCspPushBatchHandle batch ) { if( !adapter ) { @@ -317,8 +306,7 @@ CCspErrorCode ccsp_push_input_adapter_push_uint32( } } -CCspErrorCode ccsp_push_input_adapter_push_int64( - CCspPushInputAdapterHandle adapter, int64_t value, CCspPushBatchHandle batch ) +CCspErrorCode ccsp_push_input_adapter_push_int64( CCspPushInputAdapterHandle adapter, int64_t value, CCspPushBatchHandle batch ) { if( !adapter ) { @@ -341,8 +329,7 @@ CCspErrorCode ccsp_push_input_adapter_push_int64( } } -CCspErrorCode ccsp_push_input_adapter_push_uint64( - CCspPushInputAdapterHandle adapter, uint64_t value, CCspPushBatchHandle batch ) +CCspErrorCode ccsp_push_input_adapter_push_uint64( CCspPushInputAdapterHandle adapter, uint64_t value, CCspPushBatchHandle batch ) { if( !adapter ) { @@ -365,8 +352,7 @@ CCspErrorCode ccsp_push_input_adapter_push_uint64( } } -CCspErrorCode ccsp_push_input_adapter_push_double( - CCspPushInputAdapterHandle adapter, double value, CCspPushBatchHandle batch ) +CCspErrorCode ccsp_push_input_adapter_push_double( CCspPushInputAdapterHandle adapter, double value, CCspPushBatchHandle batch ) { if( !adapter ) { @@ -389,8 +375,7 @@ CCspErrorCode ccsp_push_input_adapter_push_double( } } -CCspErrorCode ccsp_push_input_adapter_push_datetime( - CCspPushInputAdapterHandle adapter, CCspDateTime value, CCspPushBatchHandle batch ) +CCspErrorCode ccsp_push_input_adapter_push_datetime( CCspPushInputAdapterHandle adapter, CCspDateTime value, CCspPushBatchHandle batch ) { if( !adapter ) { @@ -413,8 +398,7 @@ CCspErrorCode ccsp_push_input_adapter_push_datetime( } } -CCspErrorCode ccsp_push_input_adapter_push_timedelta( - CCspPushInputAdapterHandle adapter, CCspTimeDelta value, CCspPushBatchHandle batch ) +CCspErrorCode ccsp_push_input_adapter_push_timedelta( CCspPushInputAdapterHandle adapter, CCspTimeDelta value, CCspPushBatchHandle batch ) { if( !adapter ) { @@ -437,10 +421,7 @@ CCspErrorCode ccsp_push_input_adapter_push_timedelta( } } -CCspErrorCode ccsp_push_input_adapter_push_string( - CCspPushInputAdapterHandle adapter, - const char* data, size_t length, - CCspPushBatchHandle batch ) +CCspErrorCode ccsp_push_input_adapter_push_string( CCspPushInputAdapterHandle adapter, const char* data, size_t length, CCspPushBatchHandle batch ) { if( !adapter ) { @@ -469,10 +450,7 @@ CCspErrorCode ccsp_push_input_adapter_push_string( } } -CCspErrorCode ccsp_push_input_adapter_push_struct( - CCspPushInputAdapterHandle adapter, - CCspStructHandle value, - CCspPushBatchHandle batch ) +CCspErrorCode ccsp_push_input_adapter_push_struct( CCspPushInputAdapterHandle adapter, CCspStructHandle value, CCspPushBatchHandle batch ) { // TODO: Implement struct push when struct support is complete (void)adapter; diff --git a/cpp/csp/engine/StructExtern.cpp b/cpp/csp/engine/StructExtern.cpp index 2a3a76526..3bc3496c2 100644 --- a/cpp/csp/engine/StructExtern.cpp +++ b/cpp/csp/engine/StructExtern.cpp @@ -15,26 +15,26 @@ 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::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; + default: return CCSP_TYPE_UNKNOWN; } } @@ -555,8 +555,7 @@ CCspErrorCode ccsp_struct_get_timedelta( CCspStructHandle s, CCspStructFieldHand return CCSP_OK; } -CCspErrorCode ccsp_struct_get_string( CCspStructHandle s, CCspStructFieldHandle field, - const char ** out_data, size_t * out_length ) +CCspErrorCode ccsp_struct_get_string( CCspStructHandle s, CCspStructFieldHandle field, const char ** out_data, size_t * out_length ) { if( !s || !field || !out_data || !out_length ) { @@ -614,8 +613,7 @@ CCspErrorCode ccsp_struct_get_enum( CCspStructHandle s, CCspStructFieldHandle fi return CCSP_OK; } -CCspErrorCode ccsp_struct_get_struct( CCspStructHandle s, CCspStructFieldHandle field, - CCspStructHandle * out_struct ) +CCspErrorCode ccsp_struct_get_struct( CCspStructHandle s, CCspStructFieldHandle field, CCspStructHandle * out_struct ) { if( !s || !field || !out_struct ) { @@ -743,8 +741,7 @@ CCspErrorCode ccsp_struct_get_datetime_by_name( CCspStructHandle s, const char * 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 ) +CCspErrorCode ccsp_struct_get_string_by_name( CCspStructHandle s, const char * name, const char ** out_data, size_t * out_length ) { if( !s || !name ) { diff --git a/cpp/csp/python/PyCApiAdapters.cpp b/cpp/csp/python/PyCApiAdapters.cpp index c0d662886..372db833d 100644 --- a/cpp/csp/python/PyCApiAdapters.cpp +++ b/cpp/csp/python/PyCApiAdapters.cpp @@ -37,12 +37,8 @@ static const char * const CSP_C_ADAPTER_MANAGER_CAPSULE_NAME = "csp.c.AdapterMan * 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 ) +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; @@ -55,8 +51,7 @@ static InputAdapter * c_api_push_input_adapter_creator( 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 ) ); + CCspPushInputAdapterVTable * vtable = static_cast( PyCapsule_GetPointer( capsule, CSP_C_INPUT_ADAPTER_CAPSULE_NAME ) ); if( !vtable ) CSP_THROW( ValueError, "Failed to extract VTable from capsule" ); @@ -77,8 +72,7 @@ static InputAdapter * c_api_push_input_adapter_creator( auto & cspType = pyTypeAsCspType( pyType ); /* Create the adapter using createOwnedObject */ - auto * adapter = pyengine->engine()->createOwnedObject( - cspType, pushMode, pushGroup, *vtable ); + 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. */ @@ -109,8 +103,7 @@ static OutputAdapter * c_api_output_adapter_creator( 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 ) ); + CCspOutputAdapterVTable * vtable = static_cast( PyCapsule_GetPointer( capsule, CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME ) ); if( !vtable ) CSP_THROW( ValueError, "Failed to extract VTable from capsule" ); @@ -119,8 +112,7 @@ static OutputAdapter * c_api_output_adapter_creator( auto & cspType = pyTypeAsCspType( pyInputType ); /* Create the adapter using createOwnedObject */ - auto * adapter = pyengine->engine()->createOwnedObject( - cspType, *vtable ); + 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. */ @@ -156,8 +148,7 @@ static PyObject * c_api_adapter_manager_bridge( PyObject * /* self */, PyObject 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 ) ); + 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" ); diff --git a/cpp/csp/python/c/PyAdapterManager.h b/cpp/csp/python/c/PyAdapterManager.h index 1fa6c9a9c..0647a668e 100644 --- a/cpp/csp/python/c/PyAdapterManager.h +++ b/cpp/csp/python/c/PyAdapterManager.h @@ -34,12 +34,14 @@ static inline PyObject * ccsp_py_create_adapter_manager_capsule( const CCspAdapt } /* Allocate a copy of the vtable */ - CCspAdapterManagerVTable * vtable_copy = ( CCspAdapterManagerVTable * )malloc( sizeof( CCspAdapterManagerVTable ) ); + 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 ); @@ -58,7 +60,7 @@ static inline CCspAdapterManagerVTable * ccsp_py_get_adapter_manager_vtable( PyO PyErr_SetString( PyExc_TypeError, "expected adapter manager capsule" ); return NULL; } - return ( CCspAdapterManagerVTable * )PyCapsule_GetPointer( capsule, CSP_C_ADAPTER_MANAGER_CAPSULE_NAME ); + return ( CCspAdapterManagerVTable * ) PyCapsule_GetPointer( capsule, CSP_C_ADAPTER_MANAGER_CAPSULE_NAME ); } /* @@ -67,7 +69,7 @@ static inline CCspAdapterManagerVTable * ccsp_py_get_adapter_manager_vtable( PyO */ static inline void ccsp_py_adapter_manager_capsule_destructor( PyObject * capsule ) { - CCspAdapterManagerVTable * vtable = ( CCspAdapterManagerVTable * )PyCapsule_GetPointer( capsule, CSP_C_ADAPTER_MANAGER_CAPSULE_NAME ); + CCspAdapterManagerVTable * vtable = ( CCspAdapterManagerVTable * ) PyCapsule_GetPointer( capsule, CSP_C_ADAPTER_MANAGER_CAPSULE_NAME ); if( vtable ) { if( vtable -> destroy ) @@ -95,7 +97,7 @@ static inline PyObject * ccsp_py_create_adapter_manager_capsule_owned( const CCs } /* Allocate a copy of the vtable */ - CCspAdapterManagerVTable * vtable_copy = ( CCspAdapterManagerVTable * )malloc( sizeof( CCspAdapterManagerVTable ) ); + CCspAdapterManagerVTable * vtable_copy = ( CCspAdapterManagerVTable * ) malloc( sizeof( CCspAdapterManagerVTable ) ); if( !vtable_copy ) { PyErr_NoMemory(); diff --git a/cpp/csp/python/c/PyInputAdapter.h b/cpp/csp/python/c/PyInputAdapter.h index 536e9d07f..5227e655b 100644 --- a/cpp/csp/python/c/PyInputAdapter.h +++ b/cpp/csp/python/c/PyInputAdapter.h @@ -34,7 +34,7 @@ static inline PyObject * ccsp_py_create_input_adapter_capsule( const CCspPushInp } /* Allocate a copy of the vtable */ - CCspPushInputAdapterVTable * vtable_copy = ( CCspPushInputAdapterVTable * )malloc( sizeof( CCspPushInputAdapterVTable ) ); + CCspPushInputAdapterVTable * vtable_copy = ( CCspPushInputAdapterVTable * ) malloc( sizeof( CCspPushInputAdapterVTable ) ); if( !vtable_copy ) { PyErr_NoMemory(); @@ -58,7 +58,7 @@ static inline CCspPushInputAdapterVTable * ccsp_py_get_input_adapter_vtable( PyO PyErr_SetString( PyExc_TypeError, "expected input adapter capsule" ); return NULL; } - return ( CCspPushInputAdapterVTable * )PyCapsule_GetPointer( capsule, CSP_C_INPUT_ADAPTER_CAPSULE_NAME ); + return ( CCspPushInputAdapterVTable * ) PyCapsule_GetPointer( capsule, CSP_C_INPUT_ADAPTER_CAPSULE_NAME ); } /* @@ -67,7 +67,7 @@ static inline CCspPushInputAdapterVTable * ccsp_py_get_input_adapter_vtable( PyO */ static inline void ccsp_py_input_adapter_capsule_destructor( PyObject * capsule ) { - CCspPushInputAdapterVTable * vtable = ( CCspPushInputAdapterVTable * )PyCapsule_GetPointer( capsule, CSP_C_INPUT_ADAPTER_CAPSULE_NAME ); + CCspPushInputAdapterVTable * vtable = ( CCspPushInputAdapterVTable * ) PyCapsule_GetPointer( capsule, CSP_C_INPUT_ADAPTER_CAPSULE_NAME ); if( vtable ) { if( vtable -> destroy ) @@ -95,7 +95,7 @@ static inline PyObject * ccsp_py_create_input_adapter_capsule_owned( const CCspP } /* Allocate a copy of the vtable */ - CCspPushInputAdapterVTable * vtable_copy = ( CCspPushInputAdapterVTable * )malloc( sizeof( CCspPushInputAdapterVTable ) ); + CCspPushInputAdapterVTable * vtable_copy = ( CCspPushInputAdapterVTable * ) malloc( sizeof( CCspPushInputAdapterVTable ) ); if( !vtable_copy ) { PyErr_NoMemory(); diff --git a/cpp/csp/python/c/PyOutputAdapter.h b/cpp/csp/python/c/PyOutputAdapter.h index 25b81eb79..b339a396e 100644 --- a/cpp/csp/python/c/PyOutputAdapter.h +++ b/cpp/csp/python/c/PyOutputAdapter.h @@ -34,7 +34,7 @@ static inline PyObject * ccsp_py_create_output_adapter_capsule( const CCspOutput } /* Allocate a copy of the vtable */ - CCspOutputAdapterVTable * vtable_copy = ( CCspOutputAdapterVTable * )malloc( sizeof( CCspOutputAdapterVTable ) ); + CCspOutputAdapterVTable * vtable_copy = ( CCspOutputAdapterVTable * ) malloc( sizeof( CCspOutputAdapterVTable ) ); if( !vtable_copy ) { PyErr_NoMemory(); @@ -58,7 +58,7 @@ static inline CCspOutputAdapterVTable * ccsp_py_get_output_adapter_vtable( PyObj PyErr_SetString( PyExc_TypeError, "expected output adapter capsule" ); return NULL; } - return ( CCspOutputAdapterVTable * )PyCapsule_GetPointer( capsule, CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME ); + return ( CCspOutputAdapterVTable * ) PyCapsule_GetPointer( capsule, CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME ); } /* @@ -67,7 +67,7 @@ static inline CCspOutputAdapterVTable * ccsp_py_get_output_adapter_vtable( PyObj */ static inline void ccsp_py_output_adapter_capsule_destructor( PyObject * capsule ) { - CCspOutputAdapterVTable * vtable = ( CCspOutputAdapterVTable * )PyCapsule_GetPointer( capsule, CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME ); + CCspOutputAdapterVTable * vtable = ( CCspOutputAdapterVTable * ) PyCapsule_GetPointer( capsule, CSP_C_OUTPUT_ADAPTER_CAPSULE_NAME ); if( vtable ) { if( vtable -> destroy ) @@ -95,7 +95,7 @@ static inline PyObject * ccsp_py_create_output_adapter_capsule_owned( const CCsp } /* Allocate a copy of the vtable */ - CCspOutputAdapterVTable * vtable_copy = ( CCspOutputAdapterVTable * )malloc( sizeof( CCspOutputAdapterVTable ) ); + CCspOutputAdapterVTable * vtable_copy = ( CCspOutputAdapterVTable * ) malloc( sizeof( CCspOutputAdapterVTable ) ); if( !vtable_copy ) { PyErr_NoMemory(); diff --git a/examples/05_cpp/1_cpp_node/cpp/piglatin.cpp b/examples/05_cpp/1_cpp_node/cpp/piglatin.cpp index 44865619e..be6894585 100644 --- a/examples/05_cpp/1_cpp_node/cpp/piglatin.cpp +++ b/examples/05_cpp/1_cpp_node/cpp/piglatin.cpp @@ -7,15 +7,15 @@ namespace csp::piglatin { -DECLARE_CPPNODE(piglatin) +DECLARE_CPPNODE( piglatin ) { - INIT_CPPNODE(piglatin) + INIT_CPPNODE( piglatin ) {} - TS_INPUT(std::string, x); - SCALAR_INPUT(bool, capitalize); + TS_INPUT( std::string, x ); + SCALAR_INPUT( bool, capitalize ); // ALARM(Generic, alarm); - TS_OUTPUT(std::string); + TS_OUTPUT( std::string ); START() {} @@ -34,10 +34,10 @@ DECLARE_CPPNODE(piglatin) } }; -EXPORT_CPPNODE(piglatin); +EXPORT_CPPNODE( piglatin ); } -REGISTER_CPPNODE(csp::piglatin, piglatin); +REGISTER_CPPNODE( csp::piglatin, piglatin ); static PyModuleDef _piglatin_module = { PyModuleDef_HEAD_INIT, diff --git a/examples/05_cpp/3_cpp_adapter/cpp/counteradapterimpl.cpp b/examples/05_cpp/3_cpp_adapter/cpp/counteradapterimpl.cpp index 667dbeccf..a4391c40f 100644 --- a/examples/05_cpp/3_cpp_adapter/cpp/counteradapterimpl.cpp +++ b/examples/05_cpp/3_cpp_adapter/cpp/counteradapterimpl.cpp @@ -83,9 +83,7 @@ static OutputAdapter * create_counter_output_adapter( csp::AdapterManager * mana if( !counterManager ) CSP_THROW( TypeError, "Expected CounterAdapterManager" ); - if( !PyArg_ParseTuple( args, "OO!", - &pyType, - &PyDict_Type, &pyProperties ) ) + if( !PyArg_ParseTuple( args, "OO!", &pyType, &PyDict_Type, &pyProperties ) ) CSP_THROW( PythonPassthrough, "" ); auto & cspType = pyTypeAsCspType( pyType ); diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c index 472eee723..6eed754f5 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c @@ -16,14 +16,14 @@ static const char * managed_adapter_name( void * user_data ) { - ManagedAdapterState * state = ( ManagedAdapterState * )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; + ManagedAdapterState * state = ( ManagedAdapterState * ) user_data; state -> manager = manager; state -> is_started = 1; state -> message_count = 0; @@ -42,7 +42,7 @@ static void managed_adapter_start( void * user_data, CCspAdapterManagerHandle ma static void managed_adapter_stop( void * user_data ) { - ManagedAdapterState * state = ( ManagedAdapterState * )user_data; + ManagedAdapterState * state = ( ManagedAdapterState * ) user_data; printf( "[%s] Manager stopped. Total messages: %d\n", state -> name, state -> message_count ); @@ -50,8 +50,7 @@ static void managed_adapter_stop( void * user_data ) state -> is_started = 0; } -static CCspDateTime managed_adapter_process_next_sim_time_slice( void * user_data, - CCspDateTime time ) +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; @@ -61,14 +60,14 @@ static CCspDateTime managed_adapter_process_next_sim_time_slice( void * user_dat static void managed_adapter_destroy( void * user_data ) { - ManagedAdapterState * state = ( ManagedAdapterState * )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 ) ); + ManagedAdapterState * state = ( ManagedAdapterState * ) malloc( sizeof( ManagedAdapterState ) ); if( !state ) { CCspAdapterManagerVTable empty = {0}; @@ -100,10 +99,9 @@ CCspAdapterManagerVTable example_managed_adapter_create( const char * name ) * Managed Output Adapter Callbacks * ============================================================================ */ -static void managed_output_start( void * user_data, CCspEngineHandle engine, - CCspDateTime start_time, CCspDateTime end_time ) +static void managed_output_start( void * user_data, CCspEngineHandle engine, CCspDateTime start_time, CCspDateTime end_time ) { - ManagedOutputState * state = ( ManagedOutputState * )user_data; + ManagedOutputState * state = ( ManagedOutputState * ) user_data; ( void )engine; ( void )start_time; ( void )end_time; @@ -115,15 +113,14 @@ static void managed_output_start( void * user_data, CCspEngineHandle engine, static void managed_output_stop( void * user_data ) { - ManagedOutputState * state = ( ManagedOutputState * )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 ) +static void managed_output_execute( void * user_data, CCspEngineHandle engine, CCspInputHandle input ) { - ManagedOutputState * state = ( ManagedOutputState * )user_data; + ManagedOutputState * state = ( ManagedOutputState * ) user_data; if( !ccsp_input_is_valid( input ) ) { @@ -146,7 +143,7 @@ static void managed_output_execute( void * user_data, CCspEngineHandle engine, int64_t value; if( ccsp_input_get_last_int64( input, &value ) == CCSP_OK ) { - printf( "int64: %lld\n", ( long long )value ); + printf( "int64: %lld\n", ( long long ) value ); } break; } @@ -165,7 +162,7 @@ static void managed_output_execute( void * user_data, CCspEngineHandle engine, size_t length; if( ccsp_input_get_last_string( input, &data, &length ) == CCSP_OK ) { - printf( "string: \"%.*s\"\n", ( int )length, data ); + printf( "string: \"%.*s\"\n", ( int ) length, data ); } break; } @@ -180,15 +177,13 @@ static void managed_output_execute( void * user_data, CCspEngineHandle engine, static void managed_output_destroy( void * user_data ) { - ManagedOutputState * state = ( ManagedOutputState * )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 example_managed_output_adapter_create( ManagedAdapterState * shared_state, const char * topic ) { CCspOutputAdapterVTable vtable = {0}; @@ -197,7 +192,7 @@ CCspOutputAdapterVTable example_managed_output_adapter_create( return vtable; } - ManagedOutputState * state = ( ManagedOutputState * )malloc( sizeof( ManagedOutputState ) ); + ManagedOutputState * state = ( ManagedOutputState * ) malloc( sizeof( ManagedOutputState ) ); if( !state ) { 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 index 4e2733f7c..ee67703b5 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h @@ -71,9 +71,7 @@ CCspAdapterManagerVTable example_managed_adapter_create( const char * name ); * Returns: * VTable for use with ccsp_adapter_manager_create_output_adapter */ -CCspOutputAdapterVTable example_managed_output_adapter_create( - ManagedAdapterState * shared_state, - const char * topic ); +CCspOutputAdapterVTable example_managed_output_adapter_create( ManagedAdapterState * shared_state, const char * topic ); #ifdef __cplusplus } diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c index e6c7e1527..b8d4a7eca 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c @@ -23,26 +23,23 @@ typedef struct { * Callback implementations * ============================================================================ */ -static void example_output_start( void * user_data, CCspEngineHandle engine, - CCspDateTime start_time, CCspDateTime end_time ) +static void example_output_start( void * user_data, CCspEngineHandle engine, CCspDateTime start_time, CCspDateTime end_time ) { - ExampleOutputAdapterState * state = ( ExampleOutputAdapterState * )user_data; - ( void )engine; + 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 ); + 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; + ExampleOutputAdapterState * state = ( ExampleOutputAdapterState * ) user_data; dprintf( state -> fd, "[ExampleOutputAdapter] Stopped.\n" ); } -static void example_output_execute( void * user_data, CCspEngineHandle engine, - CCspInputHandle input ) +static void example_output_execute( void * user_data, CCspEngineHandle engine, CCspInputHandle input ) { - ExampleOutputAdapterState * state = ( ExampleOutputAdapterState * )user_data; + ExampleOutputAdapterState * state = ( ExampleOutputAdapterState * ) user_data; CCspDateTime now = ccsp_engine_now( engine ); CCspType type = ccsp_input_get_type( input ); @@ -56,8 +53,7 @@ static void example_output_execute( void * user_data, CCspEngineHandle engine, 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" ); + dprintf( state -> fd, "%s[%lld] bool: %s\n", prefix, ( long long ) now, val ? "true" : "false" ); } break; } @@ -66,8 +62,7 @@ static void example_output_execute( void * user_data, CCspEngineHandle engine, 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 ); + dprintf( state -> fd, "%s[%lld] int64: %lld\n", prefix, ( long long ) now, ( long long ) val ); } break; } @@ -76,7 +71,7 @@ static void example_output_execute( void * user_data, CCspEngineHandle engine, 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 ); + dprintf( state -> fd, "%s[%lld] double: %f\n", prefix, ( long long ) now, val ); } break; } @@ -86,8 +81,7 @@ static void example_output_execute( void * user_data, CCspEngineHandle engine, 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 ); + dprintf( state -> fd, "%s[%lld] string: %.*s\n", prefix, ( long long ) now, ( int ) len, data ); } break; } @@ -96,20 +90,19 @@ static void example_output_execute( void * user_data, CCspEngineHandle engine, 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 ); + 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 ); + 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; + ExampleOutputAdapterState * state = ( ExampleOutputAdapterState * ) user_data; if( state ) { if( state -> owns_prefix && state -> prefix ) diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.c b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.c index fa30b741a..2da91ba59 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.c +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.c @@ -36,7 +36,7 @@ typedef struct { static void * int_adapter_thread( void * arg ) { - IntAdapterState * state = ( IntAdapterState * )arg; + IntAdapterState * state = ( IntAdapterState * ) arg; while( state -> running ) { @@ -59,29 +59,27 @@ 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; + 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 ); + 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 ); + 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; + IntAdapterState * state = ( IntAdapterState * ) user_data; state -> running = 0; #ifdef _WIN32 @@ -91,13 +89,12 @@ static void int_adapter_stop( void * user_data ) pthread_join( state -> thread, NULL ); #endif - fprintf( stdout, "[ExampleIntInputAdapter] Stopped after %lld values\n", - ( long long )state -> counter ); + 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; + IntAdapterState * state = ( IntAdapterState * ) user_data; free( state ); } @@ -139,7 +136,7 @@ typedef struct { static void * double_adapter_thread( void * arg ) { - DoubleAdapterState * state = ( DoubleAdapterState * )arg; + DoubleAdapterState * state = ( DoubleAdapterState * ) arg; while( state -> running ) { @@ -161,17 +158,16 @@ 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; + 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 ); + 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 @@ -181,7 +177,7 @@ static void double_adapter_start( void * user_data, CCspEngineHandle engine, static void double_adapter_stop( void * user_data ) { - DoubleAdapterState * state = ( DoubleAdapterState * )user_data; + DoubleAdapterState * state = ( DoubleAdapterState * ) user_data; state -> running = 0; #ifdef _WIN32 @@ -196,7 +192,7 @@ static void double_adapter_stop( void * user_data ) static void double_adapter_destroy( void * user_data ) { - DoubleAdapterState * state = ( DoubleAdapterState * )user_data; + DoubleAdapterState * state = ( DoubleAdapterState * ) user_data; free( state ); } @@ -204,7 +200,7 @@ CCspPushInputAdapterVTable example_push_input_adapter_create_double( int interva { CCspPushInputAdapterVTable vtable = {0}; - DoubleAdapterState * state = ( DoubleAdapterState * )malloc( sizeof( DoubleAdapterState ) ); + DoubleAdapterState * state = ( DoubleAdapterState * ) malloc( sizeof( DoubleAdapterState ) ); if( !state ) { return vtable; @@ -235,10 +231,10 @@ 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; + StringAdapterState * state = ( StringAdapterState * ) user_data; + ( void ) engine; + ( void ) start_time; + ( void ) end_time; state -> adapter = adapter; fprintf( stdout, "[ExampleStringInputAdapter] Started\n" ); @@ -252,16 +248,15 @@ static void string_adapter_stop( void * user_data ) static void string_adapter_destroy( void * user_data ) { - StringAdapterState * state = ( StringAdapterState * )user_data; + StringAdapterState * state = ( StringAdapterState * ) user_data; free( state ); } -CCspPushInputAdapterVTable example_push_input_adapter_create_string( - ExampleStringCallback get_string, void * user_data ) +CCspPushInputAdapterVTable example_push_input_adapter_create_string( ExampleStringCallback get_string, void * user_data ) { CCspPushInputAdapterVTable vtable = {0}; - StringAdapterState * state = ( StringAdapterState * )malloc( sizeof( StringAdapterState ) ); + StringAdapterState * state = ( StringAdapterState * ) malloc( sizeof( StringAdapterState ) ); if( !state ) { 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 index 7b7f77c9a..533ec5b02 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h @@ -37,8 +37,7 @@ CCspPushInputAdapterVTable example_push_input_adapter_create_double( int interva * @return VTable structure */ typedef const char * ( *ExampleStringCallback )( void * user_data ); -CCspPushInputAdapterVTable example_push_input_adapter_create_string( - ExampleStringCallback get_string, void * user_data ); +CCspPushInputAdapterVTable example_push_input_adapter_create_string( ExampleStringCallback get_string, void * user_data ); #ifdef __cplusplus } diff --git a/examples/05_cpp/4_c_api_adapter/cpp/exampleadapterimpl.c b/examples/05_cpp/4_c_api_adapter/cpp/exampleadapterimpl.c index 2b72e65fe..461d0a9fe 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/exampleadapterimpl.c +++ b/examples/05_cpp/4_c_api_adapter/cpp/exampleadapterimpl.c @@ -127,8 +127,8 @@ static PyObject * create_input_adapter_py( PyObject * self, PyObject * args, PyO */ static PyObject * create_output_adapter_py( PyObject * self, PyObject * args, PyObject * kwargs ) { - ( void )self; - ( void )kwargs; /* Not used when called from wiring layer */ + ( void ) self; + ( void ) kwargs; /* Not used when called from wiring layer */ /* When called from output_adapter_def: * args = (mgr, engine, scalars) From a385bd6cf9561d050279cdc807f84eada5a5a7d4 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Wed, 18 Feb 2026 10:43:18 -0500 Subject: [PATCH 07/12] More cleanup of C++ examples, cleanup some leftover utcnow Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- README.md | 4 +-- cpp/csp/core/Platform.h | 26 ++++++++-------- docs/wiki/get-started/More-with-CSP.md | 6 ++-- docs/wiki/how-tos/Write-C-API-Adapters.md | 14 ++++++--- examples/05_cpp/3_cpp_adapter/README.md | 7 +++-- .../3_cpp_adapter/counteradapter/__main__.py | 30 +++---------------- .../counteradapter/test_counteradapter.py | 5 ++-- examples/05_cpp/4_c_api_adapter/README.md | 6 +++- examples/07_end_to_end/earthquake.ipynb | 9 +++--- examples/07_end_to_end/mta.ipynb | 7 +++-- examples/07_end_to_end/seismic_waveform.ipynb | 19 ++++++------ examples/07_end_to_end/wikimedia.ipynb | 5 ++-- .../07_end_to_end/wikimedia_inference.ipynb | 7 +++-- 13 files changed, 71 insertions(+), 74 deletions(-) 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 00950bc8f..fb3ecad73 100644 --- a/cpp/csp/core/Platform.h +++ b/cpp/csp/core/Platform.h @@ -17,30 +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 // 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) +#define CSP_C_API_EXPORT __declspec( dllexport ) #else -#define CSP_C_API_EXPORT __declspec(dllimport) +#define CSP_C_API_EXPORT __declspec( dllimport ) #endif -#define START_PACKED __pragma( pack(push, 1) ) -#define END_PACKED __pragma( pack(pop)) +#define START_PACKED __pragma( pack( push, 1 ) ) +#define END_PACKED __pragma( pack( pop ) ) -#define NO_INLINE __declspec(noinline) +#define NO_INLINE __declspec( noinline ) inline tm * localtime_r( const time_t * timep, tm * result ) { @@ -103,14 +103,14 @@ inline uint8_t ffs(uint64_t n) #define CSPTYPESIMPL_EXPORT // C API export macro - ensure symbols have default visibility for external use -#define CSP_C_API_EXPORT __attribute__((visibility("default"))) +#define CSP_C_API_EXPORT __attribute__( ( visibility( "default" ) ) ) -#define DLL_LOCAL __attribute__ ((visibility ("hidden"))) +#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/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 index 2620104dc..b2b57581c 100644 --- a/docs/wiki/how-tos/Write-C-API-Adapters.md +++ b/docs/wiki/how-tos/Write-C-API-Adapters.md @@ -769,8 +769,11 @@ _managed_output_def = output_adapter_def( #### Use Managed Adapters in Your Graph ```python +from datetime import timedelta + import csp -from datetime import datetime, timedelta +from csp.utils.datetime import utc_now + from my_adapter import MyAdapterManager @csp.graph @@ -782,14 +785,17 @@ def my_graph(): data = mgr.subscribe(int, interval_ms=100) mgr.publish(data) -csp.run(my_graph, starttime=datetime.utcnow(), endtime=timedelta(seconds=10)) +csp.run(my_graph, starttime=utc_now(), endtime=timedelta(seconds=10)) ``` #### Use in Your Graph ```python +from datetime import timedelta + import csp -from datetime import datetime, timedelta +from csp.utils.datetime import utc_now + from my_adapter import my_input, LogAdapter @csp.graph @@ -797,7 +803,7 @@ def my_graph(): data = my_input(int, interval_ms=100) LogAdapter(data, prefix="[MyApp] ") -csp.run(my_graph, starttime=datetime.utcnow(), endtime=timedelta(seconds=10)) +csp.run(my_graph, starttime=utc_now(), endtime=timedelta(seconds=10)) ``` ## See Also diff --git a/examples/05_cpp/3_cpp_adapter/README.md b/examples/05_cpp/3_cpp_adapter/README.md index 421508b65..ac4b33d62 100644 --- a/examples/05_cpp/3_cpp_adapter/README.md +++ b/examples/05_cpp/3_cpp_adapter/README.md @@ -102,7 +102,9 @@ After building, you can use the adapter in Python: ```python import csp -from datetime import datetime, timedelta +from csp.utils.datetime import utc_now + +from datetime import timedelta from counteradapter import CounterAdapterManager @@ -121,8 +123,7 @@ def my_graph(): mgr.publish(data) # Run for 2 seconds in realtime mode -csp.run(my_graph, starttime=datetime.utcnow(), - endtime=datetime.utcnow() + timedelta(seconds=2), realtime=True) +csp.run(my_graph, starttime=utc_now(), endtime=timedelta(seconds=2), realtime=True) ``` Or run the example directly: diff --git a/examples/05_cpp/3_cpp_adapter/counteradapter/__main__.py b/examples/05_cpp/3_cpp_adapter/counteradapter/__main__.py index 6d0bfeaf6..d8fb560f5 100644 --- a/examples/05_cpp/3_cpp_adapter/counteradapter/__main__.py +++ b/examples/05_cpp/3_cpp_adapter/counteradapter/__main__.py @@ -15,34 +15,12 @@ PYTHONPATH=build:$PYTHONPATH python example.py """ -import sys -from datetime import datetime, timedelta +from datetime import timedelta import csp +from counteradapter import CounterAdapterManager from csp import ts - - -# For development/testing, add the build directory to path -# In production, you would install the module properly -def setup_path(): - """Add the build directory to the Python path if needed.""" - import os - - build_dir = os.path.join(os.path.dirname(__file__), "build") - if os.path.exists(build_dir) and build_dir not in sys.path: - sys.path.insert(0, build_dir) - - -setup_path() - -# Import the adapter after setting up the path -try: - from counteradapter import CounterAdapterManager -except ImportError: - print("Error: Could not import counteradapter.") - print("Make sure you have built the C++ extension module.") - print("See the build instructions in README.md") - sys.exit(1) +from csp.utils.datetime import utc_now @csp.node @@ -84,7 +62,7 @@ def main(): print("=" * 40) # Run the graph for 2 seconds - start = datetime.utcnow() + start = utc_now() end = start + timedelta(seconds=2) result = csp.run(counter_graph, starttime=start, endtime=end, realtime=True) diff --git a/examples/05_cpp/3_cpp_adapter/counteradapter/test_counteradapter.py b/examples/05_cpp/3_cpp_adapter/counteradapter/test_counteradapter.py index 7081f7268..c971d48f8 100644 --- a/examples/05_cpp/3_cpp_adapter/counteradapter/test_counteradapter.py +++ b/examples/05_cpp/3_cpp_adapter/counteradapter/test_counteradapter.py @@ -1,12 +1,13 @@ -from datetime import datetime, timedelta +from datetime import timedelta import csp +from csp.utils.datetime import utc_now from .__main__ import counter_graph def test_counteradapter(): - start = datetime.utcnow() + start = utc_now() end = start + timedelta(seconds=2) result = csp.run(counter_graph, starttime=start, endtime=end, realtime=True) diff --git a/examples/05_cpp/4_c_api_adapter/README.md b/examples/05_cpp/4_c_api_adapter/README.md index fce571e1d..16e49512c 100644 --- a/examples/05_cpp/4_c_api_adapter/README.md +++ b/examples/05_cpp/4_c_api_adapter/README.md @@ -148,7 +148,11 @@ my_output_adapter = output_adapter_def( ## 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 @@ -159,7 +163,7 @@ def my_graph(): # Output to stdout with a prefix example_output(data, prefix="[MyApp] ") -csp.run(my_graph, starttime=datetime.utcnow(), endtime=timedelta(seconds=5)) +csp.run(my_graph, starttime=utc_now(), endtime=timedelta(seconds=5)) ``` ## API Headers 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.\")" ] From b033a52fa423a57c0f90ea9ba65db7a39c2876d5 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:28:26 -0500 Subject: [PATCH 08/12] Style enhancements, example cmake simplifications Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- .gitignore | 1 + CMakeLists.txt | 5 + examples/05_cpp/3_cpp_adapter/CMakeLists.txt | 89 ++-------- .../05_cpp/4_c_api_adapter/CMakeLists.txt | 157 +++--------------- .../cpp/ExampleManagedAdapter.c | 3 +- .../cpp/ExampleManagedAdapter.h | 6 - .../cpp/ExampleOutputAdapter.c | 3 +- .../cpp/ExampleOutputAdapter.h | 2 - .../cpp/ExamplePushInputAdapter.c | 3 +- .../cpp/ExamplePushInputAdapter.h | 2 - .../4_c_api_adapter/cpp/exampleadapterimpl.c | 7 +- 11 files changed, 54 insertions(+), 224 deletions(-) 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/examples/05_cpp/3_cpp_adapter/CMakeLists.txt b/examples/05_cpp/3_cpp_adapter/CMakeLists.txt index fadf87648..ff0331238 100644 --- a/examples/05_cpp/3_cpp_adapter/CMakeLists.txt +++ b/examples/05_cpp/3_cpp_adapter/CMakeLists.txt @@ -2,91 +2,28 @@ cmake_minimum_required(VERSION 3.20.0) project(csp-example-counter-adapter 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 - 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 - 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) - 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") - - 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) - 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}) # The Counter Adapter library add_library(counteradapter SHARED diff --git a/examples/05_cpp/4_c_api_adapter/CMakeLists.txt b/examples/05_cpp/4_c_api_adapter/CMakeLists.txt index d9cb7af26..4ee44e52e 100644 --- a/examples/05_cpp/4_c_api_adapter/CMakeLists.txt +++ b/examples/05_cpp/4_c_api_adapter/CMakeLists.txt @@ -1,156 +1,49 @@ cmake_minimum_required(VERSION 3.20.0) -project(csp-example-counter-adapter VERSION "0.0.1") +project(csp-example-c-api-adapter 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 - 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 - 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) - 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") - - 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) - 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}) # Example C adapters demonstrating the C ABI interface - -set(C_EXAMPLE_HEADER_FILES - cpp/ExamplePushInputAdapter.h - cpp/ExampleOutputAdapter.h - cpp/ExampleManagedAdapter.h -) - -set(C_EXAMPLE_SOURCE_FILES - cpp/ExamplePushInputAdapter.c - cpp/ExampleOutputAdapter.c - cpp/ExampleManagedAdapter.c -) - -add_library(csp_c_example_adapter STATIC ${C_EXAMPLE_SOURCE_FILES} ${C_EXAMPLE_HEADER_FILES}) - -# Include the engine c headers -target_include_directories(csp_c_example_adapter PUBLIC - ${CMAKE_SOURCE_DIR}/cpp -) - -# Set C standard -set_target_properties(csp_c_example_adapter PROPERTIES - PUBLIC_HEADER "${C_EXAMPLE_HEADER_FILES}" - C_STANDARD 11 - C_STANDARD_REQUIRED ON +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) -# On non-Windows, we need pthread for the example threaded adapters if(NOT WIN32) target_link_libraries(csp_c_example_adapter pthread) endif() -install(TARGETS csp_c_example_adapter - LIBRARY DESTINATION exampleadapter/lib -) +install(TARGETS csp_c_example_adapter LIBRARY DESTINATION exampleadapter/lib) # Python bindings for example C adapters - -# This creates a Python extension module 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) -# Link with the example adapter library -target_link_libraries(_exampleadapterimpl PRIVATE - csp_c_example_adapter -) - -# Include directories -target_include_directories(_exampleadapterimpl PRIVATE - ${CMAKE_SOURCE_DIR}/cpp - ${Python_INCLUDE_DIRS} -) - -# Set output name without lib prefix -set_target_properties(_exampleadapterimpl PROPERTIES - PREFIX "" - C_STANDARD 11 - C_STANDARD_REQUIRED ON -) - -# Install to the Python package location -install(TARGETS _exampleadapterimpl - LIBRARY DESTINATION exampleadapter -) +install(TARGETS _exampleadapterimpl LIBRARY DESTINATION exampleadapter) diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c index 6eed754f5..c64899584 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.c @@ -5,11 +5,12 @@ * multiple input/output adapters, similar to KafkaAdapterManager. */ -#include "ExampleManagedAdapter.h" #include #include #include +#include "ExampleManagedAdapter.h" + /* ============================================================================ * Adapter Manager Callbacks * ============================================================================ */ diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h index ee67703b5..80ef13ced 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleManagedAdapter.h @@ -78,9 +78,3 @@ CCspOutputAdapterVTable example_managed_output_adapter_create( ManagedAdapterSta #endif #endif /* _IN_CSP_ADAPTERS_C_EXAMPLE_MANAGED_ADAPTER_H */ - -#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 index b8d4a7eca..e9104eec0 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.c @@ -3,7 +3,6 @@ * * This demonstrates how to implement an output adapter using the C ABI interface. */ -#include "ExampleOutputAdapter.h" #include #include #include @@ -12,6 +11,8 @@ #include #include +#include "ExampleOutputAdapter.h" + /* Adapter state structure */ typedef struct { char * prefix; /* Prefix to print before each value */ diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.h b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.h index 3c8362782..6437700de 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.h +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExampleOutputAdapter.h @@ -36,5 +36,3 @@ CCspOutputAdapterVTable example_output_adapter_create_fd( int fd, const char * p #endif /* _IN_CSP_ADAPTERS_C_EXAMPLE_OUTPUT_ADAPTER_H */ -#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 index 2da91ba59..42a66a3a3 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.c +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.c @@ -4,13 +4,14 @@ * 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 "ExamplePushInputAdapter.h" #include #include #include #include #include +#include "ExamplePushInputAdapter.h" + #ifdef _WIN32 #include #else diff --git a/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h index 533ec5b02..7bec8527a 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h +++ b/examples/05_cpp/4_c_api_adapter/cpp/ExamplePushInputAdapter.h @@ -44,5 +44,3 @@ CCspPushInputAdapterVTable example_push_input_adapter_create_string( ExampleStri #endif #endif /* _IN_CSP_ADAPTERS_C_EXAMPLE_PUSH_INPUT_ADAPTER_H */ - -#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 index 461d0a9fe..53e6ec2ad 100644 --- a/examples/05_cpp/4_c_api_adapter/cpp/exampleadapterimpl.c +++ b/examples/05_cpp/4_c_api_adapter/cpp/exampleadapterimpl.c @@ -13,9 +13,10 @@ #include #include #include -#include -#include -#include + +#include "ExampleOutputAdapter.h" +#include "ExamplePushInputAdapter.h" +#include "ExampleManagedAdapter.h" /* * _example_adapter_manager(engine, properties: dict) -> capsule From 3c6811b1bfaf788cce7b30b18ac344fb1f172268 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:17:35 -0500 Subject: [PATCH 09/12] Simplify cmake in 1 and 2 Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- examples/05_cpp/1_cpp_node/CMakeLists.txt | 101 ++------------- .../2_cpp_node_with_struct/CMakeLists.txt | 116 ++++-------------- 2 files changed, 35 insertions(+), 182 deletions(-) diff --git a/examples/05_cpp/1_cpp_node/CMakeLists.txt b/examples/05_cpp/1_cpp_node/CMakeLists.txt index f2081229e..76c38a009 100644 --- a/examples/05_cpp/1_cpp_node/CMakeLists.txt +++ b/examples/05_cpp/1_cpp_node/CMakeLists.txt @@ -2,103 +2,28 @@ 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/ - 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 cpp/piglatin.cpp) target_link_libraries(piglatin ${CSP_LIBRARY}) 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 e01726600..6dac745aa 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,39 @@ 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/ - 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 cpp/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) From 19c8f7627c331a6688ce841d9f2f18a9a174fc87 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:44:08 -0500 Subject: [PATCH 10/12] Fix example flags on mac Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- examples/05_cpp/1_cpp_node/CMakeLists.txt | 5 +++++ examples/05_cpp/2_cpp_node_with_struct/CMakeLists.txt | 5 +++++ examples/05_cpp/3_cpp_adapter/CMakeLists.txt | 5 +++++ examples/05_cpp/4_c_api_adapter/CMakeLists.txt | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/examples/05_cpp/1_cpp_node/CMakeLists.txt b/examples/05_cpp/1_cpp_node/CMakeLists.txt index 76c38a009..ed02fdd7e 100644 --- a/examples/05_cpp/1_cpp_node/CMakeLists.txt +++ b/examples/05_cpp/1_cpp_node/CMakeLists.txt @@ -18,6 +18,11 @@ find_library(CSP_LIBRARY NAMES _cspimpl.so PATHS "${CSP_LIB_DIR}" NO_DEFAULT_PAT # 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) 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 6dac745aa..2b530a82b 100644 --- a/examples/05_cpp/2_cpp_node_with_struct/CMakeLists.txt +++ b/examples/05_cpp/2_cpp_node_with_struct/CMakeLists.txt @@ -18,6 +18,11 @@ find_library(CSP_LIBRARY NAMES _cspimpl.so PATHS "${CSP_LIB_DIR}" NO_DEFAULT_PAT # 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) diff --git a/examples/05_cpp/3_cpp_adapter/CMakeLists.txt b/examples/05_cpp/3_cpp_adapter/CMakeLists.txt index ff0331238..2ce665712 100644 --- a/examples/05_cpp/3_cpp_adapter/CMakeLists.txt +++ b/examples/05_cpp/3_cpp_adapter/CMakeLists.txt @@ -18,6 +18,11 @@ find_library(CSP_LIBRARY NAMES _cspimpl.so PATHS "${CSP_LIB_DIR}" NO_DEFAULT_PAT # 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) diff --git a/examples/05_cpp/4_c_api_adapter/CMakeLists.txt b/examples/05_cpp/4_c_api_adapter/CMakeLists.txt index 4ee44e52e..e9d8906c0 100644 --- a/examples/05_cpp/4_c_api_adapter/CMakeLists.txt +++ b/examples/05_cpp/4_c_api_adapter/CMakeLists.txt @@ -18,6 +18,11 @@ find_library(CSP_LIBRARY NAMES _cspimpl.so PATHS "${CSP_LIB_DIR}" NO_DEFAULT_PAT # 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) From c845c31c1a1942a010d4e36e3721c6740117585d Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:36:50 -0500 Subject: [PATCH 11/12] Run dep tests on 3.11 Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- .github/workflows/build.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 94dc66303..8f4e8072b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -744,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 }} From 61ead66291fef2c6f3a4cc8725cb9b03263c3baf Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:17:38 -0500 Subject: [PATCH 12/12] finish rust integration Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- .../5_c_api_adapter_rust/src/bindings.rs | 52 +++++++++-- .../5_c_api_adapter_rust/src/input_adapter.rs | 88 +++++++++++++++---- .../src/output_adapter.rs | 83 +++++++++++++---- 3 files changed, 181 insertions(+), 42 deletions(-) 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 index 6c1781669..7f3b2ed62 100644 --- a/examples/05_cpp/5_c_api_adapter_rust/src/bindings.rs +++ b/examples/05_cpp/5_c_api_adapter_rust/src/bindings.rs @@ -158,11 +158,53 @@ pub struct CCspAdapterManagerVTable { // We use weak linking / dynamic_lookup on macOS. // ============================================================================ -// Function pointer types for CSP C API -pub type FnCcspEngineNow = unsafe extern "C" fn(engine: CCspEngineHandle) -> CCspDateTime; -pub type FnCcspInputGetType = unsafe extern "C" fn(input: CCspInputHandle) -> CCspType; -pub type FnCcspInputGetLastInt64 = unsafe extern "C" fn(input: CCspInputHandle, out: *mut i64) -> CCspErrorCode; -pub type FnCcspPushInt64 = unsafe extern "C" fn(adapter: CCspPushInputAdapterHandle, value: i64, batch: *mut c_void) -> CCspErrorCode; +// ============================================================================ +// 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 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 index b55a523ba..6ef59fa5f 100644 --- 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 @@ -3,21 +3,24 @@ //! This module demonstrates how to implement a CSP push input adapter in Rust //! using the C API FFI bindings. //! -//! Note: The actual data pushing requires the CSP C API symbols to be available -//! at runtime. When built standalone, the callbacks are implemented but the -//! push functionality is stubbed. +//! 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::sync::atomic::{AtomicBool, AtomicI64, Ordering}; +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::{ - CCspDateTime, CCspEngineHandle, CCspPushInputAdapterHandle, + ccsp_push_input_adapter_push_int64, CCspDateTime, CCspEngineHandle, + CCspPushInputAdapterHandle, }; /// Push input adapter that generates incrementing counter values. /// -/// This adapter would spawn a background thread when started that periodically +/// 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 @@ -29,9 +32,11 @@ pub struct RustInputAdapter { /// Flag to signal thread to stop running: Arc, - /// Push adapter handle (set by start callback) - #[allow(dead_code)] - adapter_handle: Option, + /// Push adapter handle (stored as AtomicPtr for thread-safe access) + adapter_handle: Arc>, + + /// Thread handle + thread_handle: Option>, } impl RustInputAdapter { @@ -41,36 +46,83 @@ impl RustInputAdapter { interval_ms: if interval_ms > 0 { interval_ms } else { 100 }, counter: Arc::new(AtomicI64::new(0)), running: Arc::new(AtomicBool::new(false)), - adapter_handle: None, + adapter_handle: Arc::new(AtomicPtr::new(ptr::null_mut())), + thread_handle: None, } } /// Start the adapter. /// - /// In a full implementation with CSP C API symbols linked, this would - /// spawn a background thread that calls ccsp_push_input_adapter_push_int64. + /// Spawns a background thread that pushes integer values at the configured + /// interval using the CSP C API. pub fn start(&mut self, adapter: CCspPushInputAdapterHandle) { - self.adapter_handle = Some(adapter); + // 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] Started with interval {} ms (adapter handle: {:?})", + "[RustInputAdapter] Starting with interval {} ms (adapter handle: {:?})", self.interval_ms, adapter ); - // Note: In a full implementation, we would spawn a thread here that - // calls ccsp_push_input_adapter_push_int64 to push values. - // This requires the CSP C API symbols to be available at runtime. + // 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] Stopped after {} values", + "[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); + } + } } } 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 index 6f878d597..be96d2d49 100644 --- 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 @@ -3,17 +3,21 @@ //! This module demonstrates how to implement a CSP output adapter in Rust //! using the C API FFI bindings. //! -//! Note: The actual value retrieval requires the CSP C API symbols to be available -//! at runtime. When built standalone, the execute callback logs that it was invoked -//! but cannot access the actual values. +//! 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_void; +use std::ffi::{c_char, c_void}; +use std::slice; +use std::str; use crate::bindings::{ - CCspDateTime, CCspEngineHandle, CCspInputHandle, + 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 stdout. +/// 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. @@ -54,24 +58,65 @@ impl RustOutputAdapter { /// # Safety /// /// The engine and input handles must be valid. - /// - /// Note: In a full implementation with CSP C API symbols linked, this would - /// call ccsp_engine_now, ccsp_input_get_type, and ccsp_input_get_last_* to - /// retrieve and print the actual values. pub unsafe fn execute(&mut self, engine: CCspEngineHandle, input: CCspInputHandle) { let prefix = self.prefix.as_deref().unwrap_or(""); self.count += 1; - // Note: Without CSP C API symbols, we can only log that execute was called. - // In a full implementation, we would call: - // let now = ccsp_engine_now(engine); - // let input_type = ccsp_input_get_type(input); - // ccsp_input_get_last_int64/double/bool/string(input, &mut value) - eprintln!( - "{}[RustOutputAdapter] execute called (count={}, engine={:?}, input={:?})", - prefix, self.count, engine, input - ); + // 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); + } + } } }