diff --git a/src/WolframLink.cpp b/src/WolframLink.cpp new file mode 100644 index 0000000..cdc6d48 --- /dev/null +++ b/src/WolframLink.cpp @@ -0,0 +1,171 @@ +#include "WolframLink.hpp" + +#include + +// ---------------------------------------------------------------- +// Static member definitions +// ---------------------------------------------------------------- +std::shared_ptr WolframLink::instance_ = nullptr; +std::mutex WolframLink::instance_mutex_; + +// ---------------------------------------------------------------- +// Singleton +// ---------------------------------------------------------------- +std::shared_ptr WolframLink::instance(WSLINK link) { + std::lock_guard lock(instance_mutex_); + if (!instance_) { + if (!link) { + throw std::runtime_error("WolframLink: first call must provide a WSLINK"); + } + instance_ = std::shared_ptr(new WolframLink(link)); + } + return instance_; +} + +void WolframLink::destroy() { + std::lock_guard lock(instance_mutex_); + instance_.reset(); +} + +// ---------------------------------------------------------------- +// Constructor / Destructor +// ---------------------------------------------------------------- +WolframLink::WolframLink(WSLINK link) + : link_(link) + , shutdown_(false) + , worker_([this]() { worker_loop(); }) +{ + spdlog::debug("WolframLink created, worker started"); +} + +WolframLink::~WolframLink() { + { + std::lock_guard lock(queue_mutex_); + shutdown_ = true; + } + queue_cv_.notify_all(); + if (worker_.joinable()) { + worker_.join(); + } + spdlog::debug("WolframLink destroyed, worker joined"); +} + +// ---------------------------------------------------------------- +// Public API +// ---------------------------------------------------------------- +std::future> WolframLink::evaluate(const std::string& expr) { + auto promise = std::make_shared>>(); + auto future = promise->get_future(); + + { + std::lock_guard lock(queue_mutex_); + if (shutdown_) { + promise->set_value(std::nullopt); + return future; + } + queue_.push([this, expr, promise]() { + promise->set_value(evaluate_on_worker(expr)); + }); + } + queue_cv_.notify_one(); + return future; +} + +std::future> WolframLink::evaluate_and_log( + const std::string& label, + const std::string& expr) +{ + auto promise = std::make_shared>>(); + auto future = promise->get_future(); + + { + std::lock_guard lock(queue_mutex_); + if (shutdown_) { + promise->set_value(std::nullopt); + return future; + } + queue_.push([this, label, expr, promise]() { + auto result = evaluate_on_worker(expr); + if (result) { + spdlog::info("[{}]\n expr => {}\n result => {}\n", + label, expr, *result); + } else { + spdlog::error("[{}] Failed to evaluate: {}", label, expr); + } + promise->set_value(result); + }); + } + queue_cv_.notify_one(); + return future; +} + +std::size_t WolframLink::queue_depth() const { + std::lock_guard lock(queue_mutex_); + return queue_.size(); +} + +// ---------------------------------------------------------------- +// Worker thread +// ---------------------------------------------------------------- +void WolframLink::worker_loop() { + while (true) { + std::function job; + { + std::unique_lock lock(queue_mutex_); + queue_cv_.wait(lock, [this]() { + return !queue_.empty() || shutdown_; + }); + + if (shutdown_ && queue_.empty()) { + break; + } + + job = std::move(queue_.front()); + queue_.pop(); + } + + try { + job(); + } catch (const std::exception& e) { + spdlog::error("WolframLink worker caught exception: {}", e.what()); + } catch (...) { + spdlog::error("WolframLink worker caught unknown exception"); + } + } + spdlog::debug("WolframLink worker loop exiting"); +} + +// ---------------------------------------------------------------- +// WSTP I/O — only ever called from worker_loop() +// ---------------------------------------------------------------- +std::optional WolframLink::evaluate_on_worker(const std::string& expr) { + std::string wrapped = "ToString[" + expr + ", InputForm]"; + + WSPutFunction(link_, "EvaluatePacket", 1); + WSPutFunction(link_, "ToExpression", 1); + WSPutString(link_, wrapped.c_str()); + WSEndPacket(link_); + WSFlush(link_); + + int pkt; + while ((pkt = WSNextPacket(link_)) != RETURNPKT) { + if (pkt == 0) { + spdlog::error("WSNextPacket returned 0 (link error): {}", + WSErrorMessage(link_)); + return std::nullopt; + } + WSNewPacket(link_); + } + + const char* result = nullptr; + if (!WSGetString(link_, &result)) { + spdlog::error("WSGetString failed: {}", WSErrorMessage(link_)); + WSNewPacket(link_); + return std::nullopt; + } + + std::string out(result); + WSReleaseString(link_, result); + WSNewPacket(link_); + return out; +} diff --git a/src/WolframLink.hpp b/src/WolframLink.hpp new file mode 100644 index 0000000..b10ec1a --- /dev/null +++ b/src/WolframLink.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class WolframLink { +public: + // ---------------------------------------------------------------- + // Singleton access + // ---------------------------------------------------------------- + static std::shared_ptr instance(WSLINK link = nullptr); + static void destroy(); + + // ---------------------------------------------------------------- + // Non-copyable / non-movable + // ---------------------------------------------------------------- + WolframLink(const WolframLink&) = delete; + WolframLink& operator=(const WolframLink&) = delete; + WolframLink(WolframLink&&) = delete; + WolframLink& operator=(WolframLink&&) = delete; + + ~WolframLink(); + + // ---------------------------------------------------------------- + // Public API — callable from any thread + // ---------------------------------------------------------------- + std::future> evaluate(const std::string& expr); + + std::future> evaluate_and_log( + const std::string& label, + const std::string& expr); + + std::size_t queue_depth() const; + +private: + explicit WolframLink(WSLINK link); + + void worker_loop(); + std::optional evaluate_on_worker(const std::string& expr); + + // ---------------------------------------------------------------- + // Members + // ---------------------------------------------------------------- + WSLINK link_; + std::atomic shutdown_; + + mutable std::mutex queue_mutex_; + std::condition_variable queue_cv_; + std::queue> queue_; + + std::thread worker_; + + // ---------------------------------------------------------------- + // Singleton state + // ---------------------------------------------------------------- + static std::shared_ptr instance_; + static std::mutex instance_mutex_; +};