// Rhiannon demo — CLI + simple loop (optional WS/MIDI compiled out by default) #include "agents/rhiannon.hpp" #include #include #include #include #include #include #ifdef RHIANNON_WITH_WS #include #include #endif #ifdef __has_include # if __has_include() # include using ::spdlog::info; using ::spdlog::warn; # else static inline void info(const char* fmt) { (void)fmt; } static inline void warn(const char* fmt) { (void)fmt; } # endif #endif using namespace std::chrono_literals; struct Options { std::size_t dim = 8; int iters = 100; bool headless = false; bool live_midi = false; bool ws = false; bool gpu = false; int ws_port = 8080; }; static void print_usage(const char* argv0) { std::cout << "Usage: " << argv0 << " [--dim N] [--iters N] [--headless] [--live-midi] [--ws PORT] [--gpu]\n"; } int main(int argc, char** argv) { Options opt{}; static option long_opts[] = { {"dim", required_argument, nullptr, 'd'}, {"iters", required_argument, nullptr, 'i'}, {"headless", no_argument, nullptr, 'H'}, {"live-midi", no_argument, nullptr, 'M'}, {"ws", required_argument, nullptr, 'W'}, {"gpu", no_argument, nullptr, 'G'}, {"help", no_argument, nullptr, 'h'}, {nullptr, 0, nullptr, 0} }; int c; while ((c = ::getopt_long(argc, argv, "d:i:HMW:Gh", long_opts, nullptr)) != -1) { switch (c) { case 'd': opt.dim = static_cast(std::stoul(optarg)); break; case 'i': opt.iters = std::stoi(optarg); break; case 'H': opt.headless = true; break; case 'M': opt.live_midi = true; break; case 'W': opt.ws = true; opt.ws_port = std::stoi(optarg); break; case 'G': opt.gpu = true; break; case 'h': default: print_usage(argv[0]); return c == 'h' ? 0 : 1; } } agents::TwoWayEngine::Cfg cfg; cfg.dim = opt.dim; cfg.iters = opt.iters; cfg.epsilon = 1e-6; cfg.use_gpu = opt.gpu; agents::Rhiannon rhiannon(cfg); std::vector input(opt.dim, 0.5f); auto gen_target = [&]() { const auto now = std::chrono::steady_clock::now().time_since_epoch(); const double t = std::chrono::duration(now).count(); for (std::size_t i = 0; i < opt.dim; ++i) { input[i] = static_cast(0.5 + 0.5 * std::sin(0.2 * i + t)); } rhiannon.set_input(input); }; #ifdef RHIANNON_WITH_WS std::unique_ptr ws_thread; std::unique_ptr app; if (opt.ws) { app = std::make_unique(); app->ws("/*", { .message = [&](auto *ws, std::string_view, uWS::OpCode) { nlohmann::json j; j["e"] = rhiannon.state_e(); j["z"] = rhiannon.state_z(); ws->send(j.dump(), uWS::OpCode::TEXT); } }).listen(opt.ws_port, [&](auto *token) { if (token) info("WebSocket listening"); }); ws_thread = std::make_unique([app_ptr = app.get()]() { app_ptr->run(); }); } #else if (opt.ws) { warn("WebSocket disabled at build time; rebuild with -DRHIANNON_WITH_WS=ON"); } #endif for (int tick = 0; tick < 1000; ++tick) { gen_target(); rhiannon.tick(); const double e = rhiannon.state_e(); if (!opt.headless) { std::cout << "Tick " << tick << " e=" << e << " dim=" << opt.dim << "\n"; } if (e < 1e-4) break; std::this_thread::sleep_for(16ms); } #ifdef RHIANNON_WITH_WS if (opt.ws && app) { app->close(); if (ws_thread && ws_thread->joinable()) ws_thread->join(); } #endif return 0; }