summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorlittleblack111 <littleblack11111@gmail.com>2025-06-12 21:41:41 +0800
committerlittleblack111 <littleblack11111@gmail.com>2025-06-13 20:22:51 +0800
commit6f7b994684032060f0a2d7af82f4f3572293b8d6 (patch)
tree793d328cbcd6761365267550b55db45aee9b21fc /src/core
parent6a51ab4525c59e9fe2bdbd92493019b0a98c6b94 (diff)
internal!: organized internal structure, using cmake now
Diffstat (limited to 'src/core')
-rw-r--r--src/core/chatManager.cpp36
-rw-r--r--src/core/chatManager.hpp25
-rw-r--r--src/core/chatServer.cpp68
-rw-r--r--src/core/chatServer.hpp20
-rw-r--r--src/core/server.cpp39
-rw-r--r--src/core/server.hpp19
-rw-r--r--src/core/session.cpp273
-rw-r--r--src/core/session.hpp88
-rw-r--r--src/core/sessionManager.cpp141
-rw-r--r--src/core/sessionManager.hpp16
10 files changed, 717 insertions, 8 deletions
diff --git a/src/core/chatManager.cpp b/src/core/chatManager.cpp
new file mode 100644
index 0000000..e981e7f
--- /dev/null
+++ b/src/core/chatManager.cpp
@@ -0,0 +1,36 @@
+#include "chatManager.hpp"
+#include "../io/IOManager.hpp"
+#include "../io/inputManager.hpp"
+#include "../debug/log.hpp"
+#include "sessionManager.hpp"
+
+CChatManager::CChatManager() {
+ log(LOG, "ChatManager: initialized");
+}
+
+CChatManager::~CChatManager() {
+ log(SYS, "ChatManager: bye");
+}
+
+void CChatManager::newMessage(const SMessage &msg) {
+ log(LOG, "{}: {}", msg.username, msg.msg);
+ const auto session = msg.sender ? msg.sender->lock() : g_pSessionManager->getByName(msg.username);
+ if (session && session->isMuted()) {
+ session->write("You are muted");
+ return;
+ }
+ g_pSessionManager->broadcast(msg);
+}
+
+std::string CChatManager::fmtBroadcastMessage(const SMessage &msg) {
+ return NFormatter::fmt(NONE, "{}: {}", msg.username, msg.msg);
+}
+
+std::vector<CChatManager::SMessage> CChatManager::getChat() const {
+ std::vector<SMessage> chat;
+
+ for (const auto &[msg, _, _] : g_pIOManager->getIO())
+ if (msg)
+ chat.push_back(*msg);
+ return chat;
+}
diff --git a/src/core/chatManager.hpp b/src/core/chatManager.hpp
new file mode 100644
index 0000000..bd305de
--- /dev/null
+++ b/src/core/chatManager.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+class CSession;
+
+class CChatManager {
+ public:
+ CChatManager();
+ ~CChatManager();
+ struct SMessage {
+ std::string msg;
+ std::string username;
+ std::optional<std::weak_ptr<CSession>> sender = std::nullopt;
+ bool admin = false;
+ };
+ void newMessage(const SMessage &msg);
+ std::string fmtBroadcastMessage(const SMessage &msg);
+
+ std::vector<SMessage> getChat() const;
+};
+inline std::unique_ptr<CChatManager> g_pChatManager;
diff --git a/src/core/chatServer.cpp b/src/core/chatServer.cpp
new file mode 100644
index 0000000..4a16c56
--- /dev/null
+++ b/src/core/chatServer.cpp
@@ -0,0 +1,68 @@
+#include "chatServer.hpp"
+#include "../io/IOManager.hpp"
+#include "chatManager.hpp"
+#include "../command/commandHandler.hpp"
+#include "../command/commands.hpp"
+#include "../io/inputManager.hpp"
+#include "../debug/log.hpp"
+#include "../io/renderManager.hpp"
+#include "server.hpp"
+#include "sessionManager.hpp"
+
+CChatServer::CChatServer(uint16_t port)
+ : m_port(port) {
+ if (std::atexit([]() { g_pChatServer->cleanup(); }))
+ log(ERR, "Failed to register CChatServer atexit handler, dtors will not be called");
+
+ log(LOG, "ChatServer: initialized");
+};
+
+CChatServer::~CChatServer() {
+ cleanup();
+ log(SYS, "ChatServer: bye");
+}
+
+void CChatServer::cleanup() {
+ if (g_pSessionManager) {
+ g_pSessionManager->shutdownSessions();
+ g_pSessionManager.reset();
+ }
+
+ g_pChatManager.reset();
+ g_pServer.reset();
+ g_pCommandHandler.reset();
+ g_pInputManager.reset();
+ g_pIOManager.reset();
+ g_pRenderManager.reset();
+}
+
+void CChatServer::initManagers() {
+ log(LOG, "ChatServer: initializing managers");
+
+ log(LOG, "Creating RenderManager");
+ g_pRenderManager = std::make_unique<CRenderManager>();
+ log(LOG, "Creating InputManager");
+ g_pInputManager = std::make_unique<CInputManager>();
+ log(LOG, "Creating Server");
+ g_pServer = std::make_unique<CServer>(m_port);
+ log(LOG, "Creating IOManager");
+ g_pIOManager = std::make_unique<CIOManager>();
+ log(LOG, "Creating SessionManager");
+ g_pSessionManager = std::make_shared<CSessionManager>();
+ log(LOG, "Creating ChatManager");
+ g_pChatManager = std::make_unique<CChatManager>();
+}
+
+void CChatServer::start() {
+ initManagers();
+
+ log(LOG, "Creating CommandHandler");
+ g_pCommandHandler = std::make_unique<CCommandHandler>();
+ registerCommands();
+
+ // will await while waiting for accept() to return a new client
+ m_sessionManagerThread = std::jthread(&CSessionManager::run, g_pSessionManager.get());
+ m_sessionManagerThread.detach();
+
+ g_pInputManager->inputLoop();
+}
diff --git a/src/core/chatServer.hpp b/src/core/chatServer.hpp
new file mode 100644
index 0000000..00d2979
--- /dev/null
+++ b/src/core/chatServer.hpp
@@ -0,0 +1,20 @@
+#include <memory>
+#include <thread>
+
+class CChatServer {
+ public:
+ CChatServer(uint16_t port);
+ ~CChatServer();
+ void start();
+ void cleanup();
+
+ private:
+ uint16_t m_port;
+
+ void initManagers();
+ void initServer();
+
+ std::jthread m_sessionManagerThread;
+};
+
+inline std::unique_ptr<CChatServer> g_pChatServer;
diff --git a/src/core/server.cpp b/src/core/server.cpp
new file mode 100644
index 0000000..a78c217
--- /dev/null
+++ b/src/core/server.cpp
@@ -0,0 +1,39 @@
+#include "server.hpp"
+#include "../debug/log.hpp"
+#include <cstring>
+#include <memory>
+#include <sys/socket.h>
+
+CServer::CServer(uint16_t port)
+ : m_sockfd(std::make_shared<CFileDescriptor>(socket(AF_INET, SOCK_STREAM, 0))) {
+ if (!m_sockfd->isValid())
+ throw std::runtime_error("Failed to create socket");
+
+ constexpr int opt = 1;
+ constexpr int size = sizeof(opt);
+ if (setsockopt(m_sockfd->get(), SOL_SOCKET, SO_REUSEADDR, &opt, size) < 0)
+ log(stderr, WARN, "setsockopt(SO_REUSEADDR) failed");
+
+ if (setsockopt(m_sockfd->get(), SOL_SOCKET, SO_KEEPALIVE, &opt, size) < 0)
+ log(stderr, WARN, "setsockopt(SO_KEEPALIVE) failed");
+
+ memset(&m_addr, 0, sizeof(m_addr));
+ m_addr.sin_family = AF_INET;
+ m_addr.sin_port = htons(port);
+ m_addr.sin_addr.s_addr = INADDR_ANY;
+ bind(m_sockfd->get(), reinterpret_cast<sockaddr *>(&m_addr), sizeof(m_addr)) ||
+ listen(m_sockfd->get(), 5)
+ ? throw std::runtime_error("Failed to bind or listen")
+ : log(LOG, "Server started on port: {}", port);
+
+ log(LOG, "Server: initialized");
+};
+
+CServer::~CServer() {
+ m_sockfd.reset();
+ log(SYS, "Server: bye");
+}
+
+std::shared_ptr<CFileDescriptor> CServer::getSocket() const {
+ return m_sockfd;
+}
diff --git a/src/core/server.hpp b/src/core/server.hpp
new file mode 100644
index 0000000..c42192b
--- /dev/null
+++ b/src/core/server.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "../misc/FileDescriptor.hpp"
+#include <memory>
+#include <netinet/in.h>
+
+class CServer {
+ public:
+ CServer(uint16_t port);
+ ~CServer();
+ std::shared_ptr<CFileDescriptor> getSocket() const;
+
+ private:
+ std::shared_ptr<CFileDescriptor> m_sockfd;
+ sockaddr_in m_addr;
+
+ mutable std::mutex m_mutex;
+};
+inline std::unique_ptr<CServer> g_pServer;
diff --git a/src/core/session.cpp b/src/core/session.cpp
new file mode 100644
index 0000000..7b0b459
--- /dev/null
+++ b/src/core/session.cpp
@@ -0,0 +1,273 @@
+#include "session.hpp"
+#include "chatManager.hpp"
+#include "../command/commandHandler.hpp"
+#include "../debug/format.hpp"
+#include "../debug/log.hpp"
+#include "server.hpp"
+#include "sessionManager.hpp"
+#include <algorithm>
+#include <arpa/inet.h>
+#include <cerrno>
+#include <cstring>
+#include <memory>
+#include <optional>
+#include <sys/socket.h>
+
+CSession::CSession() {
+ m_sockfd = std::make_shared<CFileDescriptor>(accept(g_pServer->getSocket()->get(), reinterpret_cast<sockaddr *>(&m_addr), &m_addrLen)); // if this is in the init list, it will run before m_addrLen, so it won't work :/
+
+ if (!m_sockfd->isValid())
+ throw std::runtime_error("session: Failed to create socket");
+
+ m_ip.resize(INET_ADDRSTRLEN);
+ inet_ntop(AF_INET, &m_addr.sin_addr, &m_ip[0], INET_ADDRSTRLEN);
+ m_ip.resize(strlen(m_ip.c_str()));
+ m_port = ntohs(m_addr.sin_port);
+ m_isAdmin = std::ranges::any_of(m_adminIps, [this](const char *ip) { return m_ip == ip; });
+
+ log(LOG, "Client {} connected on port {}", m_ip, m_port);
+
+ log(TRACE, "session: initialized");
+}
+
+CSession::~CSession() {
+#ifdef DEBUG
+ onDisconnect();
+#endif
+ m_sockfd.reset();
+
+ log(LOG, "Client {}@{} disconnected", m_name, m_ip);
+
+ log(TRACE, "session({}@{}): bye", m_name, m_ip);
+}
+
+#ifdef DEBUG
+void CSession::onConnect() {
+ log(TRACE, "Client {}@{} connected", m_name, m_ip);
+}
+
+void CSession::onDisconnect() {
+ log(TRACE, "Client {}@{} disconnected", m_name, m_ip);
+}
+#endif
+
+void CSession::onErrno(eEventType eventType) {
+ char errbuf[256];
+ const char *err = strerror_r(errno, errbuf, sizeof(errbuf));
+
+ switch (eventType) {
+ case READ:
+ log(WARN, "Error while reading: {}", err);
+ break;
+ case WRITE:
+ log(WARN, "Error while writing: {}", err);
+ break;
+ }
+}
+
+#ifdef DEBUG
+void CSession::onRecv(const SRecvData &data) {
+ log(TRACE, "Received: {}", data.data);
+}
+
+void CSession::onSend(const std::string &msg) {
+ log(LOG, "Sent: {}", msg);
+}
+#endif
+
+void CSession::recvLoop() {
+ while (true) {
+ auto recvData = read(NFormatter::fmt(NONEWLINE, "{}: ", m_name));
+ if (!recvData->good)
+ break;
+
+ recvData->sanitize();
+ if (recvData->isEmpty()) {
+ log(TRACE, "Empty message from {}", m_name);
+ continue;
+ }
+
+ const auto isCommand = g_pCommandHandler->isCommand(recvData->data);
+ if (isCommand)
+ g_pCommandHandler->handleCommand(recvData->data, self.lock());
+
+ g_pChatManager->newMessage({.msg = recvData->data, .username = m_name, .sender = self, .admin = isCommand});
+ }
+}
+
+bool CSession::SRecvData::isEmpty() const {
+ return data.empty() || std::ranges::all_of(data, [](char c) { return std::isspace(c); });
+}
+
+void CSession::SRecvData::sanitize() {
+ if (!data.empty() && data.back() == '\n')
+ data.pop_back();
+
+ data.erase(std::remove(data.begin(), data.end(), asciiEscape), data.end()); // don't accept ASCII code, might mess up terminal // NOLINT
+
+ if (const auto start = data.find_first_not_of(" \t\r\n"); start != std::string::npos) {
+ const auto end = data.find_last_not_of(" \t\r\n");
+ data = data.substr(start, end - start + 1);
+ } else
+ data.clear();
+}
+
+std::unique_ptr<CSession::SRecvData> CSession::read() {
+ auto recvData = std::make_unique<SRecvData>();
+
+ recvData->data.resize(recvData->size);
+ ssize_t size = recv(m_sockfd->get(), &recvData->data[0], recvData->size, 0);
+
+ if (size < 0) {
+ recvData->good = false;
+ onErrno(READ);
+ } else if (size == 0)
+ recvData->good = false;
+ else {
+ recvData->data.resize(size);
+ recvData->good = true;
+ }
+
+#ifdef DEBUG
+ onRecv(*recvData);
+#endif
+ m_szReading.reset();
+ return recvData;
+}
+
+std::unique_ptr<CSession::SRecvData> CSession::read(const std::string &msg, bool bypassDeaf) {
+ if (bypassDeaf && m_bDeaf)
+ m_bDeaf = false;
+ write(NONEWLINE, "{}", msg);
+ if (bypassDeaf && !m_bDeaf)
+ m_bDeaf = true;
+ m_szReading = msg;
+ return read();
+}
+
+void CSession::run() {
+#ifdef DEBUG
+ onConnect();
+#endif
+ if (registerSession()) {
+ for (const auto &chat : g_pChatManager->getChat())
+ writeChat(chat);
+ recvLoop();
+ } else
+ log(LOG, "Client {} exited without even registering", m_ip);
+
+ g_pSessionManager->kick(self);
+}
+
+bool CSession::isMuted() const {
+ return m_bMuted;
+}
+
+void CSession::setMuted(bool mute) {
+ m_bMuted = mute;
+}
+
+bool CSession::isDeaf() const {
+ return m_bDeaf;
+}
+
+void CSession::setDeaf(bool deaf) {
+ m_bDeaf = deaf;
+}
+
+bool CSession::registerSession() {
+ auto recvData = read("Name: ", true);
+ if (!recvData->good)
+ return false;
+
+ recvData->sanitize();
+ if (recvData->isEmpty()) {
+ m_bDeaf = false;
+ write("Name cannot be empty");
+ m_bDeaf = true;
+ return registerSession();
+ }
+
+ if (g_pSessionManager->nameExists(recvData->data)) {
+ m_bDeaf = false;
+ write("Name already exists");
+ m_bDeaf = true;
+ return registerSession();
+ }
+
+ this->m_name = recvData->data;
+
+ log(LOG, "Client {} registered as: {}", m_ip, m_name);
+
+ g_pSessionManager->broadcast(NFormatter::fmt(NONEWLINE, "{} has joined the chat", m_name), self);
+
+ m_bDeaf = false;
+ return true;
+}
+
+bool CSession::isValid() {
+ if (!m_sockfd->isValid() || m_name.empty())
+ return false;
+
+ int err = 0;
+ socklen_t size = sizeof(err);
+ return getsockopt(m_sockfd->get(), SOL_SOCKET, SO_ERROR, &err, &size) >= 0 && err == 0;
+}
+
+bool CSession::write(const std::string &msg) {
+ return write("{}", msg);
+}
+
+bool CSession::writeChat(const CChatManager::SMessage &msg) {
+ const auto sender = msg.sender ? msg.sender->lock() : nullptr;
+ if (sender ? sender != self.lock() : msg.username != m_name) {
+ if (msg.admin && !m_isAdmin)
+ return false;
+
+ return write(g_pChatManager->fmtBroadcastMessage(msg));
+ }
+ return false;
+}
+
+const std::string &CSession::getName() const {
+ return m_name;
+}
+
+const std::string &CSession::getIp() const {
+ return m_ip;
+}
+
+bool CSession::isAdmin() const {
+ return m_isAdmin;
+}
+
+template <typename... Args>
+bool CSession::write(std::format_string<Args...> fmt, Args &&...args) {
+ if (m_bDeaf)
+ return false;
+ return write(NONE, fmt, std::forward<Args>(args)...);
+}
+
+template <typename... Args>
+bool CSession::write(eFormatType type, std::format_string<Args...> fmt, Args &&...args) {
+ if (m_bDeaf)
+ return false;
+
+ std::string msg = NFormatter::fmt(type, fmt, std::forward<Args>(args)...);
+ if (m_szReading) {
+ msg.insert(0, "\r");
+ msg.append(*m_szReading);
+ }
+
+ if (send(m_sockfd->get(), msg.c_str(), msg.size(), 0) < 0) {
+ onErrno(WRITE);
+ return false;
+ }
+#ifdef DEBUG
+ onSend(msg);
+#endif
+ return true;
+}
+
+template bool CSession::write<const std::string &>(std::format_string<const std::string &>, const std::string &);
+template bool CSession::write<const std::string &>(eFormatType, std::format_string<const std::string &>, const std::string &);
diff --git a/src/core/session.hpp b/src/core/session.hpp
new file mode 100644
index 0000000..ca951d1
--- /dev/null
+++ b/src/core/session.hpp
@@ -0,0 +1,88 @@
+#pragma once
+
+#include "../misc/FileDescriptor.hpp"
+#include "chatManager.hpp"
+#include "../debug/format.hpp"
+#include <format>
+#include <memory>
+#include <netinet/in.h>
+#include <optional>
+#include <string>
+#include <sys/socket.h>
+#include <thread>
+
+class CSession {
+ public:
+ CSession();
+ ~CSession();
+
+ struct SRecvData {
+ std::string data;
+ const size_t size = 1024;
+ bool good = false;
+
+ bool isEmpty() const;
+ void sanitize();
+
+ static constexpr char asciiEscape = 0x1B; // 
+ };
+
+ const std::string &getName() const;
+ const std::string &getIp() const;
+ bool isAdmin() const;
+
+ std::unique_ptr<SRecvData> read();
+ std::unique_ptr<SRecvData> read(const std::string &msg, bool bypassDeaf = false);
+ bool write(const std::string &msg);
+ template <typename... Args>
+ bool write(std::format_string<Args...> fmt, Args &&...args);
+ template <typename... Args>
+ bool write(eFormatType type, std::format_string<Args...> fmt, Args &&...args);
+ bool writeChat(const CChatManager::SMessage &msg);
+
+ bool isMuted() const;
+ void setMuted(bool mute);
+ bool isDeaf() const;
+ void setDeaf(bool deaf);
+
+ void run();
+
+ bool isValid();
+
+ private:
+ constexpr static char m_adminIps[1][INET_ADDRSTRLEN] = {"127.0.0.1"};
+
+ enum eEventType : std::uint8_t {
+ READ,
+ WRITE
+ };
+
+ std::weak_ptr<CSession> self;
+ std::shared_ptr<CFileDescriptor> m_sockfd;
+ sockaddr_in m_addr;
+ socklen_t m_addrLen = sizeof(m_addr);
+ std::string m_name;
+ std::string m_ip;
+ int m_port;
+ bool m_isAdmin = false;
+
+ std::optional<std::string> m_szReading = std::nullopt;
+ bool m_bMuted = false;
+ bool m_bDeaf = true; // init as true as we don't want anything during registerSession
+
+ void recvLoop();
+ bool registerSession();
+
+#ifdef DEBUG
+ void onConnect();
+ void onDisconnect();
+#endif
+ void onErrno(eEventType);
+#ifdef DEBUG
+ void onRecv(const SRecvData &data);
+ void onSend(const std::string &msg);
+#endif
+
+ friend class CSessionManager;
+ friend void registerCommands();
+};
diff --git a/src/core/sessionManager.cpp b/src/core/sessionManager.cpp
new file mode 100644
index 0000000..6c002a5
--- /dev/null
+++ b/src/core/sessionManager.cpp
@@ -0,0 +1,141 @@
+#include "sessionManager.hpp"
+#include "../io/IOManager.hpp"
+#include "chatServer.hpp"
+#include "../debug/format.hpp"
+#include "../debug/log.hpp"
+#include "session.hpp"
+#include <algorithm>
+#include <ranges>
+#include <unistd.h>
+#include <utility>
+
+void CSessionManager::shutdownSessions() {
+ if (m_vSessions.empty())
+ return;
+
+ for (auto &[thread, session] : m_vSessions) {
+ if (session) {
+ session->setDeaf(false);
+ session->write("Server shutting down");
+ if (!session->getName().empty())
+ g_pSessionManager->broadcast(NFormatter::fmt(NONEWLINE, "{} has left the chat", session->getName()), std::weak_ptr<CSession>(session));
+ }
+ }
+
+ for (auto &[thread, session] : m_vSessions) {
+ if (thread.joinable())
+ thread.detach();
+ session.reset();
+ }
+
+ m_vSessions.clear();
+}
+
+CSessionManager::CSessionManager() {
+ if (std::atexit([]() {
+ if (g_pSessionManager)
+ g_pSessionManager->shutdownSessions();
+ }))
+ log(ERR, "Failed to register SessionManager atexit handler, will fallback to quick_exit after hang");
+
+ log(LOG, "SessionManager: initialized");
+};
+
+CSessionManager::~CSessionManager() {
+ for (auto &[thread, session] : m_vSessions) {
+ if (thread.joinable())
+ thread.detach();
+ kick(std::weak_ptr<CSession>(session));
+ }
+
+ log(SYS, "SessionManager: bye");
+}
+
+void CSessionManager::newSession() {
+ std::shared_ptr session = std::make_shared<CSession>();
+ auto &instance = m_vSessions.emplace_back(std::jthread([session]() { session->run(); }), session);
+ instance.second->self = std::weak_ptr<CSession>(instance.second);
+}
+
+void CSessionManager::run() {
+ while (true)
+ newSession();
+}
+
+void CSessionManager::broadcast(const std::string &msg, std::optional<std::weak_ptr<CSession>> self) const {
+ for (const auto &[thread, session] : m_vSessions)
+ if (!self || (self && session != self->lock()))
+ session->write(msg);
+
+ g_pIOManager->addCustom({"", msg});
+}
+
+void CSessionManager::broadcast(const CChatManager::SMessage &msg) const {
+ for (const auto &[thread, session] : m_vSessions) {
+ if (!session)
+ continue;
+
+ session->writeChat(msg);
+ }
+
+ g_pIOManager->addMessage(msg);
+}
+
+bool CSessionManager::nameExists(const std::string &name) {
+ auto it = std::ranges::find_if(m_vSessions, [&name](const auto &s) { return s.second->getName() == name; });
+
+ if (it != m_vSessions.end()) {
+ if (!it->second->isValid()) {
+ kick(std::weak_ptr<CSession>(it->second), true, "Connection lost");
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+std::shared_ptr<CSession> CSessionManager::getByName(const std::string &name) const {
+ auto it = std::ranges::find_if(m_vSessions, [&name](const auto &s) { return s.second->getName() == name; });
+ return it != m_vSessions.end() ? it->second : nullptr;
+}
+
+std::shared_ptr<CSession> CSessionManager::getByIp(const std::string &ip) const {
+ auto it = std::ranges::find_if(m_vSessions, [&ip](const auto &s) { log(LOG, s.second->getIp()); return s.second->getIp() == ip; });
+ return it != m_vSessions.end() ? it->second : nullptr;
+}
+
+std::vector<std::shared_ptr<CSession>> CSessionManager::getSessions() const {
+ return m_vSessions | std::views::transform([](const auto &s) { return s.second; }) | std::ranges::to<std::vector>();
+}
+
+void CSessionManager::kick(std::weak_ptr<CSession> session_weak, const bool kill, const std::string &reason) {
+ auto session = session_weak.lock();
+ if (!session)
+ return;
+
+ for (auto it = m_vSessions.begin(); it != m_vSessions.end(); ++it) {
+ if (it->second == session) {
+ if (session)
+ // this only exist when the session is registered
+ // not sure why the second is null, but m_name definately is since it's setted during register
+ // but we don't need to notify people if the session didn't even "join"/register anyways
+ g_pSessionManager->broadcast(NFormatter::fmt(NONEWLINE, "{} has left the chat", session->getName()), session_weak);
+
+ if (!reason.empty()) {
+ session->setDeaf(false);
+ session->write(reason);
+ }
+ const auto native_handle = it->first.native_handle();
+ if (it->first.joinable())
+ it->first.detach(); // .detach the thread since it's removing itself
+ it->second.reset();
+ m_vSessions.erase(it);
+ if (kill && native_handle != 0)
+ // cancel(terminal/kill) the thread if it doesn't exit on its own
+ // pthread_cancel cuz it's the safest https://stackoverflow.com/a/3438576
+ // need a condition because it might crash if the thread is already dead
+ pthread_cancel(native_handle);
+ return;
+ }
+ }
+}
diff --git a/src/core/sessionManager.hpp b/src/core/sessionManager.hpp
index e84226c..71bcc5f 100644
--- a/src/core/sessionManager.hpp
+++ b/src/core/sessionManager.hpp
@@ -1,9 +1,9 @@
#pragma once
#include "../debug/format.hpp"
-#include "../misc/memory.hpp"
#include "chatManager.hpp"
#include "session.hpp"
+#include <memory>
#include <thread>
#include <vector>
@@ -15,22 +15,22 @@ class CSessionManager {
void newSession();
void run();
- void broadcast(const std::string &msg, std::optional<WP<CSession>> self = std::nullopt) const; // second param only specified when we want to exclude the sender
- void kick(WP<CSession> session, const bool kill = false, const std::string &reason = "");
+ void broadcast(const std::string &msg, std::optional<std::weak_ptr<CSession>> self = std::nullopt) const; // second param only specified when we want to exclude the sender
+ void kick(std::weak_ptr<CSession> session, const bool kill = false, const std::string &reason = "");
void addSession(const CSession &session);
bool nameExists(const std::string &name);
void shutdownSessions();
- SP<CSession> getByName(const std::string &name) const;
- SP<CSession> getByIp(const std::string &ip) const;
- std::vector<SP<CSession>> getSessions() const;
+ std::shared_ptr<CSession> getByName(const std::string &name) const;
+ std::shared_ptr<CSession> getByIp(const std::string &ip) const;
+ std::vector<std::shared_ptr<CSession>> getSessions() const;
private:
void broadcast(const CChatManager::SMessage &msg) const;
- std::vector<std::pair<std::jthread, SP<CSession>>> m_vSessions;
+ std::vector<std::pair<std::jthread, std::shared_ptr<CSession>>> m_vSessions;
friend class CChatManager;
};
-inline SP<CSessionManager> g_pSessionManager;
+inline std::shared_ptr<CSessionManager> g_pSessionManager;