diff --git a/.gitignore b/.gitignore index a065ab030..122759bec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ include +target/ *.tgz *.tar.gz *.o @@ -26,3 +27,4 @@ install build .vscode .vs +Cargo.lock \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index fea1ae623..bd6417dbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,8 @@ option(GRAVITY_USE_EXTERNAL_PROTOBUF "Download, Build and Use an externally down option(GRAVITY_USE_EXTERNAL_ZEROMQ "Download, build and use and externally downloaded ZeroMQ build") option(GRAVITY_USE_EXTERNAL_SPDLOG "Download, build and use an externally downloaded Spdlog build") option(BUILD_EXAMPLES_TESTS "Build Gravity examples and tests" ON) +option(BUILD_LIBRARY_ONLY "Only build the libraries, without components or tests") +option(BUILD_STATIC_LIBRARIES "Build libraries to link statically (useful for linking with Rust)") option(SKIP_JAVA "Skip building Gravity Java wrapper") option(SKIP_PYTHON "Skip building Gravity Python wrapper") set(JAVA_HOME "" CACHE PATH "Path to JDK to use") @@ -42,6 +44,11 @@ endif() set(PUBLIC_STAGING_DIR ${CMAKE_BINARY_DIR}/staging) macro(add_external_protobuf) + if (NOT BUILD_STATIC_LIBRARIES) + set(BUILD_SHARED_LIBS_PROTO ON) + else() + set(BUILD_SHARED_LIBS_PROTO OFF) + endif() ExternalProject_Add( protobuf_external SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/protobuf" @@ -53,7 +60,7 @@ macro(add_external_protobuf) LOG_OUTPUT_ON_FAILURE ON CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PUBLIC_STAGING_DIR} - -Dprotobuf_BUILD_SHARED_LIBS:BOOL=ON + -Dprotobuf_BUILD_SHARED_LIBS:BOOL=${BUILD_SHARED_LIBS_PROTO} -Dprotobuf_MSVC_STATIC_RUNTIME:BOOL=OFF -Dprotobuf_BUILD_TESTS:BOOL=OFF -Dprotobuf_BUILD_EXAMPLES:BOOL=OFF @@ -78,7 +85,7 @@ macro(add_external_libzmq) CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PUBLIC_STAGING_DIR} -DBUILD_SHARED:BOOL=ON - -DBUILD_STATIC:BOOL=OFF + -DBUILD_STATIC:BOOL=${BUILD_STATIC_LIBRARIES} -DZMQ_BUILD_TESTS:BOOL=OFF -DBUILD_TESTS:BOOL=OFF BUILD_COMMAND cmake --build . --config $<$:debug>$<$:release> @@ -266,11 +273,13 @@ if (NOT depends) set(GRAVITY_ROOT "${CMAKE_INSTALL_PREFIX}") set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH_ALT_SEP}) - include(CTest) - add_subdirectory(test) + if (NOT BUILD_LIBRARY_ONLY) + include(CTest) + add_subdirectory(test) - set(CPACK_GENERATOR "TGZ") - include(CPack) + set(CPACK_GENERATOR "TGZ") + include(CPack) + endif() message(STATUS "No external libraries - doing a toplevel build") else() @@ -286,6 +295,8 @@ else() -DSKIP_JAVA=${SKIP_JAVA} -DSKIP_PYTHON=${SKIP_PYTHON} -DZMQ_HOME=${ZMQ_HOME} + -DBUILD_LIBRARY_ONLY=${BUILD_LIBRARY_ONLY} + -DBUILD_STATIC_LIBRARIES=${BUILD_STATIC_LIBRARIES} -DGRAVITY_USE_EXTERNAL_PROTOBUF=${GRAVITY_USING_EXTERNAL_PROTOBUF} -DGRAVITY_USE_EXTERNAL_ZEROMQ=${GRAVITY_USING_EXTERNAL_ZEROMQ} -DGRAVITY_USE_EXTERNAL_SPDLOG=${GRAVITY_USING_EXTERNAL_SPDLOG} @@ -306,7 +317,7 @@ else() - if (BUILD_EXAMPLES_TESTS) + if (BUILD_EXAMPLES_TESTS AND NOT BUILD_LIBRARY_ONLY) ExternalProject_Add( gravity_external_examples_tests DEPENDS gravity_external diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..8a7fa06d0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "gravity" +version = "0.1.0" +edition = "2024" +description = "Rust bindings for the Gravity software framework" +readme = "README.md" +license-file = "LICENSE" +repository = "https://github.com/astrauc/gravity.git" +keywords = ["FFI", "C++", "Gravity"] +exclude = ["src/api/rust/target"] +build = "src/api/rust/build.rs" + +[lib] +path = "src/api/rust/src/lib.rs" + +[dependencies] +cxx = "1.0.158" +protobuf = "3.7.2" + +[build-dependencies] +cxx-build = "1.0.158" +cmake = "0.1" +protobuf-codegen = "3.7.2" + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 12b682db4..7dee76697 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,6 +25,7 @@ endif() add_subdirectory(keyvalue_parser) add_subdirectory(api/protobufs) add_subdirectory(api/cpp) + if (NOT SKIP_JAVA) add_subdirectory(api/java/src/swig) add_subdirectory(api/java/src/cpp) @@ -32,12 +33,14 @@ if (NOT SKIP_JAVA) add_subdirectory(api/MATLAB) endif() -add_subdirectory(components/cpp/ServiceDirectory) -add_subdirectory(components/cpp/Archiver) -add_subdirectory(components/cpp/ConfigServer) -add_subdirectory(components/cpp/LogRecorder) -add_subdirectory(components/cpp/Playback) -add_subdirectory(components/cpp/Relay) +if (NOT BUILD_LIBRARY_ONLY) + add_subdirectory(components/cpp/ServiceDirectory) + add_subdirectory(components/cpp/Archiver) + add_subdirectory(components/cpp/ConfigServer) + add_subdirectory(components/cpp/LogRecorder) + add_subdirectory(components/cpp/Playback) + add_subdirectory(components/cpp/Relay) +endif() if (NOT SKIP_PYTHON) add_subdirectory(api/python/src/swig) diff --git a/src/api/cpp/CMakeLists.txt b/src/api/cpp/CMakeLists.txt index fa3849b22..fcd5bdc3f 100644 --- a/src/api/cpp/CMakeLists.txt +++ b/src/api/cpp/CMakeLists.txt @@ -104,8 +104,11 @@ set(SRCS "${CMAKE_CURRENT_LIST_DIR}/Utility.cpp") - -add_library(${PROTO_LIB_NAME} SHARED ${PROTO_SRCS} ${PROTO_HDRS}) +if (NOT BUILD_STATIC_LIBRARIES) + add_library(${PROTO_LIB_NAME} SHARED ${PROTO_SRCS} ${PROTO_HDRS}) +else() + add_library(${PROTO_LIB_NAME} STATIC ${PROTO_SRCS} ${PROTO_HDRS}) +endif() target_link_libraries(${PROTO_LIB_NAME} PUBLIC protobuf::libprotobuf) add_dependencies(${PROTO_LIB_NAME} ${LIB_NAME}_protos) install(TARGETS ${PROTO_LIB_NAME} EXPORT ${PROJECT_NAME}-targets ARCHIVE DESTINATION lib RUNTIME DESTINATION bin) @@ -118,7 +121,12 @@ endif() add_definitions(-DGRAVITY_EXPORTS -D_USRDLL) include_directories("${CMAKE_BINARY_DIR}") -add_library(${LIB_NAME} SHARED ${SRCS}) +if (NOT BUILD_STATIC_LIBRARIES) + add_library(${LIB_NAME} SHARED ${SRCS}) +else() + add_library(${LIB_NAME} STATIC ${SRCS}) +endif() + add_dependencies(${LIB_NAME} ${PROTO_LIB_NAME}) gravity_add_dependency(${LIB_NAME}) target_include_directories(${LIB_NAME} INTERFACE @@ -160,7 +168,8 @@ if (GIT_EXE) message(STATUS "${GIT_EXE} FAILED: ${STATUS}") else() message(STATUS "${GIT_EXE} SUCCESS: ${OUTPUT2}") - add_custom_command(TARGET ${LIB_NAME} POST_BUILD COMMAND "${GIT_EXE}" describe --tag --dirty > "${CMAKE_BINARY_DIR}/VERSION.txt") + add_custom_command(TARGET ${LIB_NAME} POST_BUILD COMMAND "${GIT_EXE}" describe --tag --dirty > "${CMAKE_BINARY_DIR}/VERSION.txt" + || echo "0.0.0-unknown" > "${CMAKE_BINARY_DIR}/VERSION.txt") INSTALL(FILES "${CMAKE_BINARY_DIR}/VERSION.txt" DESTINATION .) endif() endif() diff --git a/src/api/rust/.gitignore b/src/api/rust/.gitignore new file mode 100644 index 000000000..76ef7cf0b --- /dev/null +++ b/src/api/rust/.gitignore @@ -0,0 +1,5 @@ +target/ +build/ +Cargo.lock +*.log +Testing/ \ No newline at end of file diff --git a/src/api/rust/README.md b/src/api/rust/README.md new file mode 100644 index 000000000..3c99a4efb --- /dev/null +++ b/src/api/rust/README.md @@ -0,0 +1,46 @@ +# Rust for Gravity + +A Rust API for gravity + +## Usage + +### Dependencies + +**Requires cargo 1.85 or later** +On linux, this can be easily installed through the following: +```sh +sudo apt install rustup +rustup update stable +``` + +Rustup will automatically install the latest stable release of rust compiler. + + +Before you add it to your ```Cargo.toml```, ensure that you have all the Gravity dependencies installed, specified in the [Gravity Build Guide](https://github.com/aphysci/gravity/wiki/GravitySetup). + +Not all are necessary, just g++, cmake, bison, flex. + +In linux: +```sh +sudo apt install cmake g++ bison flex +``` +Using cmake version 4.x.x or later does not work with Gravity. apt installs version 3.28 which has been tested with Gravity and the Cargo build. + +Then, add the crate to your Cargo.toml: +```toml +[dependencies] +gravity = { git = "REPOSITORY_URL" } +``` + +You are done! Now you can use Gravity from your Rust application! + +> [!NOTE] +> Using Gravity this way does not create/run the ServiceDirectory. Follow the [Gravity Build Guide](https://github.com/aphysci/gravity/wiki/GravitySetup) to set that up. +> + +## Tests +To run the unit tests, you need a Service Directory running. Then simply run ``` cargo test ```. As long you are not in the examples folder, the folder you run it in does not matter. (If you are in the examples it will run the specific example) + +## Runnable examples + +Once there is a ServiceDirectory setup, go into the example directory you want to run and ``` cargo run ``` \ No newline at end of file diff --git a/src/api/rust/bridge/RustFutureResponse.cpp b/src/api/rust/bridge/RustFutureResponse.cpp new file mode 100644 index 000000000..923e98c2a --- /dev/null +++ b/src/api/rust/bridge/RustFutureResponse.cpp @@ -0,0 +1,16 @@ +#include "RustFutureResponse.h" + +namespace gravity { + + std::shared_ptr rustNewFutureResponse(const char* arrayPtr, int size) + { + return std::shared_ptr(new FutureResponse(arrayPtr, size)); + } + + void rustSetResponse(const std::unique_ptr& fr, const std::unique_ptr& response) + { + fr->setResponse(*response); + } + + +} // namespace gravity \ No newline at end of file diff --git a/src/api/rust/bridge/RustFutureResponse.h b/src/api/rust/bridge/RustFutureResponse.h new file mode 100644 index 000000000..75000bb71 --- /dev/null +++ b/src/api/rust/bridge/RustFutureResponse.h @@ -0,0 +1,21 @@ +#ifndef RUSTFUTURERESPONSE_H_ +#define RUSTFUTURERESPONSE_H_ + + +#include + + + +namespace gravity +{ + /* Future response things*/ + std::shared_ptr rustNewFutureResponse(const char* arrayPtr, int size); + + void rustSetResponse(const std::unique_ptr& fr, const std::unique_ptr& response); + + + +} // namespace gravity + + +#endif \ No newline at end of file diff --git a/src/api/rust/bridge/RustGravityDataProduct.cpp b/src/api/rust/bridge/RustGravityDataProduct.cpp new file mode 100644 index 000000000..ceb6ec3cf --- /dev/null +++ b/src/api/rust/bridge/RustGravityDataProduct.cpp @@ -0,0 +1,195 @@ +#include "RustGravityDataProduct.h" + +namespace gravity { + + std::unique_ptr newGravityDataProduct() + { + return std::unique_ptr(new GravityDataProduct()); + } + + std::unique_ptr newGravityDataProduct(const std::string& dataProductId) + { + return std::unique_ptr(new GravityDataProduct(dataProductId)); + } + std::unique_ptr newGravityDataProduct(const char* arrayPtr, int size) + { + return std::unique_ptr(new GravityDataProduct((const void*) arrayPtr, size)); + } + + uint64_t rustGetGravityTimestamp(const std::unique_ptr& gdp) + { + return gdp->getGravityTimestamp(); + } + + uint64_t rustGetReceivedTimestamp(const std::unique_ptr& gdp) + { + return gdp->getReceivedTimestamp(); + } + + std::unique_ptr rustGetDataProductID(const std::unique_ptr& gdp) + { + return std::unique_ptr(new std::string(gdp->getDataProductID())); + } + + void rustSetSoftwareVersion(const std::unique_ptr& gdp, const std::string& softwareVersion) + { + gdp->setSoftwareVersion(softwareVersion); + } + + std::unique_ptr rustGetSoftwareVersion(const std::unique_ptr& gdp) + { + return std::unique_ptr(new std::string(gdp->getSoftwareVersion())); + } + + void rustSetData(const std::unique_ptr& gdp, const char* data, int size) + { + (*gdp).setData(data, size); + } + + void rustSetDataProto(const std::unique_ptr &gdp, const char* data, int size, const std::string& dataType) { + gdp->setData(data, size); + gdp->setProtocol("protobuf2"); + gdp->setTypeName(dataType); + } + + const char * rustGetData(const std::unique_ptr& gdp) + { + int size = gdp->getDataSize(); + char * data = (char *) malloc(sizeof(char) * size + 1); + gdp->getData(data, size); + return data; + } + + int rustGetDataSize(const std::unique_ptr& gdp) + { + return gdp->getDataSize(); + } + + int rustGetSize(const std::unique_ptr& gdp) + { + return gdp->getSize(); + } + + void rustParseFromArray(const std::unique_ptr& gdp, const char* arrayPtr, int size) + { + gdp->parseFromArray(arrayPtr, size); + } + + std::unique_ptr> rustSerializeToArray(const std::unique_ptr& gdp) { + int size = gdp->getSize(); + void * ptr = malloc(sizeof(char) * size + 1); + char * it = (char *) ptr; + gdp->serializeToArray(ptr); + std::vector ret; + for (int i = 0; i < size; i++) { + ret.push_back(*it); + it++; + } + free(ptr); + return std::unique_ptr>(new std::vector(ret)); + + } + + std::unique_ptr rustGDPGetComponentID(const std::unique_ptr& gdp) + { + return std::unique_ptr(new std::string(gdp->getComponentId())); + } + + + + bool rustIsFutureResponse(const std::unique_ptr& gdp) { + return gdp->isFutureResponse(); + } + + bool rustIsCachedDataProduct(const std::unique_ptr& gdp) + { + return gdp->isCachedDataproduct(); + } + + void rustSetIsCachedDataProduct(const std::unique_ptr& gdp, bool cached) + { + gdp->setIsCachedDataproduct(cached); + } + + std::unique_ptr rustGetFutureSocketURL(const std::unique_ptr& gdp) + { + return std::unique_ptr(new std::string(gdp->getFutureSocketUrl())); + } + + void rustSetTimestamp(const std::unique_ptr& gdp, uint32_t ts) + { + gdp->setTimestamp(ts); + } + + void rustSetReceivedTimestamp(const std::unique_ptr& gdp, uint32_t ts) + { + gdp->setReceivedTimestamp(ts); + } + + void rustSetComponentId(const std::unique_ptr& gdp, const std::string& componentId) + { + std::string cid(componentId); + gdp->setComponentId(cid); + } + + void rustSetDomain(const std::unique_ptr& gdp, const std::string& domain) + { + gdp->setDomain(domain); + } + + bool rustIsRelayedDataProduct(const std::unique_ptr& gdp) + { + return gdp->isRelayedDataproduct(); + } + + void rustSetIsRelayedDataProduct(const std::unique_ptr& gdp, bool relayed) + { + gdp->setIsRelayedDataproduct(relayed); + } + + void rustSetProtocol(const std::unique_ptr& gdp, const std::string& protocol) + { + gdp->setProtocol(protocol); + } + + std::unique_ptr rustGetProtocol(const std::unique_ptr& gdp) + { + return std::unique_ptr(new std::string(gdp->getProtocol())); + } + + void rustSetTypeName(const std::unique_ptr& gdp, const std::string& dataType) + { + gdp->setTypeName(dataType); + } + + std::unique_ptr rustGetTypeName(const std::unique_ptr& gdp) + { + return std::unique_ptr(new std::string(gdp->getTypeName())); + } + + uint32_t rustGetRegistrationTime(const std::unique_ptr& gdp) + { + return gdp->getRegistrationTime(); + } + + void rustSetRegistrationTime(const std::unique_ptr& gdp, uint32_t ts) + { + gdp->setRegistrationTime(ts); + } + + + std::unique_ptr copyGravityDataProduct(const GravityDataProduct& gdp) + { + return std::unique_ptr(new GravityDataProduct(gdp)); + } + + std::shared_ptr copyGravityDataProductShared(const GravityDataProduct& gdp) + { + return std::shared_ptr(new GravityDataProduct(gdp)); + } + + + void rustFree(const char * data) { + free((void*) data); + } +} // namespace gravity \ No newline at end of file diff --git a/src/api/rust/bridge/RustGravityDataProduct.h b/src/api/rust/bridge/RustGravityDataProduct.h new file mode 100644 index 000000000..6fe736b18 --- /dev/null +++ b/src/api/rust/bridge/RustGravityDataProduct.h @@ -0,0 +1,89 @@ +#ifndef RUSTGRAVITYDATAPRODUCT_H_ +#define RUSTGRAVITYDATAPRODUCT_H_ + + +#include + +#include + +namespace gravity +{ + std::unique_ptr newGravityDataProduct(); + + std::unique_ptr newGravityDataProduct(const std::string& dataProductId); + + std::unique_ptr newGravityDataProduct(const char *arrayPtr, int size); + + uint64_t rustGetGravityTimestamp(const std::unique_ptr& gdp); + + uint64_t rustGetReceivedTimestamp(const std::unique_ptr& gdp); + + std::unique_ptr rustGetDataProductID(const std::unique_ptr& gdp); + + void rustSetSoftwareVersion(const std::unique_ptr& gdp, const std::string& softwareVersion); + + std::unique_ptr rustGetSoftwareVersion(const std::unique_ptr& gdp); + + void rustSetData(const std::unique_ptr &gdp, const char *data, int size); + + void rustSetDataProto(const std::unique_ptr &gdp, const char* data, int size, const std::string& dataType); + + const char * rustGetData(const std::unique_ptr& gdp); + + int rustGetDataSize(const std::unique_ptr& gdp); + + int rustGetSize(const std::unique_ptr& gdp); + + void rustParseFromArray(const std::unique_ptr& gdp, const char * arrayPtr, int size); + + std::unique_ptr> rustSerializeToArray(const std::unique_ptr& gdp); + + std::unique_ptr rustGDPGetComponentID(const std::unique_ptr& gdp); + + std::unique_ptr rustGetDomain(const std::unique_ptr& gdp); + + bool rustIsFutureResponse(const std::unique_ptr& gdp); + + bool rustIsCachedDataProduct(const std::unique_ptr& gdp); + + void rustSetIsCachedDataProduct(const std::unique_ptr& gdp, bool cached); + + std::unique_ptr rustGetFutureSocketURL(const std::unique_ptr& gdp); + + void rustSetTimestamp(const std::unique_ptr& gdp, uint32_t ts); + + void rustSetReceivedTimestamp(const std::unique_ptr& gdp, uint32_t ts); + + void rustSetComponentId(const std::unique_ptr& gdp, const std::string& componentId); + + void rustSetDomain(const std::unique_ptr& gdp, const std::string& domain); + + bool rustIsRelayedDataProduct(const std::unique_ptr& gdp); + + void rustSetIsRelayedDataProduct(const std::unique_ptr& gdp, bool relayed); + + void rustSetProtocol(const std::unique_ptr& gdp, const std::string& protocol); + + std::unique_ptr rustGetProtocol(const std::unique_ptr& gdp); + + void rustSetTypeName(const std::unique_ptr& gdp, const std::string& dataType); + + std::unique_ptr rustGetTypeName(const std::unique_ptr& gdp); + + uint32_t rustGetRegistrationTime(const std::unique_ptr& gdp); + + void rustSetRegistrationTime(const std::unique_ptr& gdp, uint32_t ts); + + + + std::unique_ptr copyGravityDataProduct(const GravityDataProduct& gdp); + + std::shared_ptr copyGravityDataProductShared(const GravityDataProduct& gdp); + + + void rustFree(const char * data); + +} // namespace gravity + + +#endif \ No newline at end of file diff --git a/src/api/rust/bridge/RustGravityHeartbeatListener.cpp b/src/api/rust/bridge/RustGravityHeartbeatListener.cpp new file mode 100644 index 000000000..05e8b7afe --- /dev/null +++ b/src/api/rust/bridge/RustGravityHeartbeatListener.cpp @@ -0,0 +1,34 @@ +#include "RustGravityHeartbeatListener.h" +#include "gravity/src/api/rust/src/ffi.rs.h" + +namespace gravity +{ + void RustHeartbeatListener::MissedHeartbeat(std::string componentID, + int64_t microsecond_to_last_heartbeat, int64_t& interval_in_microseconds) + { + this->missed(componentID, microsecond_to_last_heartbeat, interval_in_microseconds, this->listenerPtr); + } + void RustHeartbeatListener::ReceivedHeartbeat(std::string componentID, int64_t& interval_in_microseconds) + { + this->received(componentID, interval_in_microseconds, this->listenerPtr); + } + RustHeartbeatListener::RustHeartbeatListener(rust::Fn missed, + rust::Fn received, + ListenerWrap * listenerPtr) : listenerPtr(listenerPtr) + { + this->received = received; + this->missed = missed; + } + + std::unique_ptr rustNewHeartbeatListener( + rust::Fn missed, + rust::Fn received, + ListenerWrap * listenerPtr + ) + { + return std::unique_ptr(new RustHeartbeatListener(missed, received, listenerPtr)); + } + + +} // namespace gravity + diff --git a/src/api/rust/bridge/RustGravityHeartbeatListener.h b/src/api/rust/bridge/RustGravityHeartbeatListener.h new file mode 100644 index 000000000..10f873028 --- /dev/null +++ b/src/api/rust/bridge/RustGravityHeartbeatListener.h @@ -0,0 +1,39 @@ +#ifndef RUSTGRAVITYHEARTBEATLISTENER_H_ +#define RUSTGRAVITYHEARTBEATLISTENER_H_ + +#include + +#include +#include "rust/cxx.h" + +struct ListenerWrap; + +namespace gravity +{ + class RustHeartbeatListener : public GravityHeartbeatListener { + private: + rust::Fn missed; + rust::Fn received; + ListenerWrap * listenerPtr; + public: + RustHeartbeatListener(rust::Fn missed, + rust::Fn received, + ListenerWrap * listenerPtr + ); + virtual void MissedHeartbeat(std::string componentID, + int64_t microsecond_to_last_heartbeat, int64_t& interval_in_microseconds); + virtual void ReceivedHeartbeat(std::string componentID, int64_t& interval_in_microseconds); + + }; + + std::unique_ptr rustNewHeartbeatListener( + rust::Fn missed, + rust::Fn received, + ListenerWrap * listenerPtr + ); + +} // namespace gravity + + +#endif \ No newline at end of file diff --git a/src/api/rust/bridge/RustGravityNode.cpp b/src/api/rust/bridge/RustGravityNode.cpp new file mode 100644 index 000000000..dc35763cc --- /dev/null +++ b/src/api/rust/bridge/RustGravityNode.cpp @@ -0,0 +1,309 @@ +#include "RustGravityNode.h" + +namespace gravity { + +//function for rust to be able to call constructor + std::unique_ptr newGravityNode(){ + + return std::unique_ptr(new GravityNode()); + } + std::unique_ptr newGravityNodeId(const std::string& componentId) + { + return std::unique_ptr(new GravityNode(componentId)); + } + + GravityReturnCode rustInit(const std::unique_ptr& gn, const std::string& componentID) + { + return gn->init(componentID); + } + GravityReturnCode rustInit(const std::unique_ptr& gn) + { + return gn->init(); + } + + void rustWaitForExit(const std::unique_ptr& gn) + { + gn->waitForExit(); + } + GravityReturnCode rustSubscribe(const std::unique_ptr& gn, const std::string& dataProductID, + const std::unique_ptr& subscriber) + { + return gn->subscribe(dataProductID, *subscriber); + } + GravityReturnCode rustSubscribe(const std::unique_ptr& gn, const std::string& dataProductID, + const std::unique_ptr& subscriber, const std::string& filter) + { + return gn->subscribe(dataProductID, *subscriber, filter); + } + GravityReturnCode rustSubscribe(const std::unique_ptr& gn, const std::string& dataProductID, + const std::unique_ptr& subscriber, const std::string& filter, + const std::string& domain) + { + return gn->subscribe(dataProductID, *subscriber, filter, domain); + } + GravityReturnCode rustSubscribe(const std::unique_ptr& gn, const std::string& dataProductID, + const std::unique_ptr& subscriber, const std::string& filter, + const std::string& domain, bool recieveLastCachedValue) + { + return gn->subscribe(dataProductID, *subscriber, filter, domain, recieveLastCachedValue); + } + GravityReturnCode rustUnsubscribe(const std::unique_ptr& gn, const std::string& dataProductID, + const std::unique_ptr& subscriber) + { + return gn->unsubscribe(dataProductID, *subscriber); + } + + GravityReturnCode rustPublish(const std::unique_ptr &gn, const std::unique_ptr &gdp) + { + return gn->publish(*gdp); + } + GravityReturnCode rustPublishFilter(const std::unique_ptr& gn, + const std::unique_ptr& gdp, const std::string& filterText) + { + return gn->publish(*gdp, filterText); + } + GravityReturnCode rustPublishTimestamp(const std::unique_ptr& gn, + const std::unique_ptr& gdp, + const std::string& filterText, uint64_t timestamp) + { + return gn->publish(*gdp, filterText, timestamp); + } + GravityReturnCode rustSubscribersExist(const std::unique_ptr& gn, const std::string& dataProductID, + bool& hasSubscribers) + { + return gn->subscribersExist(dataProductID, hasSubscribers); + } + + GravityReturnCode rustRequestAsync(const std::unique_ptr& gn, const std::string& serviceID, + const std::unique_ptr& dataProduct, + const std::unique_ptr& requestor) + { + return gn->request(serviceID, *dataProduct, *requestor); + } + + GravityReturnCode rustRequestAsyncRequestID(const std::unique_ptr& gn, const std::string& serviceID, + const std::unique_ptr& dataProduct, + const std::unique_ptr& requestor, + const std::string& requestID) + { + return gn->request(serviceID, *dataProduct, *requestor, requestID); + } + + GravityReturnCode rustRequestAsyncTimeout(const std::unique_ptr& gn, const std::string& serviceID, + const std::unique_ptr& dataProduct, + const std::unique_ptr& requestor, + const std::string& requestID, int timeout_milliseconds) + { + return gn->request(serviceID, *dataProduct, *requestor, requestID, timeout_milliseconds); + } + + GravityReturnCode rustRequestAsyncDomain(const std::unique_ptr& gn, const std::string& serviceID, + const std::unique_ptr& dataProduct, + const std::unique_ptr& requestor, + const std::string& requestID, int timeout_milliseconds, + const std::string& domain) + { + return gn->request(serviceID, *dataProduct, *requestor, requestID, timeout_milliseconds, domain); + } + + std::shared_ptr rustRequestSync(const std::unique_ptr& gn, + const std::string& serviceID, + const std::unique_ptr& request) + { + return gn->request(serviceID, *request); + } + + std::shared_ptr rustRequestSyncTimeout(const std::unique_ptr& gn, + const std::string& serviceID, + const std::unique_ptr& request, + int timeout_milliseconds) + { + return gn->request(serviceID, *request, timeout_milliseconds); + } + + std::shared_ptr rustRequestSyncDomain(const std::unique_ptr& gn, + const std::string& serviceID, + const std::unique_ptr& request, + int timeout_milliseconds, const std::string& domain) + { + return gn->request(serviceID, *request, timeout_milliseconds, domain); + } + + GravityReturnCode rustStartHeartbeat(const std::unique_ptr& gn, int64_t interval_in_microseconds) + { + return gn->startHeartbeat(interval_in_microseconds); + } + + GravityReturnCode rustStopHeartbeat(const std::unique_ptr& gn) + { + return gn->stopHeartbeat(); + } + + std::unique_ptr rustGetStringParam(const std::unique_ptr& gn, const std::string& key, const std::string& default_value){ + return std::unique_ptr(new std::string(gn->getStringParam(key, default_value))); + } + int rustGetIntParam(const std::unique_ptr& gn, const std::string& key, int default_value) + { + return gn->getIntParam(key, default_value); + } + double rustGetFloatParam(const std::unique_ptr& gn, const std::string& key, double default_value) + { + return gn->getFloatParam(key, default_value); + } + bool rustGetBoolParam(const std::unique_ptr& gn, const std::string & key, bool default_value) + { + return gn->getBoolParam(key, default_value); + } + std::unique_ptr rustGetComponentID(const std::unique_ptr& gn) { + return std::unique_ptr(new std::string(gn->getComponentID())); + } + + GravityReturnCode rustRegisterDataProduct(const std::unique_ptr& gn, const std::string& dataProductID, GravityTransportType transportType) + { + return gn->registerDataProduct(dataProductID, transportType); + } + + GravityReturnCode rustRegisterDataProduct(const std::unique_ptr& gn, const std::string & dataProductID, GravityTransportType transportType, bool cacheLastValue) + { + return gn->registerDataProduct(dataProductID, transportType, cacheLastValue); + } + + GravityReturnCode rustUnregisterDataProduct(const std::unique_ptr& gn, const std::string& dataProductID) + { + return gn->unregisterDataProduct(dataProductID); + } + + GravityReturnCode rustRegisterService(const std::unique_ptr& gn, const std::string& serviceID, + GravityTransportType transportType, + const std::unique_ptr& server) + { + return gn->registerService(serviceID, transportType, *server); + } + + GravityReturnCode rustUnregisterService(const std::unique_ptr& gn, const std::string& serviceID) + { + return gn->unregisterService(serviceID); + } + + GravityReturnCode rustRegisterHeartbeatListener(const std::unique_ptr& gn, + const std::string& componentID, int64_t interval_in_microseconds, + const std::unique_ptr& listener) + { + return gn->registerHeartbeatListener(componentID, interval_in_microseconds, *listener); + } + + GravityReturnCode rustRegisterHeartbeatListenerDomain(const std::unique_ptr& gn, + const std::string& componentID, + int64_t interval_in_microseconds, + const std::unique_ptr& listener, + const std::string& domain) + { + return gn->registerHeartbeatListener(componentID, interval_in_microseconds, *listener, domain); + } + + GravityReturnCode rustUnregisterHeartbeatListener(const std::unique_ptr& gn, + const std::string& componentID) + { + return gn->unregisterHeartbeatListener(componentID); + } + + GravityReturnCode rustUnregisterHeartbeatListenerDomain(const std::unique_ptr& gn, const std::string& componentID, const std::string& domain) + { + return gn->unregisterHeartbeatListener(componentID, domain); + } + + GravityReturnCode rustRegisterRelay(const std::unique_ptr& gn, const std::string& dataProductID, + const std::unique_ptr& subscriber, bool localOnly, + GravityTransportType transportType) + { + return gn->registerRelay(dataProductID, *subscriber, localOnly, transportType); + } + + GravityReturnCode rustRegisterRelayCache(const std::unique_ptr& gn, const std::string& dataProductID, + const std::unique_ptr& subscriber, bool localOnly, + GravityTransportType transportType, bool cacheLastValue) + { + return gn->registerRelay(dataProductID, *subscriber, localOnly, transportType, cacheLastValue); + } + + GravityReturnCode rustUnregisterRelay(const std::unique_ptr& gn, const std::string& dataProductID, + const std::unique_ptr& subscriber) + { + return gn->unregisterRelay(dataProductID, *subscriber); + } + + std::unique_ptr rustGetCodeString(const std::unique_ptr &gn, GravityReturnCode code) + { + return std::unique_ptr(new std::string(gn->getCodeString(code))); + } + + std::unique_ptr rustGetIP(const std::unique_ptr &gn) + { + return std::unique_ptr(new std::string(gn->getIP())); + } + + std::unique_ptr rustGetDomain(const std::unique_ptr& gn) + { + return std::unique_ptr(new std::string(gn->getDomain())); + } + + std::shared_ptr rustCreateFutureResponse(const std::unique_ptr& gn) + { + return gn->createFutureResponse(); + } + + GravityReturnCode rustSendFutureResponse(const std::unique_ptr& gn, + const std::shared_ptr& futureResponse) + { + return gn->sendFutureResponse(*futureResponse); + } + + GravityReturnCode rustSetSubscriptionTimeoutMonitor(const std::unique_ptr& gn, + const std::string& dataProductID, + const std::unique_ptr& monitor, + int milliSecondTimeout) + { + return gn->setSubscriptionTimeoutMonitor(dataProductID, *monitor, milliSecondTimeout); + } + + GravityReturnCode rustSetSubscriptionTimeoutMonitorFilter(const std::unique_ptr& gn, + const std::string& dataProductID, + const std::unique_ptr& monitor, + int milliSecondTimeout, const std::string& filter) + { + return gn->setSubscriptionTimeoutMonitor(dataProductID, *monitor, milliSecondTimeout, filter); + } + + GravityReturnCode rustSetSubscriptionTimeoutMonitorDomain(const std::unique_ptr& gn, + const std::string& dataProductID, + const std::unique_ptr& monitor, + int milliSecondTimeout, + const std::string& filter, const std::string& domain) + { + return gn->setSubscriptionTimeoutMonitor(dataProductID, *monitor, milliSecondTimeout, filter, domain); + } + + GravityReturnCode rustClearSubscriptionTimeoutMonitor(const std::unique_ptr& gn, + const std::string& dataProductID, + const std::unique_ptr& monitor) + { + return gn->clearSubscriptionTimeoutMonitor(dataProductID, *monitor); + } + + GravityReturnCode rustClearSubscriptionTimeoutMonitorFilter(const std::unique_ptr& gn, + const std::string& dataProductID, + const std::unique_ptr& monitor, + const std::string& filter) + { + return gn->clearSubscriptionTimeoutMonitor(dataProductID, *monitor, filter); + } + + GravityReturnCode rustClearSubscriptionTimeoutMonitorDomain(const std::unique_ptr& gn, + const std::string& dataProductID, + const std::unique_ptr& monitor, + const std::string& filter, const std::string& domain) + { + return gn->clearSubscriptionTimeoutMonitor(dataProductID, *monitor, filter, domain); + } + + } // namespace gravity \ No newline at end of file diff --git a/src/api/rust/bridge/RustGravityNode.h b/src/api/rust/bridge/RustGravityNode.h new file mode 100644 index 000000000..5216840c1 --- /dev/null +++ b/src/api/rust/bridge/RustGravityNode.h @@ -0,0 +1,133 @@ +#ifndef RUSTGRAVITYNODE_H_ +#define RUSTGRAVITYNODE_H_ + + +#include +#include +#include "RustGravitySubscriber.h" +#include "RustGravityRequestor.h" +#include "RustGravityHeartbeatListener.h" +#include "RustGravitySubscriptionMonitor.h" +#include "RustGravityServiceProvider.h" + +namespace gravity { + + std::unique_ptr newGravityNode(); + + std::unique_ptr newGravityNodeId(const std::string& componentId); + + GravityReturnCode rustInit(const std::unique_ptr& gn); + + GravityReturnCode rustInit(const std::unique_ptr &gn, const std::string &componentID); + + void rustWaitForExit(const std::unique_ptr& gn); + + GravityReturnCode rustSubscribe(const std::unique_ptr& gn, const std::string& dataProductID, const std::unique_ptr& subscriber); + + GravityReturnCode rustSubscribe(const std::unique_ptr& gn, const std::string& dataProductID, const std::unique_ptr& subscriber, + const std::string& filter); + GravityReturnCode rustSubscribe(const std::unique_ptr& gn, const std::string& dataProductID, const std::unique_ptr& subscriber, + const std::string& filter, const std::string& domain); + GravityReturnCode rustSubscribe(const std::unique_ptr& gn, const std::string& dataProductID, const std::unique_ptr& subscriber, + const std::string& filter, const std::string& domain, bool recieveLastCachedValue); + + GravityReturnCode rustUnsubscribe(const std::unique_ptr & gn, const std::string& dataProductId, const std::unique_ptr& subscriber); + + GravityReturnCode rustPublish(const std::unique_ptr &gn, const std::unique_ptr &gdp); + + GravityReturnCode rustPublishFilter(const std::unique_ptr &gn, const std::unique_ptr &gdp, const std::string &filterText); + + GravityReturnCode rustPublishTimestamp(const std::unique_ptr &gn, const std::unique_ptr &gdp, const std::string &filterText, uint64_t timestamp); + + GravityReturnCode rustSubscribersExist(const std::unique_ptr &gn, const std::string& dataProductID, bool& hasSubscribers); + + GravityReturnCode rustRequestAsync(const std::unique_ptr&gn, const std::string& serviceID, const std::unique_ptr& dataProduct, + const std::unique_ptr& requestor); + + GravityReturnCode rustRequestAsyncRequestID(const std::unique_ptr&gn, const std::string& serviceID, const std::unique_ptr& dataProduct, + const std::unique_ptr& requestor, const std::string& requestID); + + GravityReturnCode rustRequestAsyncTimeout(const std::unique_ptr&gn, const std::string& serviceID, const std::unique_ptr& dataProduct, + const std::unique_ptr& requestor, const std::string& requestID, int timeout_milliseconds); + + GravityReturnCode rustRequestAsyncDomain(const std::unique_ptr&gn, const std::string& serviceID, const std::unique_ptr& dataProduct, + const std::unique_ptr& requestor, const std::string& requestID, int timeout_milliseconds, const std::string& domain); + + std::shared_ptr rustRequestSync(const std::unique_ptr &gn, const std::string& serviceID, const std::unique_ptr& request); + + std::shared_ptr rustRequestSyncTimeout(const std::unique_ptr &gn, const std::string& serviceID, const std::unique_ptr& request, int timeout_milliseconds); + + std::shared_ptr rustRequestSyncDomain(const std::unique_ptr &gn, const std::string& serviceID, const std::unique_ptr& request, int timeout_milliseconds, const std::string &domain); + + GravityReturnCode rustStartHeartbeat(const std::unique_ptr& gn, int64_t interval_in_microseconds); + + GravityReturnCode rustStopHeartbeat(const std::unique_ptr& gn); + + std::unique_ptr rustGetStringParam(const std::unique_ptr& gn, const std::string &key, const std::string& default_value); + int rustGetIntParam(const std::unique_ptr& gn, const std::string& key, int default_value); + double rustGetFloatParam(const std::unique_ptr& gn, const std::string& key, double default_value); + bool rustGetBoolParam(const std::unique_ptr& gn, const std::string& key, bool default_value); + + std::unique_ptr rustGetComponentID(const std::unique_ptr& gn); + + GravityReturnCode rustRegisterDataProduct(const std::unique_ptr& gn, const std::string & dataProductID, GravityTransportType transportType); + + GravityReturnCode rustRegisterDataProduct(const std::unique_ptr& gn, const std::string & dataProductID, GravityTransportType transportType, bool cacheLastValue); + + GravityReturnCode rustUnregisterDataProduct(const std::unique_ptr& gn, const std::string& dataProductID); + + GravityReturnCode rustRegisterService(const std::unique_ptr& gn, const std::string& serviceID, + GravityTransportType transportType, const std::unique_ptr& server); + + GravityReturnCode rustUnregisterService(const std::unique_ptr& gn, const std::string& serviceID); + + GravityReturnCode rustRegisterRelay(const std::unique_ptr & gn, + const std::string& dataProductID, const std::unique_ptr& subscriber, + bool localOnly, GravityTransportType transportType); + + GravityReturnCode rustRegisterHeartbeatListener(const std::unique_ptr& gn, const std::string& componentID, int64_t interval_in_microseconds, + const std::unique_ptr& listener); + + GravityReturnCode rustRegisterHeartbeatListenerDomain(const std::unique_ptr& gn, const std::string& componentID, int64_t interval_in_microseconds, + const std::unique_ptr& listener, const std::string& domain); + + GravityReturnCode rustUnregisterHeartbeatListener(const std::unique_ptr& gn, const std::string& componentID); + + + GravityReturnCode rustUnregisterHeartbeatListenerDomain(const std::unique_ptr& gn, const std::string& componentID, const std::string& domain); + + GravityReturnCode rustRegisterRelayCache(const std::unique_ptr & gn, + const std::string& dataProductID, const std::unique_ptr& subscriber, + bool localOnly, GravityTransportType transportType, bool cacheLastValue); + + GravityReturnCode rustUnregisterRelay(const std::unique_ptr & gn, const std::string& dataProductID, const std::unique_ptr& subscriber); + + std::unique_ptr rustGetCodeString(const std::unique_ptr &gn, GravityReturnCode code); + std::unique_ptr rustGetIP(const std::unique_ptr &gn); + std::unique_ptr rustGetDomain(const std::unique_ptr &gn); + + std::shared_ptr rustCreateFutureResponse(const std::unique_ptr& gn); + + GravityReturnCode rustSendFutureResponse(const std::unique_ptr& gn, const std::shared_ptr& futureResponse); + + GravityReturnCode rustSetSubscriptionTimeoutMonitor(const std::unique_ptr& gn, const std::string& dataProductID, + const std::unique_ptr& monitor, int milliSecondTimeout); + + GravityReturnCode rustSetSubscriptionTimeoutMonitorFilter(const std::unique_ptr& gn, const std::string& dataProductID, + const std::unique_ptr& monitor, int milliSecondTimeout, const std::string& filter); + + GravityReturnCode rustSetSubscriptionTimeoutMonitorDomain(const std::unique_ptr& gn, const std::string& dataProductID, + const std::unique_ptr& monitor, int milliSecondTimeout, const std::string& filter, const std::string& domain); + + GravityReturnCode rustClearSubscriptionTimeoutMonitor(const std::unique_ptr&gn, const std::string& dataProductID, + const std::unique_ptr& monitor); + + GravityReturnCode rustClearSubscriptionTimeoutMonitorFilter(const std::unique_ptr&gn, const std::string& dataProductID, + const std::unique_ptr& monitor, const std::string& filter); + + GravityReturnCode rustClearSubscriptionTimeoutMonitorDomain(const std::unique_ptr&gn, const std::string& dataProductID, + const std::unique_ptr& monitor, const std::string& filter, const std::string& domain); + +} //namespace gravity + +#endif \ No newline at end of file diff --git a/src/api/rust/bridge/RustGravityRequestor.cpp b/src/api/rust/bridge/RustGravityRequestor.cpp new file mode 100644 index 000000000..b68c60d76 --- /dev/null +++ b/src/api/rust/bridge/RustGravityRequestor.cpp @@ -0,0 +1,32 @@ +#include "RustGravityRequestor.h" +#include "gravity/src/api/rust/src/ffi.rs.h" + +namespace gravity +{ + RustRequestor::RustRequestor( + rust::Fn filled, + rust::Fn timeout, + RequestorWrap * requestorPtr) : requestorPtr(requestorPtr) + { + this->filled = filled; + this->timeout = timeout; + } + + std::unique_ptr rustRustRequestor( + rust::Fn filled, + rust::Fn timeout, + RequestorWrap * requestorPtr) + { + return std::unique_ptr(new RustRequestor(filled, timeout, requestorPtr)); + } + + void RustRequestor::requestFilled(std::string serviceID, std::string requestID, const GravityDataProduct& response) + { + filled(serviceID, requestID, response, this->requestorPtr); + } + + void RustRequestor::requestTimeout(std::string serviceID, std::string requestID) + { + timeout(serviceID, requestID, this->requestorPtr); + } +} // namespace gravity diff --git a/src/api/rust/bridge/RustGravityRequestor.h b/src/api/rust/bridge/RustGravityRequestor.h new file mode 100644 index 000000000..b4d2fe18a --- /dev/null +++ b/src/api/rust/bridge/RustGravityRequestor.h @@ -0,0 +1,36 @@ +#ifndef RUSTGRAVITYREQUESTOR_H_ +#define RUSTGRAVITYREQUESTOR_H_ + + +#include +#include "rust/cxx.h" + +struct RequestorWrap; + +namespace gravity +{ + class RustRequestor : public GravityRequestor { + private: + rust::Fn filled; + rust::Fn timeout; + RequestorWrap * requestorPtr; + + public: + RustRequestor(rust::Fn filled, + rust::Fn timeout, + RequestorWrap * requestorPtr); + virtual void requestFilled(std::string serviceID, std::string requestID, + const GravityDataProduct& response); + virtual void requestTimeout(std::string serviceID, std::string requestID); + }; + + std::unique_ptr rustRustRequestor(rust::Fn func, rust::Fn timeout, RequestorWrap * requestorPtr); + + + + +} // namespace gravity + + + +#endif \ No newline at end of file diff --git a/src/api/rust/bridge/RustGravityServiceProvider.cpp b/src/api/rust/bridge/RustGravityServiceProvider.cpp new file mode 100644 index 000000000..cef436c90 --- /dev/null +++ b/src/api/rust/bridge/RustGravityServiceProvider.cpp @@ -0,0 +1,26 @@ +#include "RustGravityServiceProvider.h" +#include "gravity/src/api/rust/src/ffi.rs.h" + + +namespace gravity +{ + std::unique_ptr rustRustServiceProvider( + rust::Fn(const std::string&, const GravityDataProduct&, ServiceWrap *)> func, + ServiceWrap * servicePtr) + { + return std::unique_ptr(new RustServiceProvider(func, servicePtr)); + } + + RustServiceProvider::RustServiceProvider( + rust::Fn(const std::string&, const GravityDataProduct&, ServiceWrap *)> func, + ServiceWrap * servicePtr) : servicePtr(servicePtr) + { + this->func = func; + } + + std::shared_ptr RustServiceProvider::request(const std::string serviceID, + const GravityDataProduct& dataProduct) + { + return this->func(serviceID, dataProduct, this->servicePtr); + } +} // namespace gravity diff --git a/src/api/rust/bridge/RustGravityServiceProvider.h b/src/api/rust/bridge/RustGravityServiceProvider.h new file mode 100644 index 000000000..239a23a1e --- /dev/null +++ b/src/api/rust/bridge/RustGravityServiceProvider.h @@ -0,0 +1,25 @@ +#ifndef RUSTGRAVITYSERVICEPROVIDER_H_ +#define RUSTGRAVITYSERVICEPROVIDER_H_ + +#include +#include "rust/cxx.h" + +struct ServiceWrap; + +namespace gravity +{ + class RustServiceProvider : public GravityServiceProvider { + private: + rust::Fn(const std::string&, const GravityDataProduct&, ServiceWrap *)> func; + ServiceWrap * servicePtr; + public: + RustServiceProvider(rust::Fn(const std::string&, const GravityDataProduct&, ServiceWrap *)> func, ServiceWrap * servicePtr); + virtual std::shared_ptr request(const std::string serviceID, const GravityDataProduct& dataProduct); + }; + + std::unique_ptr rustRustServiceProvider(rust::Fn(const std::string&, const GravityDataProduct&, ServiceWrap *)> func, ServiceWrap * servicePtr); + +} // namespace gravity + + +#endif \ No newline at end of file diff --git a/src/api/rust/bridge/RustGravitySubscriber.cpp b/src/api/rust/bridge/RustGravitySubscriber.cpp new file mode 100644 index 000000000..a5d062bbb --- /dev/null +++ b/src/api/rust/bridge/RustGravitySubscriber.cpp @@ -0,0 +1,37 @@ +#include "RustGravitySubscriber.h" + +#include "gravity/src/api/rust/src/ffi.rs.h" + +namespace gravity +{ + + RustSubscriber::RustSubscriber(rust::Fn&, SubscriberWrap*)> func, SubscriberWrap* subscriber_ptr) : subscriber_ptr(subscriber_ptr) + { + this->func = func; + + } + + void RustSubscriber::subscriptionFilled(const std::vector >& dataProducts) + { + std::vector v; + for (std::vector < std::shared_ptr>::const_iterator i = dataProducts.begin(); i != dataProducts.end(); i++) + { + GravityDataProduct to_add = **i; + v.push_back(to_add); + } + this->func(v, this->subscriber_ptr); + } + + + + std::unique_ptr newRustSubscriber( + rust::Fn&, SubscriberWrap*)> func, SubscriberWrap* subscriber_ptr) + { + + // wrap.subscriber_ptr = std::move(subscriber_ptr); + return std::unique_ptr(new RustSubscriber(func, subscriber_ptr)); + } + + + +} // namespace gravity diff --git a/src/api/rust/bridge/RustGravitySubscriber.h b/src/api/rust/bridge/RustGravitySubscriber.h new file mode 100644 index 000000000..ae6f03235 --- /dev/null +++ b/src/api/rust/bridge/RustGravitySubscriber.h @@ -0,0 +1,28 @@ +#ifndef RUSTSUBSCRIBER_H_ +#define RUSTSUBSCRIBER_H_ + +#include +#include "rust/cxx.h" + +struct SubscriberWrap; + +namespace gravity +{ + + + class RustSubscriber : public GravitySubscriber { + private: + rust::Fn&, SubscriberWrap *)> func; + SubscriberWrap * subscriber_ptr; + public: + RustSubscriber(rust::Fn&, SubscriberWrap*)> func, SubscriberWrap*); + virtual void subscriptionFilled(const std::vector >& dataProducts); + }; + + + std::unique_ptr newRustSubscriber(rust::Fn&, SubscriberWrap*)> func, SubscriberWrap* subscriber_ptr); + +} // namespace gravity + + +#endif \ No newline at end of file diff --git a/src/api/rust/bridge/RustGravitySubscriptionMonitor.cpp b/src/api/rust/bridge/RustGravitySubscriptionMonitor.cpp new file mode 100644 index 000000000..f75800f47 --- /dev/null +++ b/src/api/rust/bridge/RustGravitySubscriptionMonitor.cpp @@ -0,0 +1,22 @@ +#include "RustGravitySubscriptionMonitor.h" +#include "gravity/src/api/rust/src/ffi.rs.h" + +namespace gravity +{ + void RustSubscriptionMonitor::subscriptionTimeout(std::string dataProductID, int millisecondsSinceLast, + std::string filter, std::string domain) + { + this->func(dataProductID, millisecondsSinceLast, filter, domain, this->monitorPtr); + } + + RustSubscriptionMonitor::RustSubscriptionMonitor(rust::Fn func, MonitorWrap * monitorPtr) : monitorPtr(monitorPtr) + { + this->func = func; + } + + std::unique_ptr rustNewSubscriptionMonitor(rust::Fn func, MonitorWrap * monitorPtr) + { + return std::unique_ptr(new RustSubscriptionMonitor(func, monitorPtr)); + } + +} // namespace gravity diff --git a/src/api/rust/bridge/RustGravitySubscriptionMonitor.h b/src/api/rust/bridge/RustGravitySubscriptionMonitor.h new file mode 100644 index 000000000..799f7cef5 --- /dev/null +++ b/src/api/rust/bridge/RustGravitySubscriptionMonitor.h @@ -0,0 +1,30 @@ +#ifndef RUSTGRAVITYSUBSCRIPTIONMONITOR_H_ +#define RUSTGRAVITYSUBSCRIPTIONMONITOR_H_ + +#include +#include +#include "rust/cxx.h" + +struct MonitorWrap; + +namespace gravity +{ + class RustSubscriptionMonitor : public GravitySubscriptionMonitor { + private: + rust::Fn func; + MonitorWrap * monitorPtr; + public: + RustSubscriptionMonitor(rust::Fn func, + MonitorWrap * monitorPtr); + virtual void subscriptionTimeout(std::string dataProductID, int millisecondsSinceLast, + std::string filter, std::string domain); + }; + + std::unique_ptr rustNewSubscriptionMonitor(rust::Fn func, + MonitorWrap * monitorPtr); + + +} // namespace gravity + + +#endif \ No newline at end of file diff --git a/src/api/rust/bridge/RustSpdLog.cpp b/src/api/rust/bridge/RustSpdLog.cpp new file mode 100644 index 000000000..55f27fff6 --- /dev/null +++ b/src/api/rust/bridge/RustSpdLog.cpp @@ -0,0 +1,20 @@ +#include "RustSpdLog.h" + +void spdlog_critical(const std::string& message) { + gravity::SpdLog::critical(message.c_str()); +} +void spdlog_error(const std::string& message) { + gravity::SpdLog::error(message.c_str()); +} +void spdlog_warn(const std::string& message) { + gravity::SpdLog::warn(message.c_str()); +} +void spdlog_info(const std::string& message) { + gravity::SpdLog::info(message.c_str()); +} +void spdlog_debug(const std::string& message) { + gravity::SpdLog::debug(message.c_str()); +} +void spdlog_trace(const std::string& message) { + gravity::SpdLog::trace(message.c_str()); +} \ No newline at end of file diff --git a/src/api/rust/bridge/RustSpdLog.h b/src/api/rust/bridge/RustSpdLog.h new file mode 100644 index 000000000..4eb97698f --- /dev/null +++ b/src/api/rust/bridge/RustSpdLog.h @@ -0,0 +1,16 @@ +#pragma once +#ifndef RUSTSPDLOG_H_ +#define RUSTSPDLOG_H_ + +#include + + + +void spdlog_critical(const std::string& message); +void spdlog_error(const std::string& message);; +void spdlog_warn(const std::string& message); +void spdlog_info(const std::string& message); +void spdlog_debug(const std::string& message); +void spdlog_trace(const std::string& message); + +#endif \ No newline at end of file diff --git a/src/api/rust/build.rs b/src/api/rust/build.rs new file mode 100644 index 000000000..b0adc4e40 --- /dev/null +++ b/src/api/rust/build.rs @@ -0,0 +1,93 @@ +// use std::env; +use cmake; +use std::{env, path::{Path, PathBuf}, str::FromStr}; + +fn main() { + //bridge sources + let src = ["bridge/RustSpdLog.cpp", "bridge/RustGravityNode.cpp", + "bridge/RustGravityDataProduct.cpp", "bridge/RustFutureResponse.cpp", + "bridge/RustGravitySubscriber.cpp", "bridge/RustGravityRequestor.cpp", + "bridge/RustGravityHeartbeatListener.cpp", "bridge/RustGravitySubscriptionMonitor.cpp", + "bridge/RustGravityServiceProvider.cpp" + ]; + let mut srcs = Vec::with_capacity(9); + for s in src.iter() { + let mut to_add = "src/api/rust/".to_string(); + to_add.push_str(s); + srcs.push(to_add); + } + + // get the necessary library and include paths, relative to the Cargo.toml + let dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let out_dir = env::var("OUT_DIR").unwrap(); + let root = Path::new(&dir); + let mut lib_path = out_dir.clone(); + lib_path.push_str("/install/lib"); + let mut include_path = out_dir.clone(); + include_path.push_str("/install/include"); + let header_path = root.parent().unwrap(); + let _path = PathBuf::from_str("/usr/lib/x86_64-linux-gnu/").unwrap(); + + // cmake the gravity libraries + let mut prefix = out_dir.clone(); + prefix.push_str("/install"); + + let _install_dir = cmake::Config::new(root.as_os_str()) + .define("SKIP_PYTHON", "ON") + .define("SKIP_JAVA", "ON") + .define("CMAKE_INSTALL_PREFIX", prefix) + .define("GRAVITY_USE_EXTERNAL_PROTOBUF", "ON") + .define("GRAVITY_USE_EXTERNAL_ZEROMQ", "ON") + .define("GRAVITY_USE_EXTERNAL_SPDLOG", "ON") + .define("BUILD_LIBRARY_ONLY", "ON") + .define("BUILD_STATIC_LIBRARIES", "ON") + .define("BUILD_EXAMPLES_TESTS", "OFF") + .define("CMAKE_BUILD_TYPE", "Release") + .cflag("-Wall") + .cxxflag("-Wall") + .out_dir(out_dir) + .build(); + + //compile the bridge + cxx_build::bridge("src/api/rust/src/ffi.rs") + .files(srcs.iter()) + .include(include_path) + .include(header_path) + .warnings(false) + .cargo_warnings(false) + .compile("rust_gravity"); + + // generate the protobufs for the unit tests + let mut proto_include = dir.clone(); + proto_include.push_str("/src/api/rust/src/protobuf/"); + + let filenames = ["BasicCounterDataProduct.proto", + "BigComplexPB.proto", "DataPB.proto"]; + let mut proto_files = Vec::new(); + + for item in filenames { + let mut to_add = proto_include.clone(); + to_add.push_str(item); + proto_files.push(to_add); + } + + protobuf_codegen::Codegen::new() + .pure() + .include(proto_include) + .inputs(proto_files) + .cargo_out_dir("protobuf") + .run_from_script(); + + + // search and link the libraries created + println!("cargo:rustc-link-search={lib_path}"); + // println!("cargo:rustc-link-search={}", path.display()); + println!("cargo:rustc-link-lib=static=gravity"); + println!("cargo:rustc-link-lib=static=gravity_protobufs"); + println!("cargo:rustc-link-lib=static=keyvalue_parser"); + println!("cargo:rustc-link-lib=static=protobuf"); + println!("cargo:rustc-link-lib=static=zmq"); + println!("cargo:rustc-link-lib=static=spdlog"); + + +} \ No newline at end of file diff --git a/src/api/rust/src/ffi.rs b/src/api/rust/src/ffi.rs new file mode 100644 index 000000000..733ad7cc2 --- /dev/null +++ b/src/api/rust/src/ffi.rs @@ -0,0 +1,408 @@ +#![allow(non_camel_case_types)] +#![allow(dead_code)] +pub use ffi::*; +use crate::gravity_heartbeat_listener::ListenerWrap; +use crate::gravity_subscriber::SubscriberWrap; +use crate::gravity_service_provider::ServiceWrap; +use crate::gravity_subscription_monitor::MonitorWrap; +use crate::gravity_requestor::RequestorWrap; +#[cxx::bridge] +mod ffi { + + + #[namespace = "gravity"] + #[repr(i32)] + #[derive(Debug)] + #[cxx_name = "GravityReturnCode"] + pub enum GravityReturnCodes { + SUCCESS = 0, ///< The request was successful + FAILURE = -1, ///< The request failed + NO_SERVICE_DIRECTORY = -2, ///< Could not find Service Directory + REQUEST_TIMEOUT = -3, ///< The request timed out while waiting for a response. + DUPLICATE = -4, ///< Attempting to register an existing data product ID + REGISTRATION_CONFLICT = + -5, ///< Attempting to unregister from a service or dataProductID that is not currently registered. + NOT_REGISTERED = -6, ///< TBD + NO_SUCH_SERVICE = + -7, ///< Made a bad request (Ex: used a non-existent domain or submitted a request before service was available) + LINK_ERROR = -8, ///< Error serializing or deserializing a Gravity Data Product + INTERRUPTED = -9, ///< Received an interruption. + NO_SERVICE_PROVIDER = -10, ///< No service provider found. + NO_PORTS_AVAILABLE = -11, ///< No ports available + INVALID_PARAMETER = -12, ///< Invalid parameter. (Ex: entered a negative number for time) + NOT_INITIALIZED = + -13, ///< The GravityNode has not successfully completed initialization yet (i.e. init has not been called or did not succeed). + RESERVED_DATA_PRODUCT_ID = -14, + } + #[namespace = "gravity"] + #[repr(i32)] + #[cxx_name = "GravityTransportType"] + pub enum GravityTransportTypes { + TCP = 0, ///< Transmission Control Protocol + INPROC = 1, ///< In-process (Inter-thread) Communication + PGM = 2, ///< Pragmatic General Multicast Protocol + EPGM = 3, ///< Encapsulated PGM + IPC = 4, //< Inter-Process Communication + } + #[namespace = "gravity"] + unsafe extern "C++" { + include!("gravity/src/api/rust/bridge/RustGravityNode.h"); + include!("gravity/src/api/rust/bridge/RustGravityDataProduct.h"); + include!("gravity/src/api/rust/bridge/RustFutureResponse.h"); + include!("gravity/src/api/rust/bridge/RustGravitySubscriber.h"); + include!("gravity/src/api/rust/bridge/RustGravityHeartbeatListener.h"); + include!("gravity/src/api/rust/bridge/RustGravitySubscriptionMonitor.h"); + include!("gravity/src/api/rust/bridge/RustGravityServiceProvider.h"); + + + + #[rust_name = "GravityReturnCodes"] + type GravityReturnCode; + + #[rust_name = "GravityTransportTypes"] + type GravityTransportType; + #[rust_name = "GNode"] + type GravityNode; + + #[rust_name = "GDataProduct"] + type GravityDataProduct; + + #[rust_name = "GFutureResponse"] + type FutureResponse; + type RustSubscriber; + type RustRequestor; + type RustServiceProvider; + type RustHeartbeatListener; + type RustSubscriptionMonitor; + + //GravityNode methods + #[rust_name = "GravityNode"] + fn newGravityNode() -> UniquePtr; + + #[rust_name = "gravity_node_id"] + fn newGravityNodeId(componentID: &CxxString) -> UniquePtr; + + #[rust_name = "init"] + fn rustInit(gn: &UniquePtr, componentID: &CxxString) -> GravityReturnCodes; + + #[rust_name = "init_default"] + fn rustInit(gn: &UniquePtr) -> GravityReturnCodes; + + #[rust_name = "wait_for_exit"] + fn rustWaitForExit(gn: &UniquePtr); + + #[rust_name = "publish"] + fn rustPublish(gn: &UniquePtr, dataProduct: &UniquePtr) -> GravityReturnCodes; + + #[rust_name = "publish_filter"] + fn rustPublishFilter(gn: &UniquePtr, dataProduct: &UniquePtr, filterText: &CxxString) -> GravityReturnCodes; + + #[rust_name = "publish_timestamp"] + fn rustPublishTimestamp(gn: &UniquePtr, dataProduct: &UniquePtr, filterText: &CxxString, timestamp: u64) -> GravityReturnCodes; + + #[rust_name = "subsribers_exist"] + fn rustSubscribersExist(gn: &UniquePtr, dataProductID: &CxxString, has_subscribers: &mut bool) -> GravityReturnCodes; + + #[rust_name = "start_heartbeat"] + fn rustStartHeartbeat(gn: &UniquePtr, interval_in_microseconds: i64) -> GravityReturnCodes; + + #[rust_name = "stop_heartbeat"] + fn rustStopHeartbeat(gn: &UniquePtr) -> GravityReturnCodes; + + #[rust_name = "get_string_param"] + fn rustGetStringParam(gn: &UniquePtr, key: &CxxString, default_value: &CxxString) -> UniquePtr; + + #[rust_name = "get_int_param"] + fn rustGetIntParam(gn: &UniquePtr, key: &CxxString, default_value: i32) -> i32; + + #[rust_name = "get_float_param"] + fn rustGetFloatParam(gn: &UniquePtr, key: &CxxString, default_value: f64) -> f64; + + #[rust_name = "get_bool_param"] + fn rustGetBoolParam(gn: &UniquePtr, key: &CxxString, default_value: bool) -> bool; + + #[rust_name = "get_component_ID"] + fn rustGetComponentID(gn: &UniquePtr) -> UniquePtr; + + #[rust_name = "register_data_product"] + fn rustRegisterDataProduct(gn: &UniquePtr, dataProductID: &CxxString, + transportType: GravityTransportTypes) -> GravityReturnCodes; + + #[rust_name = "register_data_product_cache"] + fn rustRegisterDataProduct(gn: &UniquePtr, dataProductID: &CxxString, + transportType: GravityTransportTypes, cacheLastValue: bool) -> GravityReturnCodes; + + #[rust_name = "unregister_data_product"] + fn rustUnregisterDataProduct(gn: &UniquePtr, dataProductID: &CxxString) -> GravityReturnCodes; + + #[rust_name = "get_code_string"] + fn rustGetCodeString(gn: &UniquePtr, code: GravityReturnCodes) -> UniquePtr; + + #[rust_name = "get_IP"] + fn rustGetIP(gn: &UniquePtr) -> UniquePtr; + + #[rust_name = "get_domain"] + fn rustGetDomain(gn: &UniquePtr) -> UniquePtr; + + #[rust_name = "subscribe"] + fn rustSubscribe(gn: &UniquePtr, dataProductID: &CxxString, subscriber: &UniquePtr) -> GravityReturnCodes; + + #[rust_name = "subscribe_filter"] + fn rustSubscribe(gn: &UniquePtr, dataProductID: &CxxString, subscriber: &UniquePtr, + filter: &CxxString) -> GravityReturnCodes; + + #[rust_name = "subscribe_domain"] + fn rustSubscribe(gn: &UniquePtr, dataProductID: &CxxString, subscriber: &UniquePtr, + filter: &CxxString, domain: &CxxString) -> GravityReturnCodes; + + #[rust_name = "subscribe_cache"] + fn rustSubscribe(gn: &UniquePtr, dataProductID: &CxxString, subscriber: &UniquePtr, + filter: &CxxString, domain: &CxxString, recieve_last_cached_value: bool) -> GravityReturnCodes; + + #[rust_name = "unsubscribe"] + fn rustUnsubscribe(gn: &UniquePtr, dataProductID: &CxxString, subscriber: &UniquePtr) -> GravityReturnCodes; + + #[rust_name = "new_rust_subscriber"] + unsafe fn newRustSubscriber(func: unsafe fn(&CxxVector, * mut SubscriberWrap), ptr: * mut SubscriberWrap) -> UniquePtr; + + #[rust_name = "new_rust_requestor"] + unsafe fn rustRustRequestor(filled: unsafe fn(&CxxString, &CxxString, &GDataProduct, * mut RequestorWrap), timeout: unsafe fn(&CxxString, &CxxString, * mut RequestorWrap), ptr: * mut RequestorWrap) -> UniquePtr; + + #[rust_name = "new_rust_heartbeat_listener"] + unsafe fn rustNewHeartbeatListener(missed: unsafe fn(&CxxString, i64, &mut i64, * mut ListenerWrap), received: unsafe fn(&CxxString, &mut i64, * mut ListenerWrap), ptr: * mut ListenerWrap) -> UniquePtr; + + #[rust_name = "new_rust_subscription_monitor"] + unsafe fn rustNewSubscriptionMonitor(func: unsafe fn(&CxxString, i32, &CxxString, &CxxString, * mut MonitorWrap), ptr: * mut MonitorWrap) -> UniquePtr; + + #[rust_name = "new_rust_service_provider"] + unsafe fn rustRustServiceProvider(func: unsafe fn(&CxxString, &GDataProduct, * mut ServiceWrap) -> SharedPtr, ptr: * mut ServiceWrap) -> UniquePtr; + + #[rust_name = "register_heartbeat_listener"] + fn rustRegisterHeartbeatListener(gn: &UniquePtr, component_id: &CxxString, interval_in_microseconds: i64, listener: &UniquePtr) -> GravityReturnCodes; + + #[rust_name = "register_heartbeat_listener_domain"] + fn rustRegisterHeartbeatListenerDomain(gn: &UniquePtr, component_id: &CxxString, interval_in_microseconds: i64, listener: &UniquePtr, domain: &CxxString) -> GravityReturnCodes; + + #[rust_name = "unregister_heartbeat_listener"] + fn rustUnregisterHeartbeatListener(gn: &UniquePtr, component_id: &CxxString) -> GravityReturnCodes; + + #[rust_name = "unregister_heartbeat_listener_domain"] + fn rustUnregisterHeartbeatListenerDomain(gn: &UniquePtr, component_id: &CxxString, domain: &CxxString) -> GravityReturnCodes; + + #[rust_name = "request_async"] + fn rustRequestAsync(gn: &UniquePtr, service_id: &CxxString, dataProduct: &UniquePtr, + requestor: &UniquePtr) -> GravityReturnCodes; + + #[rust_name = "request_async_request_id"] + fn rustRequestAsyncRequestID(gn: &UniquePtr, service_id: &CxxString, dataProduct: &UniquePtr, + requestor: &UniquePtr, request_id: &CxxString) -> GravityReturnCodes; + + #[rust_name = "request_async_timeout"] + fn rustRequestAsyncTimeout(gn: &UniquePtr, service_id: &CxxString, dataProduct: &UniquePtr, + requestor: &UniquePtr, request_id: &CxxString, timeout_milliseconds: i32) -> GravityReturnCodes; + + #[rust_name = "request_async_domain"] + fn rustRequestAsyncDomain(gn: &UniquePtr, service_id: &CxxString, dataProduct: &UniquePtr, + requestor: &UniquePtr, request_id: &CxxString, timeout_milliseconds: i32, domain: &CxxString) -> GravityReturnCodes; + + #[rust_name = "request_sync"] + fn rustRequestSync(gn: &UniquePtr, service_id: &CxxString, request: &UniquePtr) -> SharedPtr; + + #[rust_name = "request_sync_timeout"] + fn rustRequestSyncTimeout(gn: &UniquePtr, service_id: &CxxString, request: &UniquePtr, timeout_milliseconds: i32) -> SharedPtr; + + #[rust_name = "request_sync_domain"] + fn rustRequestSyncDomain(gn: &UniquePtr, service_id: &CxxString, request: &UniquePtr, timeout_milliseconds: i32, domain: &CxxString) -> SharedPtr; + + #[rust_name = "register_service"] + fn rustRegisterService(gn: &UniquePtr, service_id: &CxxString, transport_type: GravityTransportTypes, server: &UniquePtr) -> GravityReturnCodes; + + #[rust_name = "unregister_service"] + fn rustUnregisterService(gn: &UniquePtr, service_id: &CxxString) -> GravityReturnCodes; + + #[rust_name = "register_relay"] + fn rustRegisterRelay(gn: &UniquePtr, data_product_id: &CxxString, subscriber: &UniquePtr, + local_only: bool, transport_type: GravityTransportTypes) -> GravityReturnCodes; + + #[rust_name = "register_relay_cache"] + fn rustRegisterRelayCache(gn: &UniquePtr, data_product_id: &CxxString, subscriber: &UniquePtr, + local_only: bool, transport_type: GravityTransportTypes, cache_last_value: bool) -> GravityReturnCodes; + + #[rust_name = "unregister_relay"] + fn rustUnregisterRelay(gn: &UniquePtr, data_product_id: &CxxString, subscriber: &UniquePtr) -> GravityReturnCodes; + + #[rust_name = "create_future_response"] + fn rustCreateFutureResponse(gn: &UniquePtr) -> SharedPtr; + + #[rust_name = "send_future_response"] + fn rustSendFutureResponse(gn: &UniquePtr, future_response: &SharedPtr) -> GravityReturnCodes; + + #[rust_name = "set_subscription_timeout_monitor"] + fn rustSetSubscriptionTimeoutMonitor(gn: &UniquePtr, data_product_id: &CxxString, monitor: &UniquePtr, + milli_second_timeout: i32) -> GravityReturnCodes; + + #[rust_name = "set_subscription_timeout_monitor_filter"] + fn rustSetSubscriptionTimeoutMonitorFilter(gn: &UniquePtr, data_product_id: &CxxString, monitor: &UniquePtr, + milli_second_timeout: i32, filter: &CxxString) -> GravityReturnCodes; + + #[rust_name = "set_subscription_timeout_monitor_domain"] + fn rustSetSubscriptionTimeoutMonitorDomain(gn: &UniquePtr, data_product_id: &CxxString, monitor: &UniquePtr, + milli_second_timeout: i32, filter: &CxxString, domain: &CxxString) -> GravityReturnCodes; + + #[rust_name = "clear_subscription_timeout_monitor"] + fn rustClearSubscriptionTimeoutMonitor(gn: &UniquePtr, data_product_id: &CxxString, monitor: &UniquePtr, + ) -> GravityReturnCodes; + + #[rust_name = "clear_subscription_timeout_monitor_filter"] + fn rustClearSubscriptionTimeoutMonitorFilter(gn: &UniquePtr, data_product_id: &CxxString, monitor: &UniquePtr, + filter: &CxxString) -> GravityReturnCodes; + + #[rust_name = "clear_subscription_timeout_monitor_domain"] + fn rustClearSubscriptionTimeoutMonitorDomain(gn: &UniquePtr, data_product_id: &CxxString, monitor: &UniquePtr, + filter: &CxxString, domain: &CxxString) -> GravityReturnCodes; + + // GravityDataProductMethods + + #[rust_name = "gravity_data_product"] + fn newGravityDataProduct(dataProductId: &CxxString) -> UniquePtr; + + #[rust_name = "gravity_data_product_default"] + fn newGravityDataProduct() -> UniquePtr; + + #[rust_name = "gravity_data_product_bytes"] + unsafe fn newGravityDataProduct(arrayPtr: *const c_char, size: i32) -> UniquePtr; + + #[rust_name = "set_data_basic"] + unsafe fn rustSetData(gdp: &UniquePtr, data: *const c_char, size: i32); + + #[rust_name = "set_data"] + unsafe fn rustSetDataProto(gdp: &UniquePtr, data: *const c_char, size: i32, data_type: &CxxString); + + #[rust_name = "get_gravity_timestamp"] + fn rustGetGravityTimestamp(gdp: &UniquePtr) -> u64; + + #[rust_name = "get_receieved_timestamp"] + fn rustGetReceivedTimestamp(gdp: &UniquePtr) -> u64; + + #[rust_name = "get_data_product_ID"] + fn rustGetDataProductID(gdp: &UniquePtr) -> UniquePtr; + + #[rust_name = "set_software_version"] + fn rustSetSoftwareVersion(gdp: &UniquePtr, softwareVersion: &CxxString); + + #[rust_name = "get_software_version"] + fn rustGetSoftwareVersion(gdp: &UniquePtr) -> UniquePtr; + + #[rust_name = "get_data_size"] + fn rustGetDataSize(gdp: &UniquePtr) -> i32; + + #[rust_name = "get_data"] + fn rustGetData(gdp: &UniquePtr) -> * const c_char; + + #[rust_name = "get_size"] + fn rustGetSize(gdp: &UniquePtr) -> i32; + + #[rust_name = "parse_from_array"] + unsafe fn rustParseFromArray(gdp: &UniquePtr, array_ptr: * const c_char, size: i32); + + #[rust_name = "serialize_to_array"] + fn rustSerializeToArray(gdp: &UniquePtr) -> UniquePtr>; + + #[rust_name = "gdp_get_component_id"] + fn rustGDPGetComponentID(gdp: &UniquePtr) -> UniquePtr; + + #[rust_name = "gdp_get_domain"] + fn rustGetDomain(gdp: &UniquePtr) -> UniquePtr; + + #[rust_name = "is_future_response"] + fn rustIsFutureResponse(gdp: &UniquePtr) -> bool; + + #[rust_name = "is_cached_data_product"] + fn rustIsCachedDataProduct(gdp: &UniquePtr) -> bool; + + #[rust_name = "set_is_cached_data_product"] + fn rustSetIsCachedDataProduct(gdp: &UniquePtr, cached: bool); + + #[rust_name = "get_future_socket_url"] + fn rustGetFutureSocketURL(gdp: &UniquePtr) -> UniquePtr; + + #[rust_name = "set_timestamp"] + fn rustSetTimestamp(gdp: &UniquePtr, ts: u32); + + #[rust_name = "set_recieved_timestamp"] + fn rustSetReceivedTimestamp(gdp: &UniquePtr, ts: u32); + + #[rust_name = "set_component_id"] + fn rustSetComponentId(gdp: &UniquePtr, component_id: &CxxString); + + #[rust_name = "set_domain"] + fn rustSetDomain(gdp: &UniquePtr, domain: &CxxString); + + #[rust_name = "is_relayed_data_product"] + fn rustIsRelayedDataProduct(gdp: &UniquePtr) -> bool; + + #[rust_name = "set_is_relayed_data_product"] + fn rustSetIsRelayedDataProduct(gdp: &UniquePtr, relayed: bool); + + #[rust_name = "set_protocol"] + fn rustSetProtocol(gdp: &UniquePtr, protocol: &CxxString); + + #[rust_name = "get_protocol"] + fn rustGetProtocol(gdp: &UniquePtr) -> UniquePtr; + + #[rust_name = "set_type_name"] + fn rustSetTypeName(gdp: &UniquePtr, data_type: &CxxString); + + #[rust_name = "get_type_name"] + fn rustGetTypeName(gdp: &UniquePtr) -> UniquePtr; + + #[rust_name = "get_registration_time"] + fn rustGetRegistrationTime(gdp: &UniquePtr) -> u32; + + #[rust_name = "set_registration_time"] + fn rustSetRegistrationTime(gdp: &UniquePtr, ts: u32); + + #[rust_name = "copy_gdp"] + fn copyGravityDataProduct(gdp: &GDataProduct) -> UniquePtr; + + #[rust_name = "copy_gdp_shared"] + fn copyGravityDataProductShared(gdp: &GDataProduct) -> SharedPtr; + + #[rust_name ="free_data"] + unsafe fn rustFree(data: * const c_char); + + + // Future response + #[rust_name = "new_future_response"] + unsafe fn rustNewFutureResponse(array_ptr: * const c_char, size: i32) -> SharedPtr; + + #[rust_name = "set_response"] + fn rustSetResponse(fr: &UniquePtr, respons: &UniquePtr); + + + } + + + unsafe extern "C++" { + //spdlog + include!("gravity/src/api/rust/bridge/RustSpdLog.h"); + + fn spdlog_critical(message: &CxxString); + fn spdlog_error(message: &CxxString); + fn spdlog_warn(message: &CxxString); + fn spdlog_info(message: &CxxString); + fn spdlog_debug(message: &CxxString); + fn spdlog_trace(message: &CxxString); + + } + + extern "Rust" { + type SubscriberWrap; + type RequestorWrap; + type ListenerWrap; + type ServiceWrap; + type MonitorWrap; + } +} // mod ffi + + diff --git a/src/api/rust/src/future_response.rs b/src/api/rust/src/future_response.rs new file mode 100644 index 000000000..cac76e7b7 --- /dev/null +++ b/src/api/rust/src/future_response.rs @@ -0,0 +1,22 @@ +use std::ffi::c_char; + +use cxx::SharedPtr; + +use crate::ffi::{new_future_response, GFutureResponse}; + + + +/// Representative of a future response to a gravity request. +pub struct FutureResponse { + pub(crate) fr: SharedPtr, +} + +impl FutureResponse { + /// Constructor that deserializes this FutureResponse from an array of bytes. + pub fn with_array(array: &[u8]) -> FutureResponse { + let ptr = array.as_ptr() as * const c_char; + let size = array.len() as i32; + FutureResponse { fr: unsafe { new_future_response(ptr, size) } } + } + +} \ No newline at end of file diff --git a/src/api/rust/src/gravity_data_product.rs b/src/api/rust/src/gravity_data_product.rs new file mode 100644 index 000000000..eed1aecba --- /dev/null +++ b/src/api/rust/src/gravity_data_product.rs @@ -0,0 +1,209 @@ +#![allow(dead_code)] +use std::ffi::c_char; + +use cxx::{let_cxx_string, UniquePtr}; +use crate::ffi::{self, *}; + +/// Generic Data Product for the Gravity Infrastructure. +pub struct GravityDataProduct { + pub(crate) gdp: UniquePtr, +} + + +impl GravityDataProduct { + /// Constructs a GravityDataProduct using the default constructor + pub fn new() -> GravityDataProduct { + GravityDataProduct { gdp: ffi::gravity_data_product_default() } + } + + /// Constructs a GravityDataProduct with a string as a parameter.
+ /// Recommended constructor. + pub fn with_id(data_product_id: &str) -> GravityDataProduct { + let_cxx_string!(dpid = data_product_id); + GravityDataProduct {gdp: ffi::gravity_data_product(&dpid)} + } + + /// Constructor that deserializes this GravityDataProduct from an array of bytes + pub fn with_array(array: &[u8]) -> GravityDataProduct { + let size = array.len() as i32; + let array_ptr = array as *const _ as * const c_char; + GravityDataProduct {gdp: unsafe {ffi::gravity_data_product_bytes(array_ptr, size)}} + } + + + pub(crate) fn from_gdp(gdp: UniquePtr) -> GravityDataProduct { + GravityDataProduct {gdp} + } + /// Returns the data product ID of this data product. + pub fn data_product_id(&self) -> String { + ffi::get_data_product_ID(&self.gdp).to_str().unwrap().to_string() + } + /// Sets the software version of this data product. + pub fn set_software_version(&mut self, software_version: &str) + { + let_cxx_string!(sv = software_version); + ffi::set_software_version(&self.gdp, &sv); + } + /// Returns the software version of this data product. + pub fn software_version(&self) -> String { + ffi::get_software_version(&self.gdp).to_str().unwrap().to_string() + } + /// Returns the received timestamp of the data product. + pub fn receieved_timestamp(&self) -> u64 { + ffi::get_receieved_timestamp(&self.gdp) + } + /// Returns the gravity timestamp of the data product. + pub fn gravity_timestamp(&self) -> u64{ + ffi::get_gravity_timestamp(&self.gdp) + } + /// Sets the application-specific data for this data product from an array of bytes. + pub fn set_data_basic(&mut self, data: &[u8]) { + let size = data.len() as i32; + let d = data as *const _ as *const c_char; + unsafe { ffi::set_data_basic(&self.gdp, d, size); } + } + /// Sets the applicatio-specific data for this data product from a protocol buffer. + pub fn set_data(&mut self, data: &A) { + let v = data.write_to_bytes().unwrap(); + let bytes = v.as_ptr() as *const c_char; + let type_name = ::NAME; + let_cxx_string!(data_type = type_name); + unsafe {ffi::set_data(&self.gdp, bytes, data.compute_size() as i32, &data_type);} + } + /// Returns the data in this data product as a vector of bytes. + pub fn data(&self) -> Vec { + let data_str = ffi::get_data(&self.gdp); + let mut pointer = data_str as * const c_char as * const u8; + let temp = pointer as * const i8; + let size = ffi::get_data_size(&self.gdp); + + + let mut bytes_buf = Vec::new(); + for _ in 0..size { + unsafe { + bytes_buf.push(*pointer); + pointer = pointer.offset(1); + } + + } + unsafe { ffi::free_data(temp); } + + bytes_buf + + } + /// Returns the size of the data contained within this data product. + pub fn data_size(&self) -> i32 { + ffi::get_data_size(&self.gdp) + } + /// Populate a protobuf object with the data conatined in this data product. + pub fn populate_message(&self, data: &mut impl protobuf::Message) -> bool{ + let temp = self.data(); + let bytes = temp.as_slice(); + // let smth = GravityDataProductPB::parse_from_bytes(bytes).unwrap(); + data.merge_from_bytes(bytes).unwrap(); + true + } + /// Returns the size for this message. + pub fn size(&self) -> i32 { + ffi::get_size(&self.gdp) + } + /// Deserialize this GravityDataProduct from an array of bytes. + pub fn parse_from_array(&mut self, array: &[u8]) { + let len = array.len(); + let p = array.as_ptr() as * const c_char; + unsafe { ffi::parse_from_array(&self.gdp, p, len as i32);} + } + /// Serialize this GravityDatProduct + pub fn serialize_to_array(&self) -> Vec { + let temp = ffi::serialize_to_array(&self.gdp); + let mut ret = Vec::new(); + for item in temp.iter() { + ret.push(*item); + } + ret + // think about changing array -> string + } + /// Returns the component ID of the GravityNode that produced this data product + pub fn component_id(&self) -> String { + let temp = ffi::gdp_get_component_id(&self.gdp); + temp.to_str().unwrap().to_string() + } + /// Returns the domain of the GravityNode that produced this data product + pub fn domain(&self) -> String { + let temp = ffi::gdp_get_domain(&self.gdp); + temp.to_str().unwrap().to_string() + } + /// Returns the flag indicating if this is a "future" response. + pub fn is_future_response(&self) -> bool { + ffi::is_future_response(&self.gdp) + } + /// Returns the flag indicating if this was a cached data product. + pub fn is_cached_data_product(&self) -> bool { + ffi::is_cached_data_product(&self.gdp) + } + /// Sets the flag indicating this data product is cached + pub fn set_is_cached_data_product(&mut self, cached: bool) { + ffi::set_is_cached_data_product(&self.gdp, cached); + } + /// Returns the url for the REP socket of a future response + pub fn future_socket_url(&self) -> String { + let temp = ffi::get_future_socket_url(&self.gdp); + temp.to_str().unwrap().to_string() + } + /// Sets the timestamp on this GravityDataProduct (typically set by infrastructure at publish) + pub fn set_timestamp(&mut self, ts: u32) { + ffi::set_timestamp(&self.gdp, ts); + } + /// Sets the received timestamp on this GravityDataProduct (typically set by infrastructure at publish) + pub fn set_recieved_timestamp(&mut self, ts: u32) { + ffi::set_recieved_timestamp(&self.gdp, ts); + } + + /// Sets the component id of this GravityDataProduct (typically set by infrastructure at publish) + pub fn set_component_id(&mut self, component_id: &str) { + let_cxx_string!(cid = component_id); + ffi::set_component_id(&self.gdp, &cid); + } + + /// Sets the domain on this GravityDataProduct (typically set by infrastructure at publish) + pub fn set_domain(&mut self, domain: &str) { + let_cxx_string!(d = domain); + ffi::set_domain(&self.gdp, &d); + } + /// Returns the flag indicating whether this message has been relayed or has come from the original source + pub fn is_relayed_data_product(&self) -> bool { + ffi::is_relayed_data_product(&self.gdp) + } + // Sets the flag indicating whether this messsage has been relayed or has come from the original source. + pub fn set_is_relayed_data_product(&mut self, relayed: bool) { + ffi::set_is_relayed_data_product(&self.gdp, relayed); + } + /// Sets the data protocol in this data product (for instance, protobuf2). + pub fn set_protocol(&mut self, protocol: &str) { + let_cxx_string!(p = protocol); + ffi::set_protocol(&self.gdp, &p); + } + /// Returns the data protocol in this data product (for instance, protobuf2). + pub fn protocol(&self) -> String { + let temp = ffi::get_protocol(&self.gdp); + temp.to_str().unwrap().to_string() + } + /// Sets the type of data in this data product (for instance, the full protobuf type name) + pub fn set_type_name(&mut self, data_type: &str) { + let_cxx_string!(dt = data_type); + ffi::set_type_name(&self.gdp, &dt); + } + /// Returns the type of data in this data product (for instance, the full protobuf type name). + pub fn type_name(&self) -> String { + let temp = ffi::get_type_name(&self.gdp); + temp.to_str().unwrap().to_string() + } + /// Returns the time associated with the registration of this data product. + pub fn registration_time(&self) -> u32 { + ffi::get_registration_time(&self.gdp) + } + /// Sets the registration time on this GravityDataProduct (typically set by infrastructure when created). + pub fn set_registration_time(&mut self, ts: u32) { + ffi::set_registration_time(&self.gdp, ts); + } +} \ No newline at end of file diff --git a/src/api/rust/src/gravity_heartbeat_listener.rs b/src/api/rust/src/gravity_heartbeat_listener.rs new file mode 100644 index 000000000..4bec4511d --- /dev/null +++ b/src/api/rust/src/gravity_heartbeat_listener.rs @@ -0,0 +1,14 @@ +/// Interface specification for a trait object that will respond to connection outages of gravity products. +pub trait GravityHeartbeatListener { + + /// Called when another component's heartbeat is off by a certain amount. + fn missed_heartbeat(&mut self, component_id: &str, microsecond_to_last_heartbeat: i64, + interval_in_microseconds: &mut i64); + + /// Called when another component's heartbeat is received. + fn received_heartbeat(&mut self, component_id: &str, interval_in_microseconds: &mut i64); +} + +pub(crate) struct ListenerWrap { + pub(crate) listener: Box, +} \ No newline at end of file diff --git a/src/api/rust/src/gravity_node.rs b/src/api/rust/src/gravity_node.rs new file mode 100644 index 000000000..151101c21 --- /dev/null +++ b/src/api/rust/src/gravity_node.rs @@ -0,0 +1,959 @@ +#![allow(dead_code)] +use std::collections::HashMap; +use std::sync::atomic::AtomicI32; +use std::sync::Arc; +use cxx::{let_cxx_string, CxxString, CxxVector, SharedPtr, UniquePtr}; +use crate::ffi::*; +use crate::gravity_heartbeat_listener::{GravityHeartbeatListener, ListenerWrap}; +use crate::gravity_subscription_monitor::{GravitySubscriptionMonitor, MonitorWrap}; +use crate::{ffi, gravity_subscriber::*, + gravity_data_product::*, gravity_requestor::*, gravity_service_provider::* + , future_response::*}; + +/// Return codes used on a Gravity system. +pub type GravityReturnCode = GravityReturnCodes; + +/// Network transport protocols available on a Gravity system. +pub type GravityTransportType = GravityTransportTypes; + +static COUNTER: AtomicI32 = AtomicI32::new(0); + +/// A component that provides a simple interface point to a Gravity-enabled application. +pub struct GravityNode { + gn: UniquePtr, + subscribers_map: HashMap, UniquePtr)>, + service_provider_map: HashMap, UniquePtr)>, + requestor_provider_map: HashMap, UniquePtr)>, + listener_map: HashMap<(String, String), (Arc, UniquePtr)>, + monitor_map: HashMap, UniquePtr)>, +} + +impl GravityNode { + /// Default constructor. + pub fn new() -> GravityNode { + GravityNode { + gn: ffi::GravityNode(), + subscribers_map: HashMap::new(), + service_provider_map: HashMap::new(), + requestor_provider_map: HashMap::new(), + listener_map: HashMap::new(), + monitor_map: HashMap::new(), + } + } + + /// Constructor that also initializes. + /// Uses component_id to initialize. + pub fn with_id(component_id: &str) -> GravityNode { + let_cxx_string!(cid = component_id); + GravityNode { + gn: ffi::gravity_node_id(&cid), + subscribers_map: HashMap::new(), + service_provider_map: HashMap::new(), + requestor_provider_map: HashMap::new(), + listener_map: HashMap::new(), + monitor_map: HashMap::new(), + } + } + + + /// Initialize the Gravity infrastructure. + pub fn init(&mut self, component_id: &str) -> GravityReturnCode { + let_cxx_string!(cid = component_id); + ffi::init(&self.gn, &cid) + } + + /// Initialize the Gravity infrastructure. + /// Reads the component_id from the Gravity.ini file. + pub fn init_default(&mut self) -> GravityReturnCode { + ffi::init_default(&self.gn) + } + + /// Wait for the GravityNode to exit + pub fn wait_for_exit(&self) { + ffi::wait_for_exit(&self.gn); + } + + /// Gets a token for use in subscribing, unsubscribing, and registering and unregistering relays. + /// Dropping a token does not modify the GravityNode (including subscriptions). + /// Dropping a token will not cause a subscription to cancel, only unsubscribing and dropping the GravityNode will. + /// Once a token is dropped, it and it's information cannot be recovered, so make sure to keep it in scope if you would + /// like to subscribe or unsubscribe with it. + pub fn tokenize_subscriber(&mut self, subscriber: impl GravitySubscriber + 'static) -> SubscriberToken { + let boxed = Box::new(subscriber) as Box; + let mut wrap = Arc::new(SubscriberWrap { subscriber: boxed }); + let cpp_sub = unsafe { + ffi::new_rust_subscriber(GravityNode::sub_filled_internal, + Arc::get_mut(&mut wrap).unwrap() as * mut SubscriberWrap) + }; + let key = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + let tok = SubscriberToken { key: key }; + self.subscribers_map.insert(key, (wrap, cpp_sub)); + tok + } + /// Setup a subscription to a data product throguh the Gravity Service Directory. + /// The data_product_id param is the ID of the data product of interest. + /// The Subscriber is a token that has been tokenized with the GravityNode. + /// If the token has not been registered with this GravityNode, returns NOT_REGISTERED return code + pub fn subscribe(&mut self, data_product_id: &str, subscriber: &SubscriberToken) -> GravityReturnCode { + let_cxx_string!(dpid = data_product_id); + + let item = self.subscribers_map.get(&subscriber.key); + match item { + Some( (_, cpp_sub)) => ffi::subscribe(&self.gn, &dpid, cpp_sub), + None => GravityReturnCodes::NOT_REGISTERED, + } + } + + /// Setup a subscription to a data product throguh the Gravity Service Directory. + /// The data_product_id param is the ID of the data product of interest. + /// The Subscriber is a token that has been tokenized with the GravityNode. + /// Adds optional parameter filter, a text filter to apply to subscription. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode. + pub fn subscribe_with_filter(&mut self, data_product_id: &str, subscriber: &SubscriberToken, filter: &str) -> GravityReturnCode { + let_cxx_string!(dpid = data_product_id); + let_cxx_string!(f = filter); + + let item = self.subscribers_map.get(&subscriber.key); + + match item { + None => GravityReturnCodes::NOT_REGISTERED, + Some( (_, cpp_sub)) => { + ffi::subscribe_filter(&self.gn, &dpid, cpp_sub, &f) + } + } + } + + /// Setup a subscription to a data product throguh the Gravity Service Directory. + /// The data_product_id param is the ID of the data product of interest. + /// The Subscriber is a token that has been tokenized with the GravityNode. + /// Adds optional parameter filter, a text filter to apply to subscription. + /// Adds optional parameter domain, the domain of the network components. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode. + pub fn subscribe_with_domain(&mut self, data_product_id: &str, subscriber: SubscriberToken, filter: &str, domain: &str) -> GravityReturnCode { + let_cxx_string!(dpid = data_product_id); + let_cxx_string!(f = filter); + let_cxx_string!(d = domain); + + let item = self.subscribers_map.get(&subscriber.key); + + match item { + None => GravityReturnCodes::NOT_REGISTERED, + Some( (_, cpp_sub)) => { + ffi::subscribe_domain(&self.gn, &dpid, cpp_sub, &f, &d) + } + } + } + + /// Setup a subscription to a data product throguh the Gravity Service Directory. + /// The data_product_id param is the ID of the data product of interest. + /// The Subscriber is a token that has been tokenized with the GravityNode. + /// Adds optional parameter filter, a text filter to apply to subscription. + /// Adds optional parameter domain, the domain of the network components. + /// Adds optional paramter receive_last_cache_value. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode. + pub fn subscribe_with_cache(&mut self, data_product_id: &str, subscriber: &SubscriberToken, filter: &str, domain: &str, recieve_last_cache_value: bool) -> GravityReturnCode { + let_cxx_string!(dpid = data_product_id); + let_cxx_string!(f = filter); + let_cxx_string!(d = domain); + + let item = self.subscribers_map.get(&subscriber.key); + + match item { + None => GravityReturnCodes::NOT_REGISTERED, + Some( (_, cpp_sub)) => { + ffi::subscribe_cache(&self.gn, &dpid, cpp_sub, &f, &d, recieve_last_cache_value) + } + } + } + + /// Un-subscribe from a data product. + pub fn unsubscribe(&mut self, data_product_id: &str, subscriber: &SubscriberToken) -> GravityReturnCode { + let_cxx_string!(dpid = data_product_id); + let item = self.subscribers_map.get(&subscriber.key); + match item { + Some( (_, cpp_sub)) => ffi::unsubscribe(&self.gn, &dpid, cpp_sub), + None => GravityReturnCodes::SUCCESS, + } + + } + + /// Publish a data product to the Gravity Service Directory. + pub fn publish(&self, data_product: &GravityDataProduct) -> GravityReturnCode + { + ffi::publish(&self.gn, &data_product.gdp) + } + + /// Publish a data product to the Gravity Service Directory. + /// Adds parameter filter_text, a text filter associated with the publish. + pub fn publish_with_filter_text(&self, data_product: &GravityDataProduct, filter_text: &str) -> GravityReturnCode { + let_cxx_string!(ft = filter_text); + ffi::publish_filter(&self.gn, &data_product.gdp, &ft) + } + + /// Publish a data product to the Gravity Service Directory. + /// Adds parameter filter_text, a text filter associated with the publish. + /// Adds paramter timestamp, the time the data product was created. + pub fn publish_with_timestamp(&self, data_product: &GravityDataProduct, filter_text: &str, timestamp: u64) -> GravityReturnCode { + let_cxx_string!(ft = filter_text); + ffi::publish_timestamp(&self.gn, &data_product.gdp, &ft, timestamp) + } + + /// Determine whether there are any subscribers for this DataProduct. + pub fn subscribers_exist(&self, data_product_id: &str, has_subscribers: &mut bool) -> GravityReturnCode { + let_cxx_string!(dpid = data_product_id); + ffi::subsribers_exist(&self.gn, &dpid, has_subscribers) + } + + /// Get a token to use to request services asynchronously + /// Dropping a token does not change the GravityNode in any way. + /// Once a token is dropped, the information it and the information it contains is not recoverable + pub fn tokenize_requestor (&mut self, requestor: impl GravityRequestor + 'static) -> RequestorToken{ + let boxed = Box::new(requestor) as Box; + let mut wrap = Arc::new(RequestorWrap { requestor: boxed }); + let cpp_sub = unsafe { + ffi::new_rust_requestor( + GravityNode::request_filled_internal, + GravityNode::request_timeout_internal, + Arc::get_mut(&mut wrap).unwrap() as * mut RequestorWrap) + }; + let key = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + let tok = RequestorToken { key: key }; + self.requestor_provider_map.insert(key, (wrap, cpp_sub)); + tok + + } + /// Make an asynchronous request against a service provider through the Gravity Service Directory. + /// requestor is a token that has been tokenizedd with the GravityNode. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode. + pub fn request_async(&mut self, service_id: &str, request: &GravityDataProduct, + requestor: &RequestorToken) -> GravityReturnCode { + + let_cxx_string!(sid = service_id); + + let item = self.requestor_provider_map.get(&requestor.key); + + match item { + None => GravityReturnCodes::NOT_REGISTERED, + Some((_, cpp_req)) => { + ffi::request_async(&self.gn, &sid, &request.gdp, cpp_req) + } + } + + } + /// Make an asynchronous request against a service provider through the Gravity Service Directory. + /// requestor is a token that has been tokenize with the GravityNode. + /// With parameter request_id, the identifier for this request. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode. + pub fn request_async_with_request_id(&mut self, service_id: &str, request: &GravityDataProduct, + requestor: &RequestorToken, request_id: &str) -> GravityReturnCode { + let_cxx_string!(sid = service_id); + let_cxx_string!(req = request_id); + + let item = self.requestor_provider_map.get(&requestor.key); + + match item { + None => GravityReturnCodes::NOT_REGISTERED, + Some((_, cpp_req)) => { + ffi::request_async_request_id(&self.gn, &sid, &request.gdp, cpp_req, &req) + } + } + } + + /// Make an asynchronous request against a service provider through the Gravity Service Directory. + /// requestor is a token that has been tokenize with the GravityNode. + /// With parameter request_id, the identifier for this request. + /// With parameter timeout_milliseconds, the timeout in Milliseconds (-1 for no timeout). + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode. + pub fn request_async_with_timeout(&mut self, service_id: &str, request: &GravityDataProduct, + requestor: &RequestorToken, request_id: &str, timeout_milliseconds: i32) -> GravityReturnCode { + + let_cxx_string!(sid = service_id); + let_cxx_string!(req = request_id); + + let item = self.requestor_provider_map.get(&requestor.key); + + match item { + None => GravityReturnCodes::NOT_REGISTERED, + Some((_, cpp_req)) => { + ffi::request_async_timeout(&self.gn, &sid, &request.gdp, cpp_req, &req, timeout_milliseconds) + } + } + } + + /// Make an asynchronous request against a service provider through the Gravity Service Directory. + /// requestor is a token that has been tokenize with the GravityNode. + /// With parameter request_id, the identifier for this request. + /// With parameter timeout_milliseconds, the timeout in Milliseconds (-1 for no timeout). + /// With parameter domain, the domain of the network components. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode. + pub fn request_async_with_domain(&mut self, service_id: &str, request: &GravityDataProduct, + requestor: &RequestorToken, request_id: &str, timeout_milliseconds: i32, domain: &str) -> GravityReturnCode + { + let_cxx_string!(sid = service_id); + let_cxx_string!(req = request_id); + let_cxx_string!(dom = domain); + + let item = self.requestor_provider_map.get(&requestor.key); + + match item { + None => GravityReturnCodes::NOT_REGISTERED, + Some((_, cpp_req)) => { + ffi::request_async_domain(&self.gn, &sid, &request.gdp, cpp_req, &req, timeout_milliseconds, &dom) + } + } + + } + + /// Makes a synchronous request against a service provider. + /// Returns None upon failure, Some(GravityDataProduct), where the GravityDataProduct is the data product + /// representation of the response. + pub fn request_sync(&self, service_id: &str, request: &GravityDataProduct) -> Option { + let_cxx_string!(sid = service_id); + let gdp = ffi::request_sync(&self.gn, &sid, &request.gdp); + if gdp.is_null() { + return None; + } + Some(GravityNode::to_rust_gdp(gdp.as_ref().unwrap())) + } + + /// Makes a synchronous request against a service provider. + /// With parameter timeout_milliseconds, the timeout in milliseconds (-1 for no timeout). + /// Returns None upon failure, Some(GravityDataProduct), where the GravityDataProduct is the data product + /// representation of the response. + pub fn request_sync_with_timeout(&self, service_id: &str, request: &GravityDataProduct, timeout_milliseconds: i32) -> Option { + let_cxx_string!(sid = service_id); + let gdp = ffi::request_sync_timeout(&self.gn, &sid, &request.gdp, timeout_milliseconds); + if gdp.is_null() { + return None; + } + Some(GravityNode::to_rust_gdp(gdp.as_ref().unwrap())) + } + + /// Makes a synchronous request against a service provider. + /// With parameter timeout_milliseconds, the timeout in milliseconds (-1 for no timeout). + /// With parameter domain, the domain of the network components + /// Returns None upon failure, Some(GravityDataProduct), where the GravityDataProduct is the data product + /// representation of the response. + pub fn request_sync_with_domain(&self, service_id: &str, request: &GravityDataProduct, timeout_milliseconds: i32, domain: &str) -> Option { + let_cxx_string!(sid = service_id); + let_cxx_string!(d = domain); + let gdp = ffi::request_sync_domain(&self.gn, &sid, &request.gdp, timeout_milliseconds, &d); + if gdp.is_null() { + return None; + } + Some(GravityNode::to_rust_gdp(gdp.as_ref().unwrap())) + } + + /// Starts a heart beat for this GravityNode. + /// Returns success flag. + pub fn start_heartbeat(&mut self, interval_in_microseconds: i64) -> GravityReturnCode { + ffi::start_heartbeat(&self.gn, interval_in_microseconds) + } + + /// Stops the heart beat for this GravityNode + /// Returns success flag. + pub fn stop_heartbeat(&mut self) -> GravityReturnCode { + ffi::stop_heartbeat(&self.gn) + } + + /// Gravity.ini parsing function + /// key is which item in the Gravity.ini will contain the value. + /// default_value is what it should return if the key cannot be found. + pub fn get_string_param(&self, key: &str, default_value: &str) -> String { + let_cxx_string!(k = key); + let_cxx_string!(default_val = default_value); + ffi::get_string_param(&self.gn, &k, &default_val).to_string() + } + + /// Gravity.ini parsing function + /// key is which item in the Gravity.ini will contain the value. + /// default_value is what it should return if the key cannot be found. + pub fn get_int_param(&self, key: &str, default_value: i32) -> i32 { + let_cxx_string!(k = key); + ffi::get_int_param(&self.gn, &k, default_value) + } + + /// Gravity.ini parsing function + /// key is which item in the Gravity.ini will contain the value. + /// default_value is what it should return if the key cannot be found. + pub fn get_float_param(&self, key: &str, default_value: f64) -> f64 { + let_cxx_string!(k = key); + ffi::get_float_param(&self.gn, &k, default_value) + } + + /// Gravity.ini parsing function + /// key is which item in the Gravity.ini will contain the value. + /// default_value is what it should return if the key cannot be found. + pub fn get_bool_param(&self, key: &str, default_value: bool) -> bool { + let_cxx_string!(k = key); + ffi::get_bool_param(&self.gn, &k, default_value) + } + + /// Get the ID of this gravity node (given in the init function). + pub fn component_id(&self) -> String + { (*ffi::get_component_ID(&self.gn)).to_str().unwrap().to_string()} + + + /// Register a data product with the Gravity, and optionally, the Service Directrory, + /// making it available to the rest of the Gravity-enabled system. + /// Returns success flag. + pub fn register_data_product(&self, data_product_id: &str, + transport_type: GravityTransportType) -> GravityReturnCode + { + let_cxx_string!(dpid = data_product_id); + ffi::register_data_product(&self.gn, &dpid, transport_type) + } + + /// Register a data product with the Gravity infrastructure, + /// making it available to the rest of the Gravity-enabled system. + /// With parameter cache_last_value, a flag used to signify whether or not the GravityNode + /// will cache the last sent value for a published data product. + /// Returns success flag. + pub fn register_data_product_with_cache(&self, data_product_id: &str, + transport_type: GravityTransportType, cache_last_value: bool) -> GravityReturnCode + { + let_cxx_string!(dpid = data_product_id); + ffi::register_data_product_cache(&self.gn, &dpid, transport_type, cache_last_value) + } + + /// Un-register a data product, resulting in its removal from the Gravity Service Directory. + pub fn unregister_data_product(&self, data_product_id: &str) -> GravityReturnCode{ + let_cxx_string!(dpid = data_product_id); + ffi::unregister_data_product(&self.gn, &dpid) + } + + /// Get a token to register services + /// Gives GravityNode ownership of the GravityServiceProvider so it controls its scope + /// Dropping a token will not affect the GravityNode in any way (including its registered services) + /// Use caution since once a token its dropped, it is not recoverable. + // pub fn tokenize_service(&mut self, server: impl GravityServiceProvider + 'static) -> ServiceToken { + // let boxed = Box::new(server) as Box; + // let mut wrap = Arc::new(ServiceWrap { service: boxed }); + // let cpp_sub = unsafe { + // ffi::new_rust_service_provider( + // GravityNode::request_internal, + // Arc::get_mut(&mut wrap).unwrap() as * mut ServiceWrap) + // }; + // let key = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + // let tok = ServiceToken { key: key }; + // self.service_provider_map.insert(key, (wrap, cpp_sub)); + // tok + // } + + /// Registers as a service provider with Gravity. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode + pub fn register_service(&mut self, service_id: &str, transport_type: GravityTransportType, server: impl GravityServiceProvider + 'static) -> GravityReturnCode { + let_cxx_string!(sid = service_id); + + let boxed = Box::new(server) as Box; + let mut wrap = Arc::new(ServiceWrap { service: boxed }); + let cpp_sub = unsafe { + ffi::new_rust_service_provider( + GravityNode::request_internal, + Arc::get_mut(&mut wrap).unwrap() as * mut ServiceWrap) + }; + let key = service_id.to_string(); + let exists = self.service_provider_map.insert(key.clone(), (wrap, cpp_sub)); + + match exists { + Some( _ ) => {ffi::unregister_service(&self.gn, &sid);}, + None => (), + } + + + let (_, cpp_sub) = self.service_provider_map.get(&key).unwrap(); + ffi::register_service(&self.gn, &sid, transport_type, cpp_sub) + } + + // Unregister as a service provider with the Gravity Service Directory. + // Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode + pub fn unregister_service(&mut self, service_id: &str) -> GravityReturnCode { + let_cxx_string!(sid = service_id); + ffi::unregister_service(&self.gn, &sid) + } + + + /// Gets a token to use to register a heartbeat listener corresponding to the GravityHeartbeatListener provided. + /// Dropping a token does not affect the GravityNode and it and its information are irrecoverable + // pub fn tokenize_heartbeat_listener(&mut self, listener: impl GravityHeartbeatListener + 'static) -> HeartbeatListenerToken { + // let boxed = Box::new(listener); + + // let mut wrap = Arc::new(ListenerWrap { listener: boxed }); + // let cpp_listener = unsafe { + // ffi::new_rust_heartbeat_listener( + // GravityNode::missed_heartbeat_internal, + // GravityNode::received_heartbeat_internal, + // Arc::get_mut(&mut wrap).unwrap() as * mut ListenerWrap) + // }; + // let key = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + // let tok = HeartbeatListenerToken { key: key }; + // self.listener_map.insert(key, (wrap, cpp_listener)); + // tok + // } + /// Registers a callback to be called when we don't get a heartbeat from another component. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode + pub fn register_heartbeat_listener(&mut self, component_id: &str, interval_in_microseconds: i64, listener: impl GravityHeartbeatListener + 'static) -> GravityReturnCode{ + let_cxx_string!(cid = component_id); + + let boxed = Box::new(listener); + + let mut wrap = Arc::new(ListenerWrap { listener: boxed }); + let cpp_listener = unsafe { + ffi::new_rust_heartbeat_listener( + GravityNode::missed_heartbeat_internal, + GravityNode::received_heartbeat_internal, + Arc::get_mut(&mut wrap).unwrap() as * mut ListenerWrap) + }; + + let ret = ffi::register_heartbeat_listener( + &self.gn, + &cid, + interval_in_microseconds, + &cpp_listener); + + self.listener_map.insert((component_id.to_string(), "".to_string()), (wrap, cpp_listener)); + ret + + } + + /// Registers a callback to be called when we don't get a heartbeat from another component. + /// With paramter domain, the name of the domain for the component_id. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode + pub fn register_heartbeat_listener_with_domain(&mut self, component_id: &str, interval_in_microseconds: i64, + listener: impl GravityHeartbeatListener + 'static, domain: &str) -> GravityReturnCode + { + let_cxx_string!(cid = component_id); + let_cxx_string!(d = domain); + + let boxed = Box::new(listener); + + let mut wrap = Arc::new(ListenerWrap { listener: boxed }); + let cpp_listener = unsafe { + ffi::new_rust_heartbeat_listener( + GravityNode::missed_heartbeat_internal, + GravityNode::received_heartbeat_internal, + Arc::get_mut(&mut wrap).unwrap() as * mut ListenerWrap) + }; + + let ret = ffi::register_heartbeat_listener_domain( + &self.gn, + &cid, + interval_in_microseconds, + &cpp_listener, + &d); + + self.listener_map.insert((component_id.to_string(), domain.to_string()), (wrap, cpp_listener)); + ret + + } + + /// Unregisters a callback for when we get a heartbeat from another component. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode + pub fn unregister_heartbeat_listener(&mut self, component_id: &str) -> GravityReturnCode { + let_cxx_string!(cid = component_id); + let ret = ffi::unregister_heartbeat_listener(&self.gn, &cid); + self.listener_map.remove(&(component_id.to_string(), "".to_string())); + ret + } + + /// Unregisters a callback for when we get a heartbeat from another component. + /// With paramter domain, the name of the domain for the component_id. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode + pub fn unregister_heartbeat_listener_with_domain(&mut self, component_id: &str, domain: &str) -> GravityReturnCode { + let_cxx_string!(cid = component_id); + let_cxx_string!(d = domain); + let ret = ffi::unregister_heartbeat_listener_domain(&self.gn, &cid, &d); + self.listener_map.remove(&(component_id.to_string(), domain.to_string())); + ret + } + + /// Register a relay that will act as a pass-through for the given data_product_id. It will be + /// a publisher and subscriber for the given data_product_id, but other components will only subscribe + /// to this data if they are on the same host (local__only == true), or it it is acting as a global relay (local_only == false). + /// The Gravity infrastructure automatically handles which components should receive relayed or non-relayed data. + /// + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode + pub fn register_relay(&mut self, data_product_id: &str, subscriber: &SubscriberToken, + local_only: bool, transport_type: GravityTransportType) -> GravityReturnCode { + let_cxx_string!(dpid = data_product_id); + + let item = self.subscribers_map.get(&subscriber.key); + + match item { + None => GravityReturnCodes::NOT_REGISTERED, + Some( (_, cpp_sub) ) => { + ffi::register_relay( + &self.gn, + &dpid, + cpp_sub, + local_only, + transport_type) + } + } + } + + /// Register a relay that will act as a pass-through for the given data_product_id. It will be + /// a publisher and subscriber for the given data_product_id, but other components will only subscribe + /// to this data if they are on the same host (local_only == true), or it it is acting as a global relay (local_only == false). + /// The Gravity infrastructure automatically handles which components should receive relayed or non-relayed data. + /// + /// With parameter cache_last_value, flag used to signifgy whether or not GravityNode will cache the last setn value for a published + /// data product. Note that using a Relay with cached_last_value = true is atypical and may result in duplicate messages received + /// by subscribers during the relay start/stop transition. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode + pub fn register_relay_with_cache(&mut self, data_product_id: &str, subscriber: &SubscriberToken, + local_only: bool, transport_type: GravityTransportType, cache_last_value: bool) -> GravityReturnCode { + let_cxx_string!(dpid = data_product_id); + + let item = self.subscribers_map.get(&subscriber.key); + + match item { + None => GravityReturnCodes::NOT_REGISTERED, + Some( (_, cpp_sub) ) => { + ffi::register_relay_cache( + &self.gn, + &dpid, + cpp_sub, + local_only, + transport_type, + cache_last_value) + } + } + } + + /// Unregister a relay for the given data_product_id. Handles unregistering as a publisher and subscriber. + pub fn unregister_relay(&mut self, data_product_id: &str, subscriber: &SubscriberToken) -> GravityReturnCode { + let_cxx_string!(dpid = data_product_id); + + let item = self.subscribers_map.get(&subscriber.key); + + match item { + None => GravityReturnCodes::SUCCESS, + Some( (_,cpp_sub) ) => { + ffi::unregister_relay(&self.gn, &dpid, cpp_sub) + } + + } + } + + /// Returns a String representation of the provided error code. + pub fn code_string(&self, code: GravityReturnCode) -> String { + ffi::get_code_string(&self.gn, code).to_str().unwrap().to_string() + } + + /// Utility method to get the host machine's IP address. + pub fn ip(&self) -> String { + ffi::get_IP(&self.gn).to_str().unwrap().to_string() + } + + /// Returns the domain with which this node is associated. + pub fn domain(&self) -> String { + ffi::get_domain(&self.gn).to_str().unwrap().to_string() + } + + /// Creates and reuturns a FutureResponse for a delayed response to requests. + pub fn create_future_response(&self) -> FutureResponse { + let temp = ffi::create_future_response(&self.gn); + FutureResponse { fr: temp } + } + + /// Send a FutureResponse + /// Returns success flag. + pub fn send_future_response(&self, future_response: FutureResponse) -> GravityReturnCode { + ffi::send_future_response(&self.gn, &future_response.fr) + } + + /// Use a GravitySubscriptionMonitor to get a token + /// Stores the trait object within the GravityNode so the GravityNode has ownership + /// Use the token for setting and clearing subscription monitor. + /// Droppping the token will NOT have any effect on the GravityNode, so use caution when tokens go out of scope. + /// They are not recoverable + pub fn tokenize_subscription_monitor(&mut self, monitor: impl GravitySubscriptionMonitor + 'static) -> SubscriptionMonitorToken { + + let boxed = Box::new(monitor) as Box; + let mut wrap = Arc::new(MonitorWrap { monitor: boxed }); + + let cpp_monitor = unsafe { + ffi::new_rust_subscription_monitor(GravityNode::subscription_timeout_internal, Arc::get_mut(&mut wrap).unwrap() as * mut MonitorWrap) + }; + + let key = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + let tok = SubscriptionMonitorToken { key: key }; + self.monitor_map.insert(key, (wrap, cpp_monitor)); + tok + } + /// Setup a GravitySubscriptionMonitor to receive subscription timeout information through + /// the Gravity Service Directory. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode. + pub fn set_subscription_timout_monitor(&mut self, data_product_id: &str, + monitor: &SubscriptionMonitorToken, milli_second_timeout: i32) -> GravityReturnCode { + + let_cxx_string!(dpid = data_product_id); + + let item = self.monitor_map.get(&monitor.key); + match item { + None => GravityReturnCodes::NOT_REGISTERED, + Some( (_, cpp_monitor)) => { + ffi::set_subscription_timeout_monitor( + &self.gn, + &dpid, + cpp_monitor, + milli_second_timeout) + } + } + } + + /// Setup a GravitySubscriptionMonitor to receive subscription timeout information through + /// the Gravity Service Directory. + /// + /// With parameter filter. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode. + pub fn set_subscription_timout_monitor_with_filter(&mut self, data_product_id: &str, + monitor: &SubscriptionMonitorToken, milli_second_timeout: i32, filter: &str) -> GravityReturnCode { + + let_cxx_string!(dpid = data_product_id); + let_cxx_string!(f = filter); + + let item = self.monitor_map.get(&monitor.key); + + match item { + None => GravityReturnCodes::NOT_REGISTERED, + Some( (_, cpp_monitor)) => { + ffi::set_subscription_timeout_monitor_filter( + &self.gn, + &dpid, + cpp_monitor, + milli_second_timeout, + &f) + } + + } + + } + + /// Setup a GravitySubscriptionMonitor to receive subscription timeout information through + /// the Gravity Service Directory. + /// + /// With parameter filter. + /// With parameter domain. + /// Returns success flag or not_registered flag if the token is not tokenized with the current GravityNode. + pub fn set_subscription_timout_monitor_with_domain(&mut self, data_product_id: &str, + monitor: &SubscriptionMonitorToken, milli_second_timeout: i32, filter: &str, domain: &str) -> GravityReturnCode { + + let_cxx_string!(dpid = data_product_id); + let_cxx_string!(f = filter); + let_cxx_string!(d = domain); + + let item = self.monitor_map.get(&monitor.key); + + match item { + None => GravityReturnCodes::NOT_REGISTERED, + Some( (_, cpp_monitor)) => { + ffi::set_subscription_timeout_monitor_domain( + &self.gn, + &dpid, + cpp_monitor, + milli_second_timeout, + &f, + &d) + } + + } + + } + + /// Remove the given data_product_id from the given GravitySubscriptionMonitor. + /// Returns success flag. + pub fn clear_subscription_timeout_monitor(&self, data_product_id: &str, + monitor: &SubscriptionMonitorToken) -> GravityReturnCode { + let_cxx_string!(dpid = data_product_id); + + let item = self.monitor_map.get(&monitor.key); + + match item { + None => GravityReturnCode::SUCCESS, + Some((_, cpp_monitor)) => { + ffi::clear_subscription_timeout_monitor( + &self.gn, + &dpid, + cpp_monitor) + } + } + } + + /// Remove the given data_product_id from the given GravitySubscriptionMonitor. + /// With parameter filter. + /// Returns success flag. + pub fn clear_subscription_timeout_monitor_with_filter(&self, data_product_id: &str, + monitor: &SubscriptionMonitorToken, filter: &str) -> GravityReturnCode { + let_cxx_string!(dpid = data_product_id); + let_cxx_string!(f = filter); + + + let item = self.monitor_map.get(&monitor.key); + + match item { + None => GravityReturnCode::SUCCESS, + Some((_, cpp_monitor)) => { + ffi::clear_subscription_timeout_monitor_filter( + &self.gn, + &dpid, + cpp_monitor, + &f) + } + } + } + + /// Remove the given data_product_id from the given GravitySubscriptionMonitor. + /// With parameter filter and parameter domain. + /// Returns success flag. + pub fn clear_subscription_timeout_monitor_with_domain(&self, data_product_id: &str, + monitor: &SubscriptionMonitorToken, filter: &str, domain: &str) -> GravityReturnCode { + let_cxx_string!(dpid = data_product_id); + let_cxx_string!(f = filter); + let_cxx_string!(d = domain); + + let item = self.monitor_map.get(&monitor.key); + + match item { + None => GravityReturnCode::SUCCESS, + Some((_, cpp_monitor)) => { + ffi::clear_subscription_timeout_monitor_domain( + &self.gn, + &dpid, + cpp_monitor, + &f, + &d) + } + } + + } + + + fn to_rust_gdp(gdp: &GDataProduct) -> GravityDataProduct{ + GravityDataProduct::from_gdp(ffi::copy_gdp(gdp)) + } + + + fn rustify(data_products: &CxxVector) -> Vec { + let mut v = Vec::new(); + for item in data_products.iter() { + let to_add = GravityNode::to_rust_gdp(item); + v.push(to_add); + } + v + } + + fn sub_filled_internal(data_products: &CxxVector, ptr: * mut SubscriberWrap) { + + let v = GravityNode::rustify(data_products); + let next = unsafe { + &mut (*ptr) + }; + let subscriber = &mut next.subscriber; + subscriber.subscription_filled(v); + + + + } + + fn request_filled_internal(service_id: &CxxString, request_id: &CxxString, response: &GDataProduct, ptr: * mut RequestorWrap) { + let sid = service_id.to_str().unwrap(); + let rid = request_id.to_str().unwrap(); + let gdp = GravityNode::to_rust_gdp(response); + + let next = unsafe { + &mut (*ptr) + }; + let requestor = &mut next.requestor; + requestor.request_filled(sid, rid, &gdp); + } + + fn request_timeout_internal(service_id: &CxxString, request_id: &CxxString, ptr: * mut RequestorWrap) { + let sid = service_id.to_str().unwrap(); + let rid = request_id.to_str().unwrap(); + + let next = unsafe { + &mut (*ptr) + }; + let requestor = &mut next.requestor; + requestor.request_timeout(sid, rid); + } + + fn request_internal(service_id: &CxxString, data_product: &GDataProduct, ptr: * mut ServiceWrap) -> SharedPtr{ + let gdp = GravityNode::to_rust_gdp(data_product); + let sid = service_id.to_str().unwrap(); + + let next = unsafe { + &mut (*ptr) + }; + let service_provider = &mut next.service; + let g = service_provider.request(sid, &gdp); + ffi::copy_gdp_shared(&g.gdp) + } + + fn missed_heartbeat_internal(component_id: &CxxString, microsecond_to_last_heartbeat: i64, + interval_in_microseconds: &mut i64, ptr: * mut ListenerWrap){ + + let cid = component_id.to_str().unwrap(); + let next = unsafe { + &mut (*ptr) + }; + let listener = &mut next.listener; + listener.missed_heartbeat(cid, microsecond_to_last_heartbeat, interval_in_microseconds); + } + + fn received_heartbeat_internal(component_id: &CxxString, interval_in_microseconds: &mut i64, ptr: * mut ListenerWrap) { + let cid = component_id.to_str().unwrap(); + + let next = unsafe { + &mut (*ptr) + }; + let listener = &mut next.listener; + listener.received_heartbeat(cid, interval_in_microseconds); + } + + fn subscription_timeout_internal (data_product_id: &CxxString, milli_seconds_since_last: i32, + filter: &CxxString, domain: &CxxString, ptr: * mut MonitorWrap) { + let dpid = data_product_id.to_str().unwrap(); + let f = filter.to_str().unwrap(); + let d = domain.to_str().unwrap(); + + let next = unsafe { + &mut (*ptr) + }; + let monitor = &mut next.monitor; + monitor.subscription_timeout(dpid, milli_seconds_since_last, f, d); + } + +} + +/// Token used to subscribe and unsubscribe. As well as register and unregister relays. +/// Must use the token on the GravityNode that you tokenized with. +pub struct SubscriberToken { + pub(crate) key: i32, +} + +/// Token used to request against a service provider +/// Must use the token on the GravityNode that you tokenized with. +pub struct RequestorToken { + pub(crate) key: i32, +} + +/// Token to register as a service provider the GravityServiceProvider used to create this token. +/// Must use the token on the GravityNode that you tokenized with. +pub struct ServiceToken { + pub(crate) key: i32, +} + +/// Token to register as a Heartbeat listener. +/// Must use the token on the GravityNode that you tokenized with. +pub struct HeartbeatListenerToken { + pub(crate) key: i32, +} + +/// Token used to set and clear a subscription monitor +/// Must use the token on the GravityNode that you tokenized with. +pub struct SubscriptionMonitorToken { + pub(crate) key: i32, +} \ No newline at end of file diff --git a/src/api/rust/src/gravity_requestor.rs b/src/api/rust/src/gravity_requestor.rs new file mode 100644 index 000000000..9bfc8e923 --- /dev/null +++ b/src/api/rust/src/gravity_requestor.rs @@ -0,0 +1,16 @@ + +use crate::gravity_data_product::GravityDataProduct; + +/// Interface specification for a trait object that will function as the "client" side of a request-response interaction. +pub trait GravityRequestor { + /// Called when a response to a request is received through the Gravity infrastructure. + fn request_filled(&mut self, service_id: &str, request_id: &str, response: &GravityDataProduct); + + /// Called when the response to a request has timed out. + fn request_timeout(&mut self, service_id: &str, request_id: &str); +} + + +pub(crate) struct RequestorWrap { + pub(crate) requestor: Box, +} \ No newline at end of file diff --git a/src/api/rust/src/gravity_service_provider.rs b/src/api/rust/src/gravity_service_provider.rs new file mode 100644 index 000000000..e56086d49 --- /dev/null +++ b/src/api/rust/src/gravity_service_provider.rs @@ -0,0 +1,11 @@ +use crate::gravity_data_product::GravityDataProduct; + +/// Interface specification for a trait object that will function as the "server" side of a request-response interaction. +pub trait GravityServiceProvider { + /// Called when a request is made through the Gravity infrastructure + fn request(&mut self, service_id: &str, data_product: &GravityDataProduct) -> GravityDataProduct; +} + +pub struct ServiceWrap { + pub(crate) service: Box, +} \ No newline at end of file diff --git a/src/api/rust/src/gravity_subscriber.rs b/src/api/rust/src/gravity_subscriber.rs new file mode 100644 index 000000000..a9723e317 --- /dev/null +++ b/src/api/rust/src/gravity_subscriber.rs @@ -0,0 +1,12 @@ +use crate::gravity_data_product::GravityDataProduct; + +/// Interface specification for an object that will respond to subscriptions. +pub trait GravitySubscriber { + /// Called on implementing trait object when a registered subscription is filled + /// with 1 or more GravityDataProducts. + fn subscription_filled(&mut self, data_products: Vec); +} + +pub(crate) struct SubscriberWrap { + pub(crate) subscriber: Box, +} \ No newline at end of file diff --git a/src/api/rust/src/gravity_subscription_monitor.rs b/src/api/rust/src/gravity_subscription_monitor.rs new file mode 100644 index 000000000..3010cd719 --- /dev/null +++ b/src/api/rust/src/gravity_subscription_monitor.rs @@ -0,0 +1,11 @@ +/// Interface specification for a trait object that will respons to subscription timeouts. +pub trait GravitySubscriptionMonitor { + /// Called on implementing object when a subscription is not received within the registered time constraints. + fn subscription_timeout(&mut self, data_product_id: &str, milli_seconds_since_last: i32, + filter: &str, domain: &str); + +} + +pub(crate) struct MonitorWrap { + pub(crate) monitor: Box, +} \ No newline at end of file diff --git a/src/api/rust/src/lib.rs b/src/api/rust/src/lib.rs new file mode 100644 index 000000000..04fbf694c --- /dev/null +++ b/src/api/rust/src/lib.rs @@ -0,0 +1,206 @@ +//! [![github]](https://github.com/astrauc/gravity) [![crates-io]](https://github.com/astrauc/gravity) [![docs-rs]](https://github.com/astrauc/gravity) +//! +//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github +//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust +//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs +//! +//! The goal of this crate is to provide Rust bindings to Gravity +//! it relies on the crate [CXX](https://crates.io/crates/cxx). +//! +//! This crate compiles the Gravity C++ library and links it with Rust code to give a +//! seamless appearance of idiomatic Rust. +//! +//! Note that adding Gravity as a dependency to a cargo project only means that it will bridge your Rust code +//! to work with Gravity. It will not create any of the Gravity components. To get the components, follow the [Gravity Build Guide](https://github.com/aphysci/gravity/wiki/GravitySetup) +//! +//!
+//! +//! +//! Dependencies for using this crate are cmake, a C++ compiler (e.g. g++), bison, and flex. +//! Make sure these 4 items are installed before adding this crate as a dependency. +//! +//! +//! *Compiler support: requires rustc 1.85+ and c++11 or newer* +//! +//! +//! # Overview +//! +//! The idea is to call directly into the libraries compiled by [Gravity](https://github.com/aphysci/gravity) +//! The CXX crate does not support non-const member functions, so in order to make this feel like idiomatic rust +//! each struct is a wrapper that contains a pointer to a C++ object (i.e. GravityNode or GravityDataProduct) and +//! and its member functions supply that as pointer as a paramter to the C++ function call. +//! +//! To make this work, there must be a bridge on each side of the language barrier. These two items work together to +//! provide a transition from Rust to C++ that feels like Rust without modifying the original Gravity source code. +//! +//! The way that this crate deals with the concept of inheritance is by giving the work to C++. +//! In the C++ files, there are classes that inherit from the abstract classes from Gravity in C++ (i.e. RustSubscriber +//! inherits from GravitySubscriber). These Rust classes take a function pointer to a rust function that converts C++ types +//! to Rust types, and a pointer to the trait object that Rust treats as a subscriber. This will make more sense in the subscriber example. +//! +//! The function pointer unwraps the pointer into its trait object, and calls the method with the "rustified" types. +//! +//! # Example: Basic Gravity Publisher/Subscriber +//! +//! ## Publisher +//! ```rust +//! include!(concat!(env!("OUT_DIR"), "/protobuf/mod.rs")); +//! +//! use gravity::{GravityNode, GravityDataProduct, GravityTransportType}; +//! use std::time; +//! use BasicCounterDataProduct::*; +//! +//! fn main () { +//! +//! let mut gn = GravityNode::new(); +//! gn.init("RustProtobufExample"); +//! +//! gn.register_data_product( +//! //identifies the data product to the service directory so others +//! //can subscribe to it +//! "BasicCounterDataProduct", +//! //Assign a transport type to the socket (almost always tcp, unless you are only +//! //using the gravity data product between two processes on the same computer) +//! GravityTransportType::TCP); +//! +//! //TODO: set this when you want the program to quit if you need to +//! //clean up before exiting +//! let mut quit = false; +//! let mut count = 1; +//! while !quit +//! { +//! //create a data product to send across the network of type "BasicCounterDataProduct" +//! let mut counter_data_product = GravityDataProduct::with_id("BasicCounterDataProduct"); +//! +//! //Initialize our message +//! let mut counter_data_pb = BasicCounterDataProductPB::new(); +//! counter_data_pb.set_count(count); +//! +//! //Put message into data product +//! counter_data_product.set_data(&counter_data_pb); +//! +//! //Publish the data product +//! gn.publish(&counter_data_product); +//! +//! //Increment count +//! count += 1; +//! if count > 5 { +//! quit = true; +//! } +//! +//! //Sleep for 1 second. +//! std::thread::sleep(time::Duration::from_secs(1)); +//! } +//! +//! // gn.wait_for_exit(); +//!} +//! +//!``` +//! +//! ## Subscriber +//! +//! ```rust +//! include!(concat!(env!("OUT_DIR"), "/protobuf/mod.rs")); +//! +//! use gravity::GravityNode; +//! use gravity::GravitySubscriber; +//! use gravity::GravityDataProduct; +//! use gravity::SpdLog; +//! use BasicCounterDataProduct::*; +//! +//! struct MySubscriber {} +//! +//! impl GravitySubscriber for MySubscriber { +//! +//! fn subscription_filled(&mut self, data_products: Vec) { +//! +//! for data_product in data_products.iter() { +//! //Get the protobuf object from the message +//! let mut counter_data_pb = BasicCounterDataProductPB::new(); +//! data_product.populate_message(&mut counter_data_pb); +//! +//! //Process the message +//! SpdLog::warn(format!("Current Count: {}", counter_data_pb.count())); +//! +//! +//! +//! } +//! } +//! } +//! +//! // And now to use the subscriber +//! +//! fn main() { +//! let mut gn = GravityNode::new(); +//! gn.init("ProtobufDataProductSubscriber"); +//! +//! //This is just an example, you can have any other way to +//! //instantiate your own GravitySubscriber, as long as it impl GravitySubscriber trait +//! let subscriber = gn.tokenize_subscriber( MySubscriber {} ); +//! +//! //subscribe to the data product +//! //takes in a borrowed subscriber token +//! gn.subscribe("BasicCounterDataProduct", &subscriber); +//! +//! //gn.wait_for_exit() +//! } +//! +//! +//! +//! +//! +//! +//! +//! +//!``` +//! Note that if a GravityNode is to subscribe, it must be declared as mut +//! since GravityNodes hold a reference to the subscriber object it creates. +//! +//! # Cargo based setup +//! +//! Fist, make sure you install the necessary C++ dependencies. +//! For example, on linux: +//! +//! ```sh +//! sudo apt install cmake g++ bison flex +//! ``` +//! +//! Assuming you have the necessary dependencies installed, all you need is to add +//! Gravity to your Cargo.toml +//! +//! ```toml +//! # Cargo.toml +//! +//! [dependencies] +//! gravity = { git = "https://github.com/astrauc/gravity.git" } +//! ``` +//! +//! + + + +#![allow(deprecated)] +#![allow(dead_code)] + +mod spdlog; +mod gravity_node; +mod gravity_data_product; +mod gravity_subscriber; +mod gravity_requestor; +mod gravity_service_provider; +mod future_response; +mod gravity_heartbeat_listener; +mod gravity_subscription_monitor; +mod ffi; +mod unit_tests; + +pub use crate::gravity_data_product::GravityDataProduct; +pub use crate::gravity_node::*; +pub use crate::gravity_requestor::GravityRequestor; +pub use crate::gravity_service_provider::GravityServiceProvider; +pub use crate::gravity_subscriber::GravitySubscriber; +pub use crate::gravity_heartbeat_listener::GravityHeartbeatListener; +pub use crate::gravity_subscription_monitor::GravitySubscriptionMonitor; +pub use crate::future_response::FutureResponse; +pub use crate::spdlog::SpdLog; + diff --git a/src/api/rust/src/protobuf/BasicCounterDataProduct.proto b/src/api/rust/src/protobuf/BasicCounterDataProduct.proto new file mode 100755 index 000000000..c5b911d77 --- /dev/null +++ b/src/api/rust/src/protobuf/BasicCounterDataProduct.proto @@ -0,0 +1,25 @@ +/** (C) Copyright 2013, Applied Physical Sciences Corp., A General Dynamics Company + ** + ** Gravity is free software; you can redistribute it and/or modify + ** it under the terms of the GNU Lesser General Public License as published by + ** the Free Software Foundation; either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU Lesser General Public License for more details. + ** + ** You should have received a copy of the GNU Lesser General Public + ** License along with this program; + ** If not, see . + ** + */ + +syntax = "proto2"; +option optimize_for = SPEED; + +message BasicCounterDataProductPB +{ + required int32 count = 1; +} diff --git a/src/api/rust/src/protobuf/BigComplexPB.proto b/src/api/rust/src/protobuf/BigComplexPB.proto new file mode 100644 index 000000000..573343efa --- /dev/null +++ b/src/api/rust/src/protobuf/BigComplexPB.proto @@ -0,0 +1,24 @@ + +syntax = "proto2"; + +message SmallGuyPB { + optional int32 number = 1; + optional string proverb = 2; + repeated int32 values = 3; +} + +message BigGuyPB { + repeated SmallGuyPB littles = 1; + optional int32 bigNumber = 2; + optional string helloworld = 3; + optional bool shouldI = 4; +} + +message BigResultPB { + optional bool didIt = 1; + optional int32 length = 2; + repeated int32 littleNums = 3; + repeated string proverbs = 4; + repeated int32 values = 5; + +} \ No newline at end of file diff --git a/src/api/rust/src/protobuf/DataPB.proto b/src/api/rust/src/protobuf/DataPB.proto new file mode 100644 index 000000000..542ab207c --- /dev/null +++ b/src/api/rust/src/protobuf/DataPB.proto @@ -0,0 +1,32 @@ +/** (C) Copyright 2013, Applied Physical Sciences Corp., A General Dynamics Company + ** + ** Gravity is free software; you can redistribute it and/or modify + ** it under the terms of the GNU Lesser General Public License as published by + ** the Free Software Foundation; either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU Lesser General Public License for more details. + ** + ** You should have received a copy of the GNU Lesser General Public + ** License along with this program; + ** If not, see . + ** + */ + +syntax = "proto2"; +option optimize_for = SPEED; + +message MultPB +{ + optional int32 multiplicand_a = 1; + optional int32 multiplicand_b = 2; +} + +message ResultPB +{ + optional int32 result = 1; +} + diff --git a/src/api/rust/src/spdlog.rs b/src/api/rust/src/spdlog.rs new file mode 100644 index 000000000..c8c65f360 --- /dev/null +++ b/src/api/rust/src/spdlog.rs @@ -0,0 +1,41 @@ +#![allow(dead_code)] +use crate::ffi; + + +use cxx::{let_cxx_string}; + +/// Struct with provided methods used for logging. +pub struct SpdLog {} + +impl SpdLog { + + + pub fn critical(message: impl AsRef<[u8]>) { + let_cxx_string!(m = message); + ffi::spdlog_critical(&m); + } + pub fn error(message: impl AsRef<[u8]>) { + let_cxx_string!(m = message); + ffi::spdlog_error(&m); + } + pub fn warn(message: impl AsRef<[u8]>) { + let_cxx_string!(m = message); + ffi::spdlog_warn(&m); + } + pub fn info(message: impl AsRef<[u8]>) { + let_cxx_string!(m = message); + ffi::spdlog_info(&m); + } + pub fn debug(message: impl AsRef<[u8]>) { + let_cxx_string!(m = message); + ffi::spdlog_debug(&m); + } + pub fn trace(message: impl AsRef<[u8]>) { + let_cxx_string!(m = message); + ffi::spdlog_trace(&m); + } + + +} + + diff --git a/src/api/rust/src/unit_tests/Gravity.ini b/src/api/rust/src/unit_tests/Gravity.ini new file mode 100644 index 000000000..cacfefc0c --- /dev/null +++ b/src/api/rust/src/unit_tests/Gravity.ini @@ -0,0 +1,11 @@ +[general] +ServiceDirectoryURL="tcp://localhost:5555" +NoConfigServer=true +GravityLocalLogLevel=debug +GravityFileLogLevel=debug +#GravityConsoleLogLevel=DEBUG + +[Relay] +DataProductList="BasicCounterDataProduct" +#ProvideLocalOnly=false + diff --git a/src/api/rust/src/unit_tests/data_product_test.rs b/src/api/rust/src/unit_tests/data_product_test.rs new file mode 100644 index 000000000..56f02023d --- /dev/null +++ b/src/api/rust/src/unit_tests/data_product_test.rs @@ -0,0 +1,55 @@ +include!(concat!(env!("OUT_DIR"), "/protobuf/mod.rs")); + +use crate::GravityDataProduct; +use DataPB::*; + + +#[test] +fn data_product_functions() { + let mut gdp = GravityDataProduct::with_id("TestDataProduct"); + + let mut pb = MultPB::new(); + pb.set_multiplicand_a(5); + pb.set_multiplicand_b(9); + + gdp.set_data(&pb); + + assert!(gdp.size() > 0); + assert!(gdp.data_size() > 0); + assert!(gdp.data_product_id() == String::from("TestDataProduct")); + + gdp.set_is_cached_data_product(true); + assert!(gdp.is_cached_data_product()); + gdp.set_is_cached_data_product(false); + assert!(!gdp.is_cached_data_product()); + + gdp.set_is_relayed_data_product(true); + assert!(gdp.is_relayed_data_product()); + gdp.set_is_relayed_data_product(false); + assert!(!gdp.is_relayed_data_product()); + + gdp.set_component_id("newCompId"); + assert_eq!("newCompId", gdp.component_id()); + + gdp.set_timestamp(18); + + gdp.set_software_version("some version"); + assert_eq!("some version".to_string(), gdp.software_version()); + + gdp.set_type_name("multiply"); + assert_eq!("multiply", gdp.type_name()); + + let array = gdp.serialize_to_array(); + let mut gdp2 = GravityDataProduct::with_id("ParseSerializeTest"); + + gdp2.parse_from_array(&array); + assert_eq!(gdp.size(), gdp2.size()); + assert_eq!(gdp.data_size(), gdp2.data_size()); + let mut pb2 = MultPB::new(); + gdp2.populate_message(&mut pb2); + assert_eq!(5, pb2.multiplicand_a()); + assert_eq!(9, pb2.multiplicand_b()); + + + +} \ No newline at end of file diff --git a/src/api/rust/src/unit_tests/drop_tests.rs b/src/api/rust/src/unit_tests/drop_tests.rs new file mode 100644 index 000000000..c158e5844 --- /dev/null +++ b/src/api/rust/src/unit_tests/drop_tests.rs @@ -0,0 +1,76 @@ +include!(concat!(env!("OUT_DIR"), "/protobuf/mod.rs")); + +use core::time; + +use crate::{unit_tests::subscriber, GravityDataProduct, GravityNode, GravitySubscriber, GravityTransportType, SpdLog}; +use BasicCounterDataProduct::*; + +struct MySubscriber {} + +impl GravitySubscriber for MySubscriber { + fn subscription_filled(&mut self, data_products: Vec) { + for i in data_products.iter() { + let mut pb = BasicCounterDataProductPB::new(); + i.populate_message(&mut pb); + // warn!("got {}, {}", pb.multiplicand_a(), pb.multiplicand_b()); + // SpdLog::warn(format!("Count: {}", pb.count())); + } + } +} + +fn mover (x: T) -> T { + x +} + +// #[test] +fn subscriber_drop() { + let handle = std::thread::spawn( || { + let mut gn = GravityNode::new(); + gn.init("DropPublisherComponent"); + + gn.register_data_product("DropDataProduct", GravityTransportType::TCP); + + let mut count = 1; + loop { + + let mut data_product = GravityDataProduct::with_id("DropDataProduct"); + + let mut data = BasicCounterDataProductPB::new(); + data.set_count(count); + + data_product.set_data(&data); + + gn.publish(&data_product); + + count += 1; + std::thread::sleep(time::Duration::from_secs(1)); + } + }); + + let mut gn = GravityNode::new(); + gn.init("DropSubscriberComponent"); + + let subscriber = gn.tokenize_subscriber(MySubscriber {}); + + gn.subscribe("DropDataProduct", &subscriber); + + std::thread::sleep(time::Duration::from_secs(2)); + + let moved = mover(gn); + // SpdLog::warn("Moved Node"); + + std::thread::sleep(time::Duration::from_secs(2)); + + let moved_sub = mover(subscriber); + // SpdLog::warn("Moved Subscriber"); + + std::thread::sleep(time::Duration::from_secs(2)); + + std::mem::drop(moved_sub); + + // SpdLog::warn("Dropped subscriber..."); + + std::thread::sleep(time::Duration::from_secs(2)); + + +} \ No newline at end of file diff --git a/src/api/rust/src/unit_tests/heartbeat.rs b/src/api/rust/src/unit_tests/heartbeat.rs new file mode 100644 index 000000000..09ff7b38b --- /dev/null +++ b/src/api/rust/src/unit_tests/heartbeat.rs @@ -0,0 +1,77 @@ +#![allow(dead_code)] +#![allow(unused_variables)] + + + + +use std::time; +use crate::SpdLog; +use crate::{GravityNode, GravityReturnCode, GravityTransportType}; +use crate::GravityHeartbeatListener; + + + +struct MyListener {} + +impl GravityHeartbeatListener for MyListener { + fn missed_heartbeat(&mut self, component_id: &str, microsecond_to_last_heartbeat: i64, + interval_in_microseconds: &mut i64) { + // GravityLogger::warn(format!("Missed Heartbeat. Last heartbeat {} microseconds ago", microsecond_to_last_heartbeat)); + + } + + fn received_heartbeat(&mut self, component_id: &str, interval_in_microseconds: &mut i64) { + // GravityLogger::warn("Receieved Heartbeat"); + } + + +} +#[test] +fn simple_heartbeat_test () { + + let mut gn = GravityNode::new(); + gn.init("HeartbeatExample"); + + let interval = gn.get_int_param("HeartbeatInterval", 500000) as i64; + let listen_for_heartbeat = gn.get_bool_param("HeartbeatListener", true); + + gn.start_heartbeat(interval); + + let listener = MyListener {}; + + if listen_for_heartbeat { + gn.register_heartbeat_listener("HeartbeatExample", interval, listener); + } + + let mut gn2 = GravityNode::new(); + gn2.init("HeartbeatExample2"); + + let mut quit = false; + let mut count = 0; + while !quit { + std::thread::sleep(time::Duration::from_millis(500)); + count += 1; + + if count == 2 { + gn.stop_heartbeat(); + // print!("stopping") + } + if count == 4 { + gn.start_heartbeat(interval); + } + if count == 6 { + gn.register_heartbeat_listener("HeartbeatExample2", interval, MyListener {}); + } + if count == 8 { + gn.unregister_heartbeat_listener("HeartbeatExample"); + } + if count == 10 { + gn.unregister_heartbeat_listener("HeartbeatExample2"); + } + if count == 12 { + quit = true; + } + } + +// gn.wait_for_exit(); +} diff --git a/src/api/rust/src/unit_tests/misc_func.rs b/src/api/rust/src/unit_tests/misc_func.rs new file mode 100644 index 000000000..402ff4a1a --- /dev/null +++ b/src/api/rust/src/unit_tests/misc_func.rs @@ -0,0 +1,109 @@ +use core::time; + +use crate::{GravityDataProduct, GravityHeartbeatListener, GravityNode, GravityTransportType, GravitySubscriber, SpdLog}; + + + +fn misc_component_1 () -> GravityNode { + let mut gn = GravityNode::new(); + + gn.init("MiscGravityComponentID1"); + + let interval = 500000; + + gn.start_heartbeat(interval); + + gn.register_data_product("IPCDataProduct", GravityTransportType::TCP); + + let mut count = 5; + while count > 0 { + count -= 1; + + + let mut ipc_data_product = GravityDataProduct::with_id("IPCDataProduct"); + + let data = "hey!"; + + ipc_data_product.set_data_basic(data.as_bytes()); + + gn.publish(&ipc_data_product); + + + } + gn +} + + +struct MiscHBListener {} + +impl GravityHeartbeatListener for MiscHBListener { + fn missed_heartbeat(&mut self, component_id: &str, microsecond_to_last_heartbeat: i64, + interval_in_microseconds: &mut i64) { + // SpdLog::warn(format!("Missed Heartbeat. Last one {} microseconds agpo", microsecond_to_last_heartbeat)); + } + + fn received_heartbeat(&mut self, component_id: &str, interval_in_microseconds: &mut i64) { + // SpdLog::warn("Received heartbeat"); + } +} + +struct MiscGravitySubscriber {} + +impl GravitySubscriber for MiscGravitySubscriber { + fn subscription_filled(&mut self, data_products: Vec) { + for data_product in data_products { + let data = data_product.data(); + let message = String::from_utf8(data).unwrap(); + assert_eq!(String::from("hey!"), message); + // SpdLog::warn(format!("Got message {}", message)); + } + } +} + +fn misc_component_2 () -> GravityNode { + let mut gn = GravityNode::new(); + gn.init("MiscGravityComponentID2"); + + let interval = 500000; + + let hb1 = MiscHBListener {}; + + gn.register_heartbeat_listener("MiscGravityComponentID1", interval, hb1); + + + let ipc_subscriber = gn.tokenize_subscriber(MiscGravitySubscriber {}); + + gn.subscribe("IPCDataProduct", &ipc_subscriber); + + gn +} + +#[test] +fn misc_func () { + + // test if the listener drops first (good now) + let listener = misc_component_2(); + // misc_component_2(); + + let beater = misc_component_1(); + // std::mem::drop(beater); + + std::thread::sleep(time::Duration::from_secs(3)); + // std::mem::drop(beater); + // std::mem::drop(listener); + std::thread::sleep(time::Duration::from_secs(3)); + +} + +#[test] +fn listener_drops_second () { + // test if the listener drops second (good) + let mut listener = misc_component_2(); + + let beater = misc_component_1(); + + std::thread::sleep(time::Duration::from_secs(3)); + + listener.stop_heartbeat(); //does nothing, but need an action to keep it around + +} \ No newline at end of file diff --git a/src/api/rust/src/unit_tests/mod.rs b/src/api/rust/src/unit_tests/mod.rs new file mode 100644 index 000000000..325166240 --- /dev/null +++ b/src/api/rust/src/unit_tests/mod.rs @@ -0,0 +1,12 @@ +#![allow(dead_code)] +#![allow(unused_imports)] +#![allow(unused_variables)] + +mod service; +mod subscriber; +mod heartbeat; +// mod relay; +mod misc_func; + +mod data_product_test; +mod drop_tests; \ No newline at end of file diff --git a/src/api/rust/src/unit_tests/relay.rs b/src/api/rust/src/unit_tests/relay.rs new file mode 100644 index 000000000..809514115 --- /dev/null +++ b/src/api/rust/src/unit_tests/relay.rs @@ -0,0 +1,86 @@ +include!(concat!(env!("OUT_DIR"), "/protobuf/mod.rs")); + +use std::{process::Command, time}; + +use spdlog::{critical, error, warn}; + +use crate::{gravity_data_product::GravityDataProduct, gravity_node::{GravityNode, GravityReturnCode, GravityTransportType}, gravity_subscriber::GravitySubscriber, protos::BasicCounterDataProduct::BasicCounterDataProductPB}; + +struct SimpleCounterSubscriber {} + +impl GravitySubscriber for SimpleCounterSubscriber { + fn subscription_filled(&self, data_products: &Vec) { + for data_product in data_products { + let mut counter_data_pb = BasicCounterDataProductPB::new(); + data_product.populate_message(&mut counter_data_pb); + + assert!(data_product.is_relayed_data_product(), "Not relayed :/ Did you forget to start the Relay process???"); + // warn!("Current Count: {}. Message was relayed: {}", counter_data_pb.count(), + // if data_product.is_relayed_data_product() { "true" } else {"false"}) + } + } +} +fn publish() { + let data_product_id = "BasicCounterDataProduct"; + let gn = GravityNode::new(); + let mut ret = gn.init("RustExample"); + + if ret != GravityReturnCode::SUCCESS { + critical!("Unable to initialize GravityNode (return code {:?})", ret); + std::process::exit(1); + } + + ret = gn.register_data_product(&data_product_id, GravityTransportType::TCP); + if ret != GravityReturnCode::SUCCESS { + critical!("Unable to register data product (return code {:?})", ret); + std::process::exit(1) + } + + + + std::thread::sleep(time::Duration::from_secs(1)); + + let mut quit = false; + let mut count = 1; + while !quit + { + let gdp = GravityDataProduct::from_id(&data_product_id); + + let mut data = BasicCounterDataProductPB::new(); + data.set_count(count); + + // //TODO, but that should be all + gdp.set_data(&data); + + + ret = gn.publish(&gdp); + if ret != GravityReturnCode::SUCCESS { + error!("Could not publish data product (return code {:?})", ret); + std::process::exit(1) + } + // if count == 9 { gnn.unsubscribe(data_product_id, &sub); + // std::thread::sleep(time::Duration::from_millis(300));} + + if count == 20 { quit = true;} + count += 1; + + std::thread::sleep(time::Duration::from_millis(1000)); + } + +} + +#[test] +fn simple_relay() { + + // This is a horrible test since you have to manually start the relay + // + let mut gn = GravityNode::new(); + gn.init("Subscriber"); + + let counter_subscriber = SimpleCounterSubscriber {}; + + gn.subscribe("BasicCounterDataProduct", &counter_subscriber); + + publish(); + +} \ No newline at end of file diff --git a/src/api/rust/src/unit_tests/service.rs b/src/api/rust/src/unit_tests/service.rs new file mode 100644 index 000000000..914f6170a --- /dev/null +++ b/src/api/rust/src/unit_tests/service.rs @@ -0,0 +1,296 @@ + +include!(concat!(env!("OUT_DIR"), "/protobuf/mod.rs")); + + +use core::time; +use crate::ffi::new_future_response; +use crate::SpdLog; +use crate::GravityServiceProvider; +use crate::GravityDataProduct; +use crate::GravityRequestor; +use crate::{GravityNode, GravityReturnCode, GravityTransportType}; +// use gravity::gravity_logger::SpdLog; +use DataPB::{MultPB, ResultPB}; +use BigComplexPB::{BigGuyPB, BigResultPB, SmallGuyPB}; + +struct MyProvider {} + +impl GravityServiceProvider for MyProvider { + fn request(&mut self, service_id: &str, data_product: &GravityDataProduct) -> GravityDataProduct { + if data_product.data_product_id() != "Multiplication" { + // SpdLog::error("Request is not for multiplication"); + } + let mut mult_ops = MultPB::new(); + data_product.populate_message(&mut mult_ops); + + // SpdLog::warn(format!("Request recieved {} x {}", mult_ops.multiplicand_a.unwrap(), mult_ops.multiplicand_b.unwrap())); + + + let result = mult_ops.multiplicand_a.unwrap() * mult_ops.multiplicand_b.unwrap(); + + let mut result_pb = ResultPB::new(); + result_pb.set_result(result); + + let mut ret = GravityDataProduct::with_id("MultiplicationResult"); + ret.set_data(&result_pb); + ret + } +} +struct MyRequestor {} + +impl GravityRequestor for MyRequestor { + fn request_filled(&mut self, _service_id: &str, request_id: &str, response: &GravityDataProduct) { + let mut result_pb = ResultPB::new(); + response.populate_message(&mut result_pb); + + assert_eq!(result_pb.result(), 16); + + } + + fn request_timeout(&mut self, service_id: &str, request_id: &str) { + // assert!(true); + } +} + +#[test] +fn service() { + + let mut gn = GravityNode::new(); + + gn.init("MultiplicationComponent"); + + + let msp = MyProvider {}; + gn.register_service("Multiplication", + GravityTransportType::TCP, msp); + + let mut gn2 = GravityNode::new(); + + let mut ret = gn2.init("MultiplicationRequestor"); + + while ret != GravityReturnCode::SUCCESS { + // SpdLog::warn("Unable to init component, retrying..."); + ret = gn2.init("MultiplicationRequestor"); + } + + let requestor = gn2.tokenize_requestor(MyRequestor {}); + + let mut mult_request = GravityDataProduct::with_id("Multiplication"); + let mut operands = MultPB::new(); + + operands.set_multiplicand_a(8); + operands.set_multiplicand_b(2); + mult_request.set_data(&operands); + + ret = gn2.request_async("Multiplication", &mult_request, &requestor); + while ret != GravityReturnCode::SUCCESS { + // SpdLog::warn("request to Multiplication service failed, retrying..."); + std::thread::sleep(time::Duration::from_secs(1)); + + ret = gn2.request_async("Multiplication", &mult_request, &requestor); + } + + let mut request2 = GravityDataProduct::with_id("Multiplication"); + let mut operands2 = MultPB::new(); + operands2.set_multiplicand_a(5); + operands2.set_multiplicand_b(7); + + + request2.set_data(&operands2); + let mult_sync = gn2.request_sync("Multiplication", &request2); + + match mult_sync { + None => panic!(), + Some(gdp) => { + let mut result2 = ResultPB::new(); + gdp.populate_message(&mut result2); + assert_eq!(35, result2.result()); + // SpdLog::warn(format!("Synchronous response recieved: 5 x 7 = {}", result2.result())); + } + } +} +struct BetterProvider {} + +impl GravityServiceProvider for BetterProvider { + fn request(&mut self, service_id: &str, data_product: &GravityDataProduct) -> GravityDataProduct { + let mut bigops = BigGuyPB::new(); + data_product.populate_message(&mut bigops); + // SpdLog::warn(format!("Request recieved for Big Complex")); + + + let mut bigresult = BigResultPB::new(); + bigresult.set_didIt(bigops.shouldI()); + bigresult.set_length(bigops.littles.len() as i32); + assert!(bigops.has_helloworld()); + assert_eq!("HelloWorld", bigops.helloworld()); + assert!(bigops.has_bigNumber()); + assert_eq!(1785, bigops.bigNumber()); + + for small in bigops.littles.iter() { + bigresult.littleNums.push(small.number()); + bigresult.proverbs.push(small.proverb().to_string()); + for val in small.values.iter() { + bigresult.values.push(*val); + } + } + + let mut result_gdp = GravityDataProduct::with_id("BigComplex"); + result_gdp.set_data(&bigresult); + + result_gdp + + } +} + +struct BetterRequestor {} + +impl GravityRequestor for BetterRequestor { + fn request_filled(&mut self, service_id: &str, request_id: &str, response: &GravityDataProduct) { + let mut bigresult = BigResultPB::new(); + response.populate_message(&mut bigresult); + + assert!(bigresult.didIt()); + assert_eq!(3, bigresult.length()); + let mut count = 0; + for num in bigresult.littleNums.iter() { + assert_eq!(count, *num); + count += 1; + } + count = 0; + for val in bigresult.values.iter() { + assert_eq!(count, *val); + count += 2; + } + assert_eq!(3, bigresult.proverbs.len()); + for (index, proverb) in bigresult.proverbs.iter().enumerate() { + + match index { + 0 => assert_eq!("I love unit tests", proverb), + 1 => assert_eq!("I hate unit tests", proverb), + 2 => assert_eq!("What's a unit test?", proverb), + _ => panic!(), + } + + } + } + + fn request_timeout(&mut self, service_id: &str, request_id: &str) { + assert!(true) + } +} + +#[test] +fn service2 () { + let mut gn = GravityNode::new(); + gn.init("BigComplexComponentq"); + + let msp = BetterProvider {}; + gn.register_service("BigComplex", + GravityTransportType::TCP, msp); + + let mut gn2 = GravityNode::new(); + + let mut ret = gn2.init("BigComplexRequestor"); + + gn.register_service("BigComplex", + GravityTransportType::TCP, BetterProvider {}); + + + while ret != GravityReturnCode::SUCCESS { + // SpdLog::warn("Unable to init component, retrying..."); + ret = gn2.init("BigComplexRequestor"); + } + + let requestor = gn2.tokenize_requestor(BetterRequestor {}); + + let mut mult_request = GravityDataProduct::with_id("BigComplex"); + let mut operands = BigGuyPB::new(); + + operands.set_bigNumber(1785); + operands.set_helloworld("HelloWorld".to_string()); + operands.set_shouldI(true); + for _ in 0..3 { + let to_add = SmallGuyPB::new(); + operands.littles.push(to_add); + } + + let mut count = 0; + let list = ["I love unit tests", "I hate unit tests", "What's a unit test?"]; + for (index , small) in operands.littles.iter_mut().enumerate() { + small.set_number(index as i32); + small.set_proverb(list.get(index).unwrap().to_string()); + for _ in 0..3 { + small.values.push(count); + count += 2; + } + + } + mult_request.set_data(&operands); + + ret = gn2.request_async("BigComplex", &mult_request, &requestor); + while ret != GravityReturnCode::SUCCESS { + // SpdLog::warn("request to Multiplication service failed, retrying..."); + std::thread::sleep(time::Duration::from_secs(1)); + + ret = gn2.request_async("BigComplex", &mult_request, &requestor); + } + + let mut request2 = GravityDataProduct::with_id("BigComplex"); + let mut operands2 = BigGuyPB::new(); + operands2.set_bigNumber(1785); + operands2.set_helloworld("HelloWorld".to_string()); + operands2.set_shouldI(true); + for _ in 0..3 { + let to_add = SmallGuyPB::new(); + operands2.littles.push(to_add); + } + + let mut count = 0; + let list = ["I love unit tests", "I hate unit tests", "What's a unit test?"]; + for (index , small) in operands2.littles.iter_mut().enumerate() { + small.set_number(index as i32); + small.set_proverb(list.get(index).unwrap().to_string()); + for _ in 0..3 { + small.values.push(count); + count += 2; + } + + } + request2.set_data(&operands2); + let mult_sync = gn2.request_sync("BigComplex", &request2); + + match mult_sync { + None => panic!(), + Some(gdp) => { + let mut result2 = BigResultPB::new(); + gdp.populate_message(&mut result2); + assert_eq!(true, result2.didIt()); + assert_eq!(3, result2.length()); + let mut count = 0; + for num in result2.littleNums.iter() { + assert_eq!(count, *num); + count += 1; + } + count = 0; + for val in result2.values.iter() { + assert_eq!(count, *val); + count += 2; + } + assert_eq!(3, result2.proverbs.len()); + for (index, proverb) in result2.proverbs.iter().enumerate() { + + match index { + 0 => assert_eq!("I love unit tests", proverb), + 1 => assert_eq!("I hate unit tests", proverb), + 2 => assert_eq!("What's a unit test?", proverb), + _ => panic!(), + } + + } + } + } + + std::thread::sleep(time::Duration::from_secs(1)); + + +} \ No newline at end of file diff --git a/src/api/rust/src/unit_tests/subscriber.rs b/src/api/rust/src/unit_tests/subscriber.rs new file mode 100644 index 000000000..4a6fad831 --- /dev/null +++ b/src/api/rust/src/unit_tests/subscriber.rs @@ -0,0 +1,375 @@ +#![allow(dead_code)] +#![allow(unused_variables)] + +include!(concat!(env!("OUT_DIR"), "/protobuf/mod.rs")); + +use std::rc::Rc; +use std::sync::Arc; +use std::time; +use crate::gravity_node::SubscriberToken; +use crate::unit_tests::subscriber; +use crate::GravityNode; +use crate::GravityDataProduct; +use crate::GravityReturnCode; +use crate::GravityTransportType; +use crate::GravitySubscriber; +use BasicCounterDataProduct::BasicCounterDataProductPB; +use crate::SpdLog; + + +use DataPB::*; + +struct MySubscriber {} + +impl GravitySubscriber for MySubscriber { + fn subscription_filled(&mut self, data_products: Vec) { + // SpdLog::warn("filled subscription"); + for i in data_products.iter() { + let mut pb = MultPB::new(); + i.populate_message(&mut pb); + // SpdLog::warn(format!("got {}, {}", pb.multiplicand_a(), pb.multiplicand_b())); + assert!(pb.multiplicand_b() == (pb.multiplicand_a() + 1024)); + } + } +} + +#[test] +fn basic_subscriber () { + + let mut gnn = GravityNode::new(); + gnn.init("SubNode"); + let data_product_id = "RustDataProduct"; + + let s = MySubscriber {}; + let subscriber = gnn.tokenize_subscriber(s); + + gnn.subscribe(data_product_id, &subscriber); + + + let mut gn = GravityNode::new(); + let mut ret = gn.init("RustExample"); + let fs = gn.get_int_param("Fs", 0); + + if ret != GravityReturnCode::SUCCESS { + // SpdLog::critical("Unable to initialize GravityNode (return code {:?})", ret); + std::process::exit(1); + } + // SpdLog::info("Gravity returned code SUCCESS. Init successful"); + + + + ret = gn.register_data_product(&data_product_id, GravityTransportType::TCP); + if ret != GravityReturnCode::SUCCESS { + // SpdLog::critical(format!("Unable to register data product (return code {:?})", ret)); + std::process::exit(1) + } + + + + std::thread::sleep(time::Duration::from_secs(1)); + + let mut quit = false; + let mut count = 1; + while !quit + { + let mut gdp = GravityDataProduct::with_id(&data_product_id); + + let mut data = MultPB::new(); + data.set_multiplicand_a(count); + data.set_multiplicand_b(count + 1024); + + // //TODO, but that should be all + gdp.set_data(&data); + + + ret = gn.publish(&gdp); + if ret != GravityReturnCode::SUCCESS { + // error("Could not publish data product (return code {:?})", ret); + std::process::exit(1) + } + + if count == 9 { gnn.unsubscribe(data_product_id, &subscriber); } + + if count == 18 { quit = true;} + count += 1; + + std::thread::sleep(time::Duration::from_millis(1000)); + } + + + // gn.wait_for_exit(); + // gnn.wait_for_exit(); + + + +} + + +struct CounterSubscriber { + count_totals: i32, +} + +impl GravitySubscriber for CounterSubscriber { + fn subscription_filled(&mut self, data_products: Vec) { + // warn!("Subscriber 1 got message"); + for i in data_products.iter() { + let mut counter_data_pb = BasicCounterDataProductPB::new(); + i.populate_message(&mut counter_data_pb); + // SpdLog::warn(format!("cuurent count = {}", self.count_totals)); + // SpdLog::warn(format!("Adding {}", counter_data_pb.count())); + self.count_totals += counter_data_pb.count(); + + // SpdLog::warn(format!("Subscriber 1: Sum of All Counts Receieved: {}", self.count_totals)) + + } + + } +} + +struct HelloSubscriber {} + + +impl GravitySubscriber for HelloSubscriber { + fn subscription_filled(&mut self, data_products: Vec) { + for i in data_products.iter() { + let message = String::from_utf8(i.data()).unwrap(); + // SpdLog::warn(format!("Subscriber 2: Got message: {}", message)); + + + } + } +} + +struct SimpleSubscriber {} + +impl GravitySubscriber for SimpleSubscriber { + fn subscription_filled(&mut self, data_products: Vec) { + for i in data_products.iter() { + // SpdLog::warn(format!("Subscriber 3 got a {} data product", i.data_product_id())); + + if i.data_product_id() == "BasicCounterDataProduct" { + let mut counter_data_pb = BasicCounterDataProductPB::new(); + i.populate_message(&mut counter_data_pb); + // SpdLog::warn(format!("Subscriber3 : Current Count: {}", counter_data_pb.count())); + + } else if i.data_product_id() == "HelloWorldDataProduct" { + let message = String::from_utf8(i.data()).unwrap(); + // SpdLog::warn(format!("Subscriber 3: Got message: {}", message)); + } + } + } +} +#[test] +fn multiple_subscribers () { + + let mut gn = GravityNode::new(); + gn.init("SimpleGravityComponentSub"); + + // subscribe counter + let counter = gn.tokenize_subscriber(CounterSubscriber { count_totals: 0} ); + gn.subscribe("BasicCounterDataProduct", &counter); + + // subscribe message + let hello = gn.tokenize_subscriber(HelloSubscriber {}); + gn.subscribe("HelloWorldDataProduct", &hello); + + // // subscribe to both + // let simple = gn.tokenize_subscriber(SimpleSubscriber {}); + // gn.subscribe("BasicCounterDataProduct", &simple); + // gn.subscribe("HelloWorldDataProduct", &simple); + + let mut gnpub = GravityNode::new(); + gnpub.init("SimpleGravityComponentPub"); + + gnpub.register_data_product("BasicCounterDataProduct", GravityTransportType::TCP); + gnpub.register_data_product("HelloWorldDataProduct", GravityTransportType::TCP); + + let mut quit = false; + let mut count = 1; + + while !quit + { + let mut counter_data_product = GravityDataProduct::with_id("BasicCounterDataProduct"); + let mut counter_data_pb = BasicCounterDataProductPB::new(); + counter_data_pb.set_count(count); + + counter_data_product.set_data(&counter_data_pb); + + gnpub.publish(&counter_data_product); + + count += 1; + if count > 4 { quit = true}; + + + let mut hello_data_product = GravityDataProduct::with_id("HelloWorldDataProduct"); + let data = "Hello World"; + hello_data_product.set_data_basic(data.as_bytes()); + + gnpub.publish(&hello_data_product); + + // warn!("Published message 1 with count {} and message 2 with data {}", count, data); + + std::thread::sleep(time::Duration::from_secs(1)); + + } + + + +} + +fn external_subscribe(gn: &mut GravityNode, subscriber: SubscriberToken) -> SubscriberToken { + gn.subscribe("SimpleRustDataProduct", &subscriber); + subscriber +} + + +#[test] +fn outside_function () { + + let mut gnn = GravityNode::new(); + gnn.init("SimpleGravityComponent"); + + let mut sub = gnn.tokenize_subscriber(MySubscriber {}); + + sub = external_subscribe(&mut gnn, sub); + gnn.subscribe("SimpleRustDataProduct", &sub); + + //testing a dropped subscriber struct. Still works + { + let sub = gnn.tokenize_subscriber(MySubscriber {}); + gnn.subscribe("SimpleRustDataProduct", &sub); + } + + let data_product_id = "SimpleRustDataProduct"; + let mut gn = GravityNode::new(); + let mut ret = gn.init("RustExample"); + let fs = gn.get_int_param("Fs", 0); + + if ret != GravityReturnCode::SUCCESS { + // critical!("Unable to initialize GravityNode (return code {:?})", ret); + std::process::exit(1); + } + // info!("Gravity returned code SUCCESS. Init successful"); + + + + ret = gn.register_data_product(&data_product_id, GravityTransportType::TCP); + if ret != GravityReturnCode::SUCCESS { + // critical!("Unable to register data product (return code {:?})", ret); + std::process::exit(1) + } + + + + std::thread::sleep(time::Duration::from_secs(1)); + + let mut quit = false; + let mut count = 1; + while !quit + { + let mut gdp = GravityDataProduct::with_id(&data_product_id); + + let mut data = MultPB::new(); + data.set_multiplicand_a(count); + data.set_multiplicand_b(count + 1024); + + // //TODO, but that should be all + gdp.set_data(&data); + + + ret = gn.publish(&gdp); + if ret != GravityReturnCode::SUCCESS { + // error!("Could not publish data product (return code {:?})", ret); + std::process::exit(1) + } + // if count == 9 { gnn.unsubscribe(data_product_id, &sub); + // std::thread::sleep(time::Duration::from_millis(300));} + + if count == 15 { quit = true;} + count += 1; + + std::thread::sleep(time::Duration::from_millis(100)); + } + + // gn.wait_for_exit(); +} + + +struct MyDropSubscriber {} + +impl GravitySubscriber for MyDropSubscriber { + fn subscription_filled(&mut self, data_products: Vec) { + for i in data_products.iter() { + let mut pb = MultPB::new(); + i.populate_message(&mut pb); + // warn!("got {}, {}", pb.multiplicand_a(), pb.multiplicand_b()); + panic!(); //shhould never get here! + // assert!(pb.multiplicand_b() == (pb.multiplicand_a() + 1024)); + } + } +} +#[test] +fn dropped_node() { + let mut gnn = GravityNode::new(); + gnn.init("SimpleGravityComponent"); + + let sub = gnn.tokenize_subscriber( MyDropSubscriber {} ); + + gnn.subscribe("SimpleRustDataProduct", &sub); + { + let to_drop = gnn; + } + + + let data_product_id = "SimpleRustDataProduct"; + let mut gn = GravityNode::new(); + let mut ret = gn.init("RustExample"); + let fs = gn.get_int_param("Fs", 0); + + if ret != GravityReturnCode::SUCCESS { + // critical!("Unable to initialize GravityNode (return code {:?})", ret); + std::process::exit(1); + } + // info!("Gravity returned code SUCCESS. Init successful"); + + + + ret = gn.register_data_product(&data_product_id, GravityTransportType::TCP); + if ret != GravityReturnCode::SUCCESS { + // critical!("Unable to register data product (return code {:?})", ret); + std::process::exit(1) + } + + + + std::thread::sleep(time::Duration::from_secs(1)); + + let mut quit = false; + let mut count = 1; + while !quit + { + let mut gdp = GravityDataProduct::with_id(&data_product_id); + + let mut data = MultPB::new(); + data.set_multiplicand_a(count); + data.set_multiplicand_b(count + 1024); + + // //TODO, but that should be all + gdp.set_data(&data); + + + ret = gn.publish(&gdp); + if ret != GravityReturnCode::SUCCESS { + // error!("Could not publish data product (return code {:?})", ret); + std::process::exit(1) + } + // if count == 9 { gnn.unsubscribe(data_product_id, &sub); + // std::thread::sleep(time::Duration::from_millis(300));} + + if count == 15 { quit = true;} + count += 1; + + std::thread::sleep(time::Duration::from_millis(100)); + } + +} \ No newline at end of file diff --git a/src/keyvalue_parser/CMakeLists.txt b/src/keyvalue_parser/CMakeLists.txt index 3ec1c2e0f..e02483f75 100644 --- a/src/keyvalue_parser/CMakeLists.txt +++ b/src/keyvalue_parser/CMakeLists.txt @@ -30,8 +30,8 @@ if (NOT Yacc_CMD) message(FATAL_ERROR "Could not locate yacc cmd") endif() -set(SRCS keyvalue_parser.h keyvalue_parser.c params.h "${CMAKE_CURRENT_LIST_DIR}/lex.yy.c" "${CMAKE_CURRENT_LIST_DIR}/y.tab.c") -set_source_files_properties("${CMAKE_CURRENT_LIST_DIR}/lex.yy.c" "${CMAKE_CURRENT_LIST_DIR}/y.tab.c" PROPERTIES GENERATED ON) +set(SRCS keyvalue_parser.h keyvalue_parser.c params.h "${CMAKE_CURRENT_BINARY_DIR}/lex.yy.c" "${CMAKE_CURRENT_BINARY_DIR}/y.tab.c") +set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/lex.yy.c" "${CMAKE_CURRENT_BINARY_DIR}/y.tab.c" PROPERTIES GENERATED ON) add_library(${LIB_NAME} STATIC ${SRCS}) target_compile_definitions(${LIB_NAME} PUBLIC "-DLIBKEYVALUE_STATIC") set_target_properties(${LIB_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) @@ -39,10 +39,10 @@ set_target_properties(${LIB_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) gravity_add_dependency(${LIB_NAME}) if (WIN32) - add_custom_command(OUTPUT "${CMAKE_CURRENT_LIST_DIR}/lex.yy.c" COMMAND ${Lex_CMD} -o "${CMAKE_CURRENT_LIST_DIR}/lex.yy.c" "${CMAKE_CURRENT_LIST_DIR}/keyvalue.l") - add_custom_command(OUTPUT "${CMAKE_CURRENT_LIST_DIR}/y.tab.c" COMMAND ${Yacc_CMD} -dt "${CMAKE_CURRENT_LIST_DIR}/keyvalue.y" -o "${CMAKE_CURRENT_LIST_DIR}/y.tab.c") + add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/lex.yy.c" COMMAND ${Lex_CMD} -o "${CMAKE_CURRENT_BINARY_DIR}/lex.yy.c" "${CMAKE_CURRENT_LIST_DIR}/keyvalue.l") + add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/y.tab.c" COMMAND ${Yacc_CMD} -dt "${CMAKE_CURRENT_LIST_DIR}/keyvalue.y" -o "${CMAKE_CURRENT_BINARY_DIR}/y.tab.c") else() - add_custom_target(RunLexYacc ALL ${Lex_CMD} -o "${CMAKE_CURRENT_LIST_DIR}/lex.yy.c" "${CMAKE_CURRENT_LIST_DIR}/keyvalue.l" COMMAND ${Yacc_CMD} -dt "${CMAKE_CURRENT_LIST_DIR}/keyvalue.y" -o "${CMAKE_CURRENT_LIST_DIR}/y.tab.c" DEPENDS "${CMAKE_CURRENT_LIST_DIR}/keyvalue.l" "${CMAKE_CURRENT_LIST_DIR}/keyvalue.y" BYPRODUCTS "${CMAKE_CURRENT_LIST_DIR}/y.tab.c" "${CMAKE_CURRENT_LIST_DIR}/lex.yy.c") + add_custom_target(RunLexYacc ALL ${Lex_CMD} -o "${CMAKE_CURRENT_BINARY_DIR}/lex.yy.c" "${CMAKE_CURRENT_LIST_DIR}/keyvalue.l" COMMAND ${Yacc_CMD} -dt "${CMAKE_CURRENT_LIST_DIR}/keyvalue.y" -o "${CMAKE_CURRENT_BINARY_DIR}/y.tab.c" DEPENDS "${CMAKE_CURRENT_LIST_DIR}/keyvalue.l" "${CMAKE_CURRENT_LIST_DIR}/keyvalue.y" BYPRODUCTS "${CMAKE_CURRENT_BINARY_DIR}/y.tab.c" "${CMAKE_CURRENT_BINARY_DIR}/lex.yy.c") add_dependencies(${LIB_NAME} RunLexYacc) endif() diff --git a/test/examples/15-RustBasicDataProduct/.gitignore b/test/examples/15-RustBasicDataProduct/.gitignore new file mode 100644 index 000000000..447098846 --- /dev/null +++ b/test/examples/15-RustBasicDataProduct/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock \ No newline at end of file diff --git a/test/examples/15-RustBasicDataProduct/Cargo.toml b/test/examples/15-RustBasicDataProduct/Cargo.toml new file mode 100644 index 000000000..f6afddb87 --- /dev/null +++ b/test/examples/15-RustBasicDataProduct/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rust_basic_data_product" +version = "0.1.0" +edition = "2024" + +[build-dependencies] +protobuf-codegen = "3.7.2" +protoc-bin-vendored = "3.2.0" + +[dependencies] +gravity = { git = "https://github.com/astrauc/gravity.git" } +protobuf = "3.7.2" diff --git a/test/examples/15-RustBasicDataProduct/Gravity.ini b/test/examples/15-RustBasicDataProduct/Gravity.ini new file mode 100644 index 000000000..739757191 --- /dev/null +++ b/test/examples/15-RustBasicDataProduct/Gravity.ini @@ -0,0 +1,2 @@ +[general] +test_number = 1234 \ No newline at end of file diff --git a/test/examples/15-RustBasicDataProduct/build.rs b/test/examples/15-RustBasicDataProduct/build.rs new file mode 100644 index 000000000..6f5a1ee68 --- /dev/null +++ b/test/examples/15-RustBasicDataProduct/build.rs @@ -0,0 +1,25 @@ +use std::path::Path; + +// build script to auto generate protobufs +fn main() { + + let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + let root = Path::new(&dir); + let path = root.parent().unwrap(); + let mut path = path.to_str().unwrap().to_string(); + path.push_str("/protobuf"); + let mut file = path.clone(); + file.push_str("/BasicCounterDataProduct.proto"); + + protobuf_codegen::Codegen::new() + // .protoc_path(&protoc_bin_vendored::protoc_bin_path().unwrap()) + // .protoc() + .pure() + .include(path) + .input(file) + .cargo_out_dir("protobuf") + .run_from_script(); + + +} \ No newline at end of file diff --git a/test/examples/15-RustBasicDataProduct/src/main.rs b/test/examples/15-RustBasicDataProduct/src/main.rs new file mode 100644 index 000000000..bfc4d7cfd --- /dev/null +++ b/test/examples/15-RustBasicDataProduct/src/main.rs @@ -0,0 +1,52 @@ +include!(concat!(env!("OUT_DIR"), "/protobuf/mod.rs")); + +use core::time; + +use gravity::{GravityReturnCode, GravityDataProduct, GravityNode, GravitySubscriber, GravityTransportType, SpdLog}; +use BasicCounterDataProduct::*; + + +struct SimpleGravityCounterSubscriber {} + +impl GravitySubscriber for SimpleGravityCounterSubscriber { + fn subscription_filled(&mut self, data_products: Vec) { + + for data_product in data_products.iter() { + let mut counter_data_pb = BasicCounterDataProductPB::new(); + data_product.populate_message(&mut counter_data_pb); + + SpdLog::warn(format!("Current Count: {}", counter_data_pb.count())); + + } + } +} +fn main() { + // SpdLog::warn("Starting..."); + let mut gn = GravityNode::new(); + while gn.init("BasicCounterSubscriberID") != GravityReturnCode::SUCCESS { + SpdLog::warn("retrying init"); + } + assert_eq!(1234, gn.get_int_param("test_number", 0)); + + gn.register_data_product("BasicCounterDataProduct", GravityTransportType::TCP); + let counter_subscriber = gn.tokenize_subscriber(SimpleGravityCounterSubscriber {}); + + gn.subscribe("BasicCounterDataProduct", &counter_subscriber); + + + //setup the publisher + let mut count = 1; + while count <= 20 { + let mut counter_pb = BasicCounterDataProductPB::new(); + counter_pb.set_count(count); + + let mut gdp = GravityDataProduct::with_id("BasicCounterDataProduct"); + gdp.set_data(&counter_pb); + gn.publish(&gdp); + + count += 1; + + std::thread::sleep(time::Duration::from_millis(1000)); + } + +} diff --git a/test/examples/16-RustBasicService/.gitignore b/test/examples/16-RustBasicService/.gitignore new file mode 100644 index 000000000..447098846 --- /dev/null +++ b/test/examples/16-RustBasicService/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock \ No newline at end of file diff --git a/test/examples/16-RustBasicService/Cargo.toml b/test/examples/16-RustBasicService/Cargo.toml new file mode 100644 index 000000000..164fd29e9 --- /dev/null +++ b/test/examples/16-RustBasicService/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rust_basic_service" +version = "0.1.0" +edition = "2024" + +[build-dependencies] +protobuf-codegen = "3.7.2" +protoc-bin-vendored = "3.2.0" + +[dependencies] +gravity = { git = "https://github.com/astrauc/gravity.git" } +protobuf = "3.7.2" diff --git a/test/examples/16-RustBasicService/build.rs b/test/examples/16-RustBasicService/build.rs new file mode 100644 index 000000000..d969ec7f5 --- /dev/null +++ b/test/examples/16-RustBasicService/build.rs @@ -0,0 +1,25 @@ +use std::path::Path; + +// build script to auto generate the protobufs +fn main() { + + let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + let root = Path::new(&dir); + let path = root.parent().unwrap(); + let mut path = path.to_str().unwrap().to_string(); + path.push_str("/protobuf"); + let mut file = path.clone(); + file.push_str("/Multiplication.proto"); + + protobuf_codegen::Codegen::new() + // .protoc_path(&protoc_bin_vendored::protoc_bin_path().unwrap()) + // .protoc() + .pure() + .include(path) + .input(file) + .cargo_out_dir("protobuf") + .run_from_script(); + + +} \ No newline at end of file diff --git a/test/examples/16-RustBasicService/src/main.rs b/test/examples/16-RustBasicService/src/main.rs new file mode 100644 index 000000000..1359672e0 --- /dev/null +++ b/test/examples/16-RustBasicService/src/main.rs @@ -0,0 +1,131 @@ +include!(concat!(env!("OUT_DIR"), "/protobuf/mod.rs")); + +use core::time; +use std::sync::Mutex; + +use gravity::{GravityDataProduct, GravityNode, GravityRequestor, GravityReturnCode, GravityServiceProvider, GravityTransportType, SpdLog}; +use Multiplication::{MultiplicationOperandsPB, MultiplicationResultPB}; + +static GOT_ASYNC: Mutex = Mutex::new(false); +struct MultiplicationServiceProvider {} + +impl GravityServiceProvider for MultiplicationServiceProvider { + fn request(&mut self, _service_id: &str, data_product: &GravityDataProduct) -> GravityDataProduct { + + if data_product.data_product_id() != "Multiplication" { + SpdLog::error("Request is not for multiplication!"); + return GravityDataProduct::with_id("BadRequest"); + } + + // Get the params for this result + let mut params = MultiplicationOperandsPB::new(); + data_product.populate_message(&mut params); + + SpdLog::warn(format!( + "Request Received: {} x {}", + params.multiplicand_a(), + params.multiplicand_b())); + + // Do the calculation + let result = params.multiplicand_a() * params.multiplicand_b(); + + // Return the results to the requestor + let mut result_pb = MultiplicationResultPB::new(); + result_pb.set_result(result); + + let mut result_gdp = GravityDataProduct::with_id("MultiplicationResult"); + result_gdp.set_data(&result_pb); + + result_gdp + } +} + +struct MultiplicationRequestor {} + +impl GravityRequestor for MultiplicationRequestor { + fn request_filled(&mut self, _service_id: &str, request_id: &str, response: &GravityDataProduct) { + // Parse the message into a protobuf + let mut result = MultiplicationResultPB::new(); + response.populate_message(&mut result); + + // Write the answer + SpdLog::warn(format!( + "Asynchronous response received: {} = {}", request_id, result.result() + )); + + let mut data = GOT_ASYNC.lock().expect("Something already has this"); + *data = true; + } + + fn request_timeout(&mut self, _: &str, _: &str) {} +} + +fn main() { + let mut service_gn = GravityNode::new(); + service_gn.init("MultiplicationComponent"); + + let msp = MultiplicationServiceProvider {}; + service_gn.register_service( + // Identifies the service to the service directory so others can make a request to it + "Multiplication", + // Almost always going to be TCP + GravityTransportType::TCP, + // Give the service provider to register the service. + msp); + + // Setup the requests + let mut gn = GravityNode::new(); + gn.init("MultiplicationRequestor"); + + // Set up the first multiplication request + let requestor = gn.tokenize_requestor(MultiplicationRequestor {}); + let mut mult_request1 = GravityDataProduct::with_id("Multiplication"); + let mut params1 = MultiplicationOperandsPB::new(); + params1.set_multiplicand_a(8); + params1.set_multiplicand_b(2); + mult_request1.set_data(¶ms1); + + while gn.request_async_with_request_id( + "Multiplication", // Service name + &mult_request1, // Request + &requestor, // token representing the object with the callback + "8 x 2" // string identifying which request this is + ) != GravityReturnCode::SUCCESS { + SpdLog::warn("request to Multiplication servicec failed, retrying..."); + std::thread::sleep(time::Duration::from_millis(1000)); + } + + // Set up the second multiplication request + let mut mult_request2 = GravityDataProduct::with_id("Multiplication"); + let mut params2 = MultiplicationOperandsPB::new(); + params2.set_multiplicand_a(5); + params2.set_multiplicand_b(7); + mult_request2.set_data(¶ms2); + + // Make a synchronous request for multiplication + let mult_sync = gn.request_sync_with_timeout( + "Multiplication", // Service name + &mult_request2, // Request + 1000); // Timeout in milliseconds + + match mult_sync { + None => SpdLog::error("Request returned None"), + Some( gdp ) => { + let mut result = MultiplicationResultPB::new(); + gdp.populate_message(&mut result); + + SpdLog::warn(format!( + "Synchronous response received: 5 x 7 = {}", result.result() + )); + + } + } + + while { + !*GOT_ASYNC.lock().expect("lock held") + } { + SpdLog::warn("Waiting for asyn request"); + std::thread::sleep(time::Duration::from_millis(1000)); + } + +}