#include "session_registry.h" #ifdef VOICECAT_HAS_NET #include #include #include #include "conn_session.h" namespace voicecat::server { void SessionRegistry::init_default_channels() { std::unique_lock lk(mu_); ChannelEntry lobby; lobby.proto.set_id(1); lobby.proto.set_name("Lobby"); lobby.proto.set_type(voicecat::v1::CHANNEL_PERMANENT); lobby.proto.set_order(0); { // Speech profile: mono, low bitrate, FEC+DTX on for resilience/silence-suppression. auto* a = lobby.proto.mutable_audio(); a->set_codec(0); a->set_mode(voicecat::v1::MODE_MONO); a->set_sample_rate(48000); a->set_bitrate_bps(24000); a->set_frame_ms(20); a->set_application(voicecat::v1::OPUS_VOIP); a->set_fec(true); a->set_expected_packet_loss(10); a->set_dtx(true); a->set_complexity(5); } channels_[1] = std::move(lobby); ChannelEntry music; music.proto.set_id(2); music.proto.set_name("Music Room"); music.proto.set_type(voicecat::v1::CHANNEL_PERMANENT); music.proto.set_order(1); { // Music/screen-audio profile: stereo, high bitrate, FEC/DTX off (continuous signal). auto* a = music.proto.mutable_audio(); a->set_codec(0); a->set_mode(voicecat::v1::MODE_STEREO); a->set_sample_rate(48000); a->set_bitrate_bps(128000); a->set_frame_ms(20); a->set_application(voicecat::v1::OPUS_AUDIO); a->set_fec(false); a->set_expected_packet_loss(0); a->set_dtx(false); a->set_complexity(8); } channels_[2] = std::move(music); next_channel_id_ = 3; // 1 and 2 are now reserved (Lobby, Music Room) } uint64_t SessionRegistry::register_session(std::weak_ptr session) { std::unique_lock lk(mu_); uint64_t id = next_session_id_++; sessions_[id] = std::move(session); return id; } void SessionRegistry::unregister_session(uint64_t session_id) { std::unique_lock lk(mu_); sessions_.erase(session_id); } uint32_t SessionRegistry::add_user(uint64_t session_id, const voicecat::v1::User& user) { std::unique_lock lk(mu_); uint32_t uid = next_user_id_++; UserEntry entry; entry.proto = user; entry.proto.set_id(uid); entry.proto.set_channel_id(1); // start in Lobby entry.session_id = session_id; users_[uid] = std::move(entry); return uid; } void SessionRegistry::remove_user(uint32_t user_id) { std::unique_lock lk(mu_); users_.erase(user_id); } bool SessionRegistry::set_user_channel(uint32_t user_id, uint32_t channel_id) { std::unique_lock lk(mu_); auto ch_it = channels_.find(channel_id); if (ch_it == channels_.end()) return false; auto user_it = users_.find(user_id); if (user_it == users_.end()) return false; user_it->second.proto.set_channel_id(channel_id); return true; } std::vector SessionRegistry::channel_snapshot() const { std::shared_lock lk(mu_); std::vector result; result.reserve(channels_.size()); for (auto& [id, entry] : channels_) result.push_back(entry.proto); return result; } std::vector SessionRegistry::user_snapshot() const { std::shared_lock lk(mu_); std::vector result; result.reserve(users_.size()); for (auto& [id, entry] : users_) result.push_back(entry.proto); return result; } std::vector> SessionRegistry::resolve_text_targets( uint64_t sender_session_id, voicecat::v1::TextScope scope, uint32_t target_id) const { std::shared_lock lk(mu_); std::vector> targets; if (scope == voicecat::v1::TEXT_CHANNEL) { // Find channel_id of the target, then all users in that channel for (auto& [uid, entry] : users_) { if (entry.proto.channel_id() != target_id) continue; if (entry.session_id == sender_session_id) continue; auto sit = sessions_.find(entry.session_id); if (sit == sessions_.end()) continue; if (auto sess = sit->second.lock()) targets.push_back(sess); } } else if (scope == voicecat::v1::TEXT_PRIVATE) { // target_id is user_id auto user_it = users_.find(target_id); if (user_it != users_.end()) { auto sit = sessions_.find(user_it->second.session_id); if (sit != sessions_.end()) { if (auto sess = sit->second.lock()) targets.push_back(sess); } } } return targets; } void SessionRegistry::broadcast(const voicecat::v1::Envelope& env, uint64_t exclude_session_id) const { std::shared_lock lk(mu_); for (auto& [sid, weak] : sessions_) { if (sid == exclude_session_id) continue; if (auto sess = weak.lock()) sess->send_envelope(env); } } // ── M2: UDP / media ────────────────────────────────────────────────────────── void SessionRegistry::register_udp_token(const std::array& token, uint64_t session_id) { std::unique_lock lk(mu_); udp_tokens_[token] = session_id; } std::shared_ptr SessionRegistry::find_by_udp_token( const std::array& token) const { std::shared_lock lk(mu_); auto it = udp_tokens_.find(token); if (it == udp_tokens_.end()) return nullptr; auto sit = sessions_.find(it->second); if (sit == sessions_.end()) return nullptr; return sit->second.lock(); } void SessionRegistry::register_udp_endpoint(asio::ip::udp::endpoint ep, uint64_t session_id) { std::unique_lock lk(mu_); udp_endpoints_[ep] = session_id; } std::shared_ptr SessionRegistry::find_by_udp_endpoint( const asio::ip::udp::endpoint& ep) const { std::shared_lock lk(mu_); auto it = udp_endpoints_.find(ep); if (it == udp_endpoints_.end()) return nullptr; auto sit = sessions_.find(it->second); if (sit == sessions_.end()) return nullptr; return sit->second.lock(); } uint32_t SessionRegistry::assign_ssrc(uint64_t session_id) { uint32_t ssrc = next_ssrc_.fetch_add(1, std::memory_order_relaxed); std::unique_lock lk(mu_); ssrc_to_session_[ssrc] = session_id; return ssrc; } std::vector> SessionRegistry::find_channel_sessions( uint32_t channel_id, uint64_t exclude_session_id) const { std::shared_lock lk(mu_); std::vector> result; for (auto& [uid, entry] : users_) { if (entry.proto.channel_id() != channel_id) continue; if (entry.session_id == exclude_session_id) continue; auto sit = sessions_.find(entry.session_id); if (sit == sessions_.end()) continue; if (auto sess = sit->second.lock()) result.push_back(sess); } return result; } std::optional SessionRegistry::set_user_stream( uint32_t user_id, const voicecat::v1::StreamInfo& info) { std::unique_lock lk(mu_); auto it = users_.find(user_id); if (it == users_.end()) return std::nullopt; auto* streams = it->second.proto.mutable_streams(); for (int i = 0; i < streams->size(); ++i) { if (streams->Get(i).stream_id() == info.stream_id()) { *streams->Mutable(i) = info; return it->second.proto; } } *streams->Add() = info; return it->second.proto; } std::optional SessionRegistry::clear_user_stream(uint32_t user_id, uint32_t stream_id) { std::unique_lock lk(mu_); auto it = users_.find(user_id); if (it == users_.end()) return std::nullopt; auto* streams = it->second.proto.mutable_streams(); for (int i = 0; i < streams->size(); ++i) { if (streams->Get(i).stream_id() == stream_id) { streams->erase(streams->begin() + i); break; } } return it->second.proto; } uint32_t SessionRegistry::user_channel(uint32_t user_id) const { std::shared_lock lk(mu_); auto it = users_.find(user_id); return (it == users_.end()) ? 0 : it->second.proto.channel_id(); } std::optional SessionRegistry::channel_audio_config( uint32_t channel_id) const { std::shared_lock lk(mu_); auto it = channels_.find(channel_id); if (it == channels_.end()) return std::nullopt; return it->second.proto.audio(); } } // namespace voicecat::server #endif // VOICECAT_HAS_NET