From f03922f2188a72d746a91048725679ffb34afe42 Mon Sep 17 00:00:00 2001 From: DustyBagel Date: Fri, 21 Jun 2024 16:33:44 -0500 Subject: [PATCH] Basic Lan Implmentation --- builtin/mainmenu/serverlistmgr.lua | 8 + builtin/mainmenu/tab_online.lua | 21 +- builtin/settingtypes.txt | 3 + src/CMakeLists.txt | 1 + src/client/clientlauncher.cpp | 2 + src/debug.h | 2 +- src/defaultsettings.cpp | 1 + src/fm_porting.h | 96 ++++++++++ src/log_types.cpp | 101 ++++++++++ src/log_types.h | 55 ++++++ src/network/CMakeLists.txt | 1 + src/network/address.h | 4 + src/network/lan.cpp | 296 +++++++++++++++++++++++++++++ src/network/lan.h | 46 +++++ src/script/lua_api/l_mainmenu.cpp | 48 +++++ src/script/lua_api/l_mainmenu.h | 11 ++ src/server.cpp | 9 + src/server.h | 4 + src/server/serverlist.cpp | 36 ++++ src/server/serverlist.h | 10 + src/threading/CMakeLists.txt | 2 + src/threading/concurrent_map.h | 209 ++++++++++++++++++++ src/threading/lock.cpp | 131 +++++++++++++ src/threading/lock.h | 164 ++++++++++++++++ src/threading/thread_vector.cpp | 118 ++++++++++++ src/threading/thread_vector.h | 37 ++++ 26 files changed, 1414 insertions(+), 2 deletions(-) create mode 100644 src/fm_porting.h create mode 100644 src/log_types.cpp create mode 100644 src/log_types.h create mode 100644 src/network/lan.cpp create mode 100644 src/network/lan.h create mode 100644 src/threading/concurrent_map.h create mode 100644 src/threading/lock.cpp create mode 100644 src/threading/lock.h create mode 100644 src/threading/thread_vector.cpp create mode 100644 src/threading/thread_vector.h diff --git a/builtin/mainmenu/serverlistmgr.lua b/builtin/mainmenu/serverlistmgr.lua index 601b42110..8fc09d85f 100644 --- a/builtin/mainmenu/serverlistmgr.lua +++ b/builtin/mainmenu/serverlistmgr.lua @@ -153,6 +153,14 @@ local function fetch_geoip() end function serverlistmgr.sync() + if minetest.settings:get("serverlist_lan") then + if core.ask_lan_servers then --This checks if the function exists before running it. + core.ask_lan_servers(); + else + print("core.ask_lan_servers isn't defined.") + end + end + if not serverlistmgr.servers then serverlistmgr.servers = {{ name = fgettext("Loading..."), diff --git a/builtin/mainmenu/tab_online.lua b/builtin/mainmenu/tab_online.lua index d93f45dcf..e1653dce1 100644 --- a/builtin/mainmenu/tab_online.lua +++ b/builtin/mainmenu/tab_online.lua @@ -22,6 +22,24 @@ local function get_sorted_servers() incompatible = {} } + --Special thanks to proller for letting use use the get_lan_servers function. + if minetest.settings:get_bool("serverlist_lan") then + if core.get_lan_servers then + servers.lan = core.get_lan_servers(); + + for _, server in ipairs(servers.lan) do + server.is_compatible = is_server_protocol_compat(server.proto_min, server.proto_max) + end + else + print("core.get_lan_servers isn't defined.") + servers.lan = {} + end + else + servers.lan = {} + end + + print(dump(servers.lan)) + local favs = serverlistmgr.get_favorites() local taken_favs = {} local result = menudata.search_result or serverlistmgr.servers @@ -154,11 +172,12 @@ local function get_formspec(tabview, name, tabdata) local servers = get_sorted_servers() local dividers = { + lan = "1,#00ff00," .. fgettext("Lan") .. ",,,0,0,,", fav = "5,#ffff00," .. fgettext("Favorites") .. ",,,0,0,,", public = "6,#4bdd42," .. fgettext("Public Servers") .. ",,,0,0,,", incompatible = "7,"..mt_color_grey.."," .. fgettext("Incompatible Servers") .. ",,,0,0,," } - local order = {"fav", "public", "incompatible"} + local order = {"lan", "fav", "public", "incompatible"} tabdata.lookup = {} -- maps row number to server local rows = {} diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index aca960590..c38322306 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -819,6 +819,9 @@ server_url (Server URL) string https://minetest.net # Automatically report to the serverlist. server_announce (Announce server) bool false +# Make local servers visible to local clients. +serverlist_lan (Show local servers) bool true + # Announce to this serverlist. serverlist_url (Serverlist URL) string servers.minetest.net diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 955bf05f2..00d5d0ca5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -417,6 +417,7 @@ set(common_SRCS light.cpp lighting.cpp log.cpp + #log_types.cpp main.cpp map.cpp map_settings_manager.cpp diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 6379fc141..b4af58eee 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -37,6 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include "server/serverlist.h" #if USE_SOUND #include "sound/sound_openal.h" @@ -536,6 +537,7 @@ bool ClientLauncher::launch_game(std::string &error_message, void ClientLauncher::main_menu(MainMenuData *menudata) { + ServerList::lan_get(); bool *kill = porting::signal_handler_killstatus(); video::IVideoDriver *driver = m_rendering_engine->get_video_driver(); diff --git a/src/debug.h b/src/debug.h index 80497f2b3..3a16c9dde 100644 --- a/src/debug.h +++ b/src/debug.h @@ -78,7 +78,7 @@ void debug_set_exception_handler(); These should be put into every thread */ -#if CATCH_UNHANDLED_EXCEPTIONS == 1 +#if CATCH_UNHANDLED_EXCEPTIONS == 0 #define BEGIN_DEBUG_EXCEPTION_HANDLER try { #define END_DEBUG_EXCEPTION_HANDLER \ } catch (std::exception &e) { \ diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index f16c56db4..09a55e220 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -529,6 +529,7 @@ void set_default_settings() Mapgen::setDefaultSettings(settings); // Server list announcing + settings->setDefault("serverlist_lan", "true"); settings->setDefault("server_announce", "false"); settings->setDefault("server_url", ""); settings->setDefault("server_address", ""); diff --git a/src/fm_porting.h b/src/fm_porting.h new file mode 100644 index 000000000..43ca4340f --- /dev/null +++ b/src/fm_porting.h @@ -0,0 +1,96 @@ +#pragma once + +#include "porting.h" + +#if defined(linux) || defined(__linux) + #include +#elif defined(__FreeBSD__) || defined(__OpenBSD__) + #include + #include +#elif defined(__NetBSD__) + #include +#elif defined(__APPLE__) + #include +#endif + +#if defined(linux) || defined(__linux) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define PORTING_USE_PTHREAD 1 + #include +#endif + +namespace porting +{ + +extern std::atomic_bool g_sighup, g_siginfo; + + +#if defined(linux) || defined(__linux) + inline void setThreadName(const char *name) { + /* It would be cleaner to do this with pthread_setname_np, + * which was added to glibc in version 2.12, but some major + * distributions are still runing 2.11 and previous versions. + */ + prctl(PR_SET_NAME, name); + } +#elif defined(__FreeBSD__) || defined(__OpenBSD__) + inline void setThreadName(const char *name) { + pthread_set_name_np(pthread_self(), name); + } +#elif defined(__NetBSD__) + inline void setThreadName(const char *name) { + pthread_setname_np(pthread_self(), name); + } +#elif defined(_MSC_VER) + typedef struct tagTHREADNAME_INFO { + DWORD dwType; // must be 0x1000 + LPCSTR szName; // pointer to name (in user addr space) + DWORD dwThreadID; // thread ID (-1=caller thread) + DWORD dwFlags; // reserved for future use, must be zero + } THREADNAME_INFO; + + inline void setThreadName(const char *name) { + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = -1; + info.dwFlags = 0; + __try { + RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD), (ULONG_PTR *) &info); + } __except (EXCEPTION_CONTINUE_EXECUTION) {} + } +#elif defined(__APPLE__) + inline void setThreadName(const char *name) { + pthread_setname_np(name); + } +#elif defined(_WIN32) || defined(__GNU__) + inline void setThreadName(const char* name) {} +#else +#ifndef __EMSCRIPTEN__ + #warning "Unrecognized platform, thread names will not be available." +#endif + + inline void setThreadName(const char* name) {} +#endif + + inline void setThreadPriority(int priority) { +#if PORTING_USE_PTHREAD + // http://en.cppreference.com/w/cpp/thread/thread/native_handle + sched_param sch; + //int policy; + //pthread_getschedparam(pthread_self(), &policy, &sch); + sch.sched_priority = priority; + if(pthread_setschedparam(pthread_self(), SCHED_FIFO /*SCHED_RR*/, &sch)) { + //std::cout << "Failed to setschedparam: " << std::strerror(errno) << '\n'; + } +#endif + } + + +#ifndef SERVER + +//void irr_device_wait_egl (irr::IrrlichtDevice * device = nullptr); + +#endif + + +} \ No newline at end of file diff --git a/src/log_types.cpp b/src/log_types.cpp new file mode 100644 index 000000000..6d623234f --- /dev/null +++ b/src/log_types.cpp @@ -0,0 +1,101 @@ +/* +This file is part of Freeminer. + +Freeminer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Freeminer 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Freeminer. If not, see . +*/ + +#include "log_types.h" +#include "convert_json.h" +#include "irr_v3d.h" +#include "network/address.h" + +std::ostream &operator<<(std::ostream &s, const v2s16 &p) +{ + s << "(" << p.X << "," << p.Y << ")"; + return s; +} + +std::ostream &operator<<(std::ostream &s, const v2s32 &p) +{ + s << "(" << p.X << "," << p.Y << ")"; + return s; +} + +std::ostream &operator<<(std::ostream &s, const v2f &p) +{ + s << "(" << p.X << "," << p.Y << ")"; + return s; +} + +std::ostream &operator<<(std::ostream &s, const v3pos_t &p) +{ + s << "(" << p.X << "," << p.Y << "," << p.Z << ")"; + return s; +} + +std::ostream &operator<<(std::ostream &s, const v3f &p) +{ + s << "(" << p.X << "," << p.Y << "," << p.Z << ")"; + return s; +} + +#if USE_OPOS64 +std::ostream &operator<<(std::ostream &s, const v3opos_t &p) +{ + s << "(" << p.X << "," << p.Y << "," << p.Z << ")"; + return s; +} +#endif + +std::ostream &operator<<(std::ostream &s, const std::map &p) +{ + for (auto &i : p) + s << i.first << "=" << i.second << " "; + return s; +} + +std::ostream &operator<<(std::ostream &s, const irr::video::SColor &c) +{ + s << "c32(" << c.color << ": a=" << c.getAlpha() << ",r=" << c.getRed() + << ",g=" << c.getGreen() << ",b=" << c.getBlue() << ")"; + return s; +} + +std::ostream &operator<<(std::ostream &s, const irr::video::SColorf &c) +{ + s << "cf32(" + << "a=" << c.getAlpha() << ",r=" << c.getRed() << ",g=" << c.getGreen() + << ",b=" << c.getBlue() << ")"; + return s; +} + +#include "util/string.h" +std::ostream &operator<<(std::ostream &s, const std::wstring &w) +{ + s << wide_to_utf8(w); + return s; +} + +std::ostream &operator<<(std::ostream &s, const Json::Value &json) +{ + s << fastWriteJson(json); + return s; +} + +std::ostream &operator<<(std::ostream &s, const Address &addr) +{ + addr.print(s); + // s << addr.getPort(); + return s; +} \ No newline at end of file diff --git a/src/log_types.h b/src/log_types.h new file mode 100644 index 000000000..7a24f2c90 --- /dev/null +++ b/src/log_types.h @@ -0,0 +1,55 @@ +/* +This file is part of Freeminer. + +Freeminer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Freeminer 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Freeminer. If not, see . +*/ + +#ifndef LOG_TYPES_HEADER +#define LOG_TYPES_HEADER + +//#include "log.h" //for replacing log.h to log_types.h in includes + +#include "irr_v2d.h" +#include "irr_v3d.h" + +#include +std::ostream &operator<<(std::ostream &s, const v2s16 &p); +std::ostream &operator<<(std::ostream &s, const v2s32 &p); +std::ostream &operator<<(std::ostream &s, const v2f &p); +std::ostream &operator<<(std::ostream &s, const v3pos_t &p); +std::ostream &operator<<(std::ostream &s, const v3f &p); +#if USE_OPOS64 +std::ostream &operator<<(std::ostream &s, const v3opos_t &p); +#endif + +#include +std::ostream &operator<<(std::ostream &s, const irr::video::SColor &c); +std::ostream &operator<<(std::ostream &s, const irr::video::SColorf &c); + +#include +std::ostream &operator<<(std::ostream &s, const std::map &p); + +std::ostream &operator<<(std::ostream &s, const std::wstring &w); + +namespace Json +{ +class Value; +}; + +//std::ostream &operator<<(std::ostream &s, const Json::Value &json); + +class Address; +std::ostream &operator<<(std::ostream &s, const Address &addr); + +#endif \ No newline at end of file diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index d2e2f52e9..1a37eb995 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -2,6 +2,7 @@ set(common_network_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/address.cpp ${CMAKE_CURRENT_SOURCE_DIR}/connection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/connectionthreads.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/lan.cpp ${CMAKE_CURRENT_SOURCE_DIR}/networkpacket.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serverpackethandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serveropcodes.cpp diff --git a/src/network/address.h b/src/network/address.h index 2fbf419a7..6eecc3f79 100644 --- a/src/network/address.h +++ b/src/network/address.h @@ -46,6 +46,9 @@ class Address Address(u32 address, u16 port); Address(u8 a, u8 b, u8 c, u8 d, u16 port); Address(const IPv6AddressBytes *ipv6_bytes, u16 port); + Address(const in6_addr & addr, u16 port) { setAddress(addr); setPort(port); }; + Address(const sockaddr_in6 & sai) { m_address.ipv6 = sai.sin6_addr; m_addr_family = sai.sin6_family; m_port = ntohs(sai.sin6_port); }; + Address(const sockaddr_in & sai) { m_address.ipv4 = sai.sin_addr; m_addr_family = sai.sin_family; m_port = ntohs(sai.sin_port); }; bool operator==(const Address &address) const; bool operator!=(const Address &address) const { return !(*this == address); } @@ -74,6 +77,7 @@ class Address void setAddress(u32 address); void setAddress(u8 a, u8 b, u8 c, u8 d); void setAddress(const IPv6AddressBytes *ipv6_bytes); + void setAddress(const in6_addr & addr) { m_address.ipv6 = addr; m_addr_family = AF_INET6;} void setPort(u16 port); private: diff --git a/src/network/lan.cpp b/src/network/lan.cpp new file mode 100644 index 000000000..e7f163395 --- /dev/null +++ b/src/network/lan.cpp @@ -0,0 +1,296 @@ +/* +Copyright (C) 2016 proller +*/ + +/* +This file is part of Freeminer. + +Freeminer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Freeminer 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Freeminer. If not, see . +*/ + +#include "network/lan.h" +#include +#include "convert_json.h" +#include "socket.h" +#include "log.h" +#include "settings.h" +#include "version.h" +#include "networkprotocol.h" +#include "server/serverlist.h" +#include "debug.h" +#include "json/json.h" +#include "porting.h" +#include "threading/thread_vector.h" +#include "threading/concurrent_map.h" +#include "network/address.h" + +//copypaste from ../socket.cpp +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +// Without this some of the network functions are not found on mingw +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif +#include +#include +#include +#define LAST_SOCKET_ERR() WSAGetLastError() +typedef SOCKET socket_t; +typedef int socklen_t; +#else +#include +#include +#include +#include +#include +#include +#include + +#ifndef __ANDROID__ +#include +#define HAVE_IFADDRS 1 +#endif + +#define LAST_SOCKET_ERR() (errno) +typedef int socket_t; +#endif + +const static unsigned short int adv_port = 29998; +static std::string ask_str; + +lan_adv::lan_adv() : thread_vector("lan_adv") +{ +} + +void lan_adv::ask() +{ + reanimate(); + + if (ask_str.empty()) { + Json::Value j; + j["cmd"] = "ask"; + ask_str = fastWriteJson(j); + } + + send_string(ask_str); +} + +void lan_adv::send_string(const std::string &str) +{ + try { + sockaddr_in addr = {}; + addr.sin_family = AF_INET; + addr.sin_port = htons(adv_port); + addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); + UDPSocket socket_send(false); + int set_option_on = 1; + setsockopt(socket_send.GetHandle(), SOL_SOCKET, SO_BROADCAST, + (const char *)&set_option_on, sizeof(set_option_on)); + socket_send.Send(Address(addr), str.c_str(), str.size()); + } catch (const std::exception &e) { + verbosestream << "udp broadcast send4 fail " << e.what() << "\n"; + } + + std::vector scopes; +// todo: windows and android + +#if HAVE_IFADDRS + struct ifaddrs *ifaddr = nullptr, *ifa = nullptr; + if (getifaddrs(&ifaddr) < 0) { + } else { + for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr) + continue; + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + + auto sa = *((struct sockaddr_in6 *)ifa->ifa_addr); + if (sa.sin6_scope_id) + scopes.push_back(sa.sin6_scope_id); + + /*errorstream<<"in=" << ifa->ifa_name << " a="<ifa_addr)).serializeString()<<" ba=" << ifa->ifa_broadaddr <<" sc=" << sa.sin6_scope_id <<" fl=" << ifa->ifa_flags + //<< " bn=" << Address(*((struct sockaddr_in6*)ifa->ifa_broadaddr)).serializeString() + <<"\n"; */ + } + } + freeifaddrs(ifaddr); +#endif + + if (scopes.empty()) + scopes.push_back(0); + + struct addrinfo hints + { + }; + + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG; + struct addrinfo *result = nullptr; + if (!getaddrinfo("ff02::1", nullptr, &hints, &result)) { + for (auto info = result; info; info = info->ai_next) { + try { + sockaddr_in6 addr = *((struct sockaddr_in6 *)info->ai_addr); + addr.sin6_port = htons(adv_port); + UDPSocket socket_send(true); + int set_option_on = 1; + setsockopt(socket_send.GetHandle(), SOL_SOCKET, SO_BROADCAST, + (const char *)&set_option_on, sizeof(set_option_on)); + auto use_scopes = scopes; + if (addr.sin6_scope_id) { + use_scopes.clear(); + use_scopes.push_back(addr.sin6_scope_id); + } + for (auto &scope : use_scopes) { + addr.sin6_scope_id = scope; + socket_send.Send(Address(addr), str.c_str(), str.size()); + } + } catch (const std::exception &e) { + verbosestream << "udp broadcast send6 fail " << e.what() << "\n"; + } + } + freeaddrinfo(result); + } +} + +void lan_adv::serve(unsigned short port) +{ + server_port = port; + restart(); +} + +void *lan_adv::run() +{ + BEGIN_DEBUG_EXCEPTION_HANDLER; + + reg("LanAdv" + (server_port ? std::string("Server") : std::string("Client"))); + + UDPSocket socket_recv(true); + int set_option_off = 0, set_option_on = 1; + setsockopt(socket_recv.GetHandle(), SOL_SOCKET, SO_REUSEADDR, + (const char *)&set_option_on, sizeof(set_option_on)); +#ifdef SO_REUSEPORT + setsockopt(socket_recv.GetHandle(), SOL_SOCKET, SO_REUSEPORT, + (const char *)&set_option_on, sizeof(set_option_on)); +#endif + setsockopt(socket_recv.GetHandle(), SOL_SOCKET, SO_BROADCAST, + (const char *)&set_option_on, sizeof(set_option_on)); + setsockopt(socket_recv.GetHandle(), IPPROTO_IPV6, IPV6_V6ONLY, + (const char *)&set_option_off, sizeof(set_option_off)); + socket_recv.setTimeoutMs(200); + try { + socket_recv.Bind(Address(in6addr_any, adv_port)); + } catch (const std::exception &e) { + warningstream << m_name << ": cant bind ipv6 address [" << e.what() + << "], trying ipv4. " << std::endl; + try { + socket_recv.Bind(Address((u32)INADDR_ANY, adv_port)); + } catch (const std::exception &e) { + warningstream << m_name << ": cant bind ipv4 too [" << e.what() << "]" + << std::endl; + return nullptr; + } + } + std::unordered_map limiter; + const unsigned int packet_maxsize = 16384; + char buffer[packet_maxsize]; + Json::Reader reader; + std::string answer_str; + Json::Value server; + if (server_port) { + server["name"] = g_settings->get("server_name"); + server["description"] = g_settings->get("server_description"); + server["version"] = g_version_string; + bool strict_checking = g_settings->getBool("strict_protocol_version_checking"); + server["proto_min"] = + strict_checking ? LATEST_PROTOCOL_VERSION : SERVER_PROTOCOL_VERSION_MIN; + server["proto_max"] = + strict_checking ? LATEST_PROTOCOL_VERSION : SERVER_PROTOCOL_VERSION_MAX; + server["url"] = g_settings->get("server_url"); + server["creative"] = g_settings->getBool("creative_mode"); + server["damage"] = g_settings->getBool("enable_damage"); + server["password"] = g_settings->getBool("disallow_empty_password"); + server["pvp"] = g_settings->getBool("enable_pvp"); + server["port"] = server_port; + server["clients"] = clients_num.load(); + server["clients_max"] = g_settings->getU16("max_users"); + + send_string(fastWriteJson(server)); + } + + while (!stopRequested()) { + BEGIN_DEBUG_EXCEPTION_HANDLER; + Address addr; + int rlen = socket_recv.Receive(addr, buffer, packet_maxsize); + if (rlen <= 0) + continue; + Json::Value p; + if (!reader.parse(std::string(buffer, rlen), p)) { + //errorstream << "cant parse "<< s << "\n"; + continue; + } + auto addr_str = addr.serializeString(); + auto now = porting::getTimeMs(); + //errorstream << " a=" << addr.serializeString() << " : " << addr.getPort() << " l=" << rlen << " b=" << p << " ; server=" << server_port << "\n"; + + if (server_port) { + if (p["cmd"] == "ask" && limiter[addr_str] < now) { + (clients_num.load() ? infostream : actionstream) + << "lan: want play " << addr_str << std::endl; + + server["clients"] = clients_num.load(); + answer_str = fastWriteJson(server); + + limiter[addr_str] = now + 3000; + UDPSocket socket_send(true); + addr.setPort(adv_port); + socket_send.Send(addr, answer_str.c_str(), answer_str.size()); + } + } else { + if (p["cmd"] == "ask") { + actionstream << "lan: want play " << addr_str << std::endl; + } + if (p["port"].isInt()) { + p["address"] = addr_str; + auto key = addr_str + ":" + p["port"].asString(); + if (p["cmd"].asString() == "shutdown") { + //infostream << "server shutdown " << key << "\n"; + collected.erase(key); + fresh = true; + } else { + if (!collected.count(key)) + actionstream << "lan server start " << key << "\n"; + collected.insert_or_assign(key, p); + fresh = true; + } + } + + //errorstream<<" current list: ";for (auto & i : collected) {errorstream<< i.first <<" ; ";}errorstream< + +This file is part of Freeminer. + +Freeminer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Freeminer 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Freeminer. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include "threading/thread_vector.h" +#include "threading/concurrent_map.h" + + +class lan_adv : public thread_vector +{ +public: + void *run(); + + lan_adv(); + void ask(); + void send_string(const std::string &str); + + void serve(unsigned short port); + + concurrent_map collected; + std::atomic_bool fresh; + std::atomic_int clients_num; + +private: + unsigned short server_port = 0; +}; \ No newline at end of file diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index a5913e807..6a637f7d7 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "convert_json.h" #include "content/content.h" #include "content/subgames.h" +#include "server/serverlist.h" #include "mapgen/mapgen.h" #include "settings.h" #include "client/client.h" @@ -40,6 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content/mod_configuration.h" #include "threading/mutex_auto_lock.h" #include "common/c_converter.h" +#include "json-forwards.h" /******************************************************************************/ std::string ModApiMainMenu::getTextData(lua_State *L, const std::string &name) @@ -1080,6 +1082,48 @@ int ModApiMainMenu::l_do_async_callback(lua_State *L) return 1; } +/******************************************************************************/ + +/* +ModApiMainMenu::l_ask_lan_servers, ModApiMainMenu::l_get_lan_servers +by proller , special thanks for letting us use them. +*/ + +int ModApiMainMenu::l_ask_lan_servers(lua_State *L) +{ + ServerList::lan_get(); + return 0; +} + +int ModApiMainMenu::l_get_lan_servers(lua_State *L) +{ + lua_newtable(L); + int top = lua_gettop(L); + unsigned int index = 1; + + for (const auto &server : ServerList::lan_adv_client.collected) { + lua_pushnumber(L, index); + + lua_newtable(L); + int top_lvl2 = lua_gettop(L); + + for (const auto &field_name : server.second.getMemberNames()) { + lua_pushstring(L, field_name.c_str()); + if (server.second[field_name].isString()) + lua_pushstring(L, server.second[field_name].asCString()); + else if (server.second[field_name].isConvertibleTo(Json::realValue)) + lua_pushnumber(L, server.second[field_name].asDouble()); + else + lua_pushnil(L); + lua_settable(L, top_lvl2); + } + + lua_settable(L, top); + ++index; + } + return 1; +} + /******************************************************************************/ void ModApiMainMenu::Initialize(lua_State *L, int top) { @@ -1132,6 +1176,8 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(open_dir); API_FCT(share_file); API_FCT(do_async_callback); + API_FCT(ask_lan_servers); + API_FCT(get_lan_servers); } /******************************************************************************/ @@ -1160,4 +1206,6 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top) API_FCT(get_max_supp_proto); API_FCT(get_language); API_FCT(gettext); + API_FCT(get_lan_servers); + API_FCT(ask_lan_servers); } diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index 5535d2170..38d7c50cc 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -60,6 +60,17 @@ class ModApiMainMenu: public ModApiBase */ static bool mayModifyPath(std::string path); + //lan + + /* + ModApiMainMenu::l_ask_lan_servers, ModApiMainMenu::l_get_lan_servers + by proller , special thanks for letting us use it. + */ + + static int l_ask_lan_servers(lua_State *L); + + static int l_get_lan_servers(lua_State *L); + //api calls static int l_start(lua_State *L); diff --git a/src/server.cpp b/src/server.cpp index 316f349b2..5fd50d5aa 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -75,6 +75,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gameparams.h" #include "particles.h" #include "gettext.h" +#include "network/lan.h" class ClientNotFoundException : public BaseException { @@ -561,6 +562,10 @@ void Server::start() // Start thread m_thread->start(); + if (!m_simple_singleplayer_mode && g_settings->getBool("serverlist_lan")) { + lan_adv_server.serve(m_bind_addr.getPort()); + }; + // ASCII art for the win! const char *art[] = { " __. __. __. ", @@ -778,6 +783,10 @@ void Server::AsyncRunStep(float dtime, bool initial_step) } #endif + if (!isSingleplayer() && g_settings->getBool("serverlist_lan")) { + lan_adv_server.clients_num = m_clients.getPlayerNames().size(); + }; + /* Check added and deleted active objects */ diff --git a/src/server.h b/src/server.h index 16c1ea4cc..3f4824830 100644 --- a/src/server.h +++ b/src/server.h @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "network/lan.h" #include "irr_v3d.h" #include "map.h" #include "hud.h" @@ -671,6 +672,9 @@ class Server : public con::PeerHandler, public MapEventReceiver, // The server mainly operates in this thread ServerThread *m_thread = nullptr; + // For local server discovery. + lan_adv lan_adv_server; + /* Client interface */ diff --git a/src/server/serverlist.cpp b/src/server/serverlist.cpp index e702ba73d..1165ae9da 100644 --- a/src/server/serverlist.cpp +++ b/src/server/serverlist.cpp @@ -28,9 +28,45 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "convert_json.h" #include "httpfetch.h" #include "server.h" +#include "network/lan.h" +#include "json/json.h" namespace ServerList { + +/* +Special thanks to proller for +letting us use the methods lan_get() and lan_fresh() +as well as the lan_adv class. +*/ + +static std::string ask_str; + +lan_adv lan_adv_client; + +void lan_get() { + if (!g_settings->getBool("serverlist_lan")) + return; + lan_adv_client.ask(); +} + +/* + if (ask_str.empty()) { + Json::Value j; + j["cmd"] = "ask"; + j["proto_min"] = Server::getProtocolVersionMin(); + j["proto_max"] = Server::getProtocolVersionMax(); + ask_str = fastWriteJson(j); + + }; + lan_adv_client.send_string(ask_str); +*/ + +bool lan_fresh() { + auto result = lan_adv_client.fresh.load(); + lan_adv_client.fresh = false; + return result; +} #if USE_CURL void sendAnnounce(AnnounceAction action, const u16 port, diff --git a/src/server/serverlist.h b/src/server/serverlist.h index 2e2b8590f..568337278 100644 --- a/src/server/serverlist.h +++ b/src/server/serverlist.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content/mods.h" #include "json-forwards.h" #include +#include "network/lan.h" #pragma once @@ -28,6 +29,15 @@ with this program; if not, write to the Free Software Foundation, Inc., namespace ServerList { + /* + Special thanks to proller for + letting us use the methods lan_get() and lan_fresh() + as well as the lan_adv class. + */ + extern lan_adv lan_adv_client; + void lan_get(); + bool lan_fresh(); + #if USE_CURL enum AnnounceAction {AA_START, AA_UPDATE, AA_DELETE}; void sendAnnounce(AnnounceAction, u16 port, diff --git a/src/threading/CMakeLists.txt b/src/threading/CMakeLists.txt index 8f86158be..25c7e955c 100644 --- a/src/threading/CMakeLists.txt +++ b/src/threading/CMakeLists.txt @@ -2,5 +2,7 @@ set(JTHREAD_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/event.cpp ${CMAKE_CURRENT_SOURCE_DIR}/thread.cpp ${CMAKE_CURRENT_SOURCE_DIR}/semaphore.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/lock.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/thread_vector.cpp PARENT_SCOPE) diff --git a/src/threading/concurrent_map.h b/src/threading/concurrent_map.h new file mode 100644 index 000000000..c07971d59 --- /dev/null +++ b/src/threading/concurrent_map.h @@ -0,0 +1,209 @@ +/* +Copyright (C) 2024 proller +*/ + +/* +This file is part of Freeminer. + +Freeminer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Freeminer 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Freeminer. If not, see . +*/ + +#pragma once + +#include + +#include "lock.h" + +template , + class Allocator = std::allocator>> +class concurrent_map_ : public std::map, public LOCKER +{ +public: + typedef typename std::map full_type; + typedef Key key_type; + typedef T mapped_type; + + mapped_type &operator[](const key_type &k) = delete; + mapped_type &operator[](key_type &&k) = delete; + + mapped_type nothing = {}; + + template + mapped_type& get(Args &&...args) + { + auto lock = LOCKER::lock_shared_rec(); + + //if (!full_type::contains(std::forward(args)...)) + if (full_type::find(std::forward(args)...) == full_type::end()) + return nothing; + + return full_type::operator[](std::forward(args)...); + } + + template + decltype(auto) at(Args &&...args) + { + auto lock = LOCKER::lock_shared_rec(); + return full_type::at(std::forward(args)...); + } + + template + decltype(auto) assign(Args &&...args) + { + auto lock = LOCKER::lock_unique_rec(); + return full_type::assign(std::forward(args)...); + } + + template + decltype(auto) insert(Args &&...args) + { + auto lock = LOCKER::lock_unique_rec(); + return full_type::insert(std::forward(args)...); + } + + template + decltype(auto) emplace(Args &&...args) + { + auto lock = LOCKER::lock_unique_rec(); + return full_type::emplace(std::forward(args)...); + } + + template + decltype(auto) emplace_try(Args &&...args) + { + auto lock = LOCKER::try_lock_unique_rec(); + if (!lock->owns_lock()) + return false; + return full_type::emplace(std::forward(args)...).second; + } + + template + decltype(auto) insert_or_assign(Args &&...args) + { + auto lock = LOCKER::lock_unique_rec(); + return full_type::insert_or_assign(std::forward(args)...); + } + + template + decltype(auto) empty(Args &&...args) const noexcept + { + auto lock = LOCKER::lock_shared_rec(); + return full_type::empty(std::forward(args)...); + } + + template + decltype(auto) size(Args &&...args) const + { + auto lock = LOCKER::lock_shared_rec(); + return full_type::size(std::forward(args)...); + } + + template + decltype(auto) count(Args &&...args) const + { + auto lock = LOCKER::lock_shared_rec(); + return full_type::count(std::forward(args)...); + } + + template + decltype(auto) contains(Args &&...args) const + { + auto lock = LOCKER::lock_shared_rec(); + return full_type::contains(std::forward(args)...); + } + + template + decltype(auto) find(Args &&...args) + { + auto lock = LOCKER::lock_shared_rec(); + return full_type::find(std::forward(args)...); + } + + template + decltype(auto) begin(Args &&...args) + { + auto lock = LOCKER::lock_shared_rec(); + return full_type::begin(std::forward(args)...); + } + + template + decltype(auto) rbegin(Args &&...args) + { + auto lock = LOCKER::lock_shared_rec(); + return full_type::rbegin(std::forward(args)...); + } + + template + decltype(auto) end(Args &&...args) + { + auto lock = LOCKER::lock_shared_rec(); + return full_type::end(std::forward(args)...); + } + + template + decltype(auto) rend(Args &&...args) + { + auto lock = LOCKER::lock_shared_rec(); + return full_type::rend(std::forward(args)...); + } + + + template + decltype(auto) erase(Args &&...args) + { + auto lock = LOCKER::lock_unique_rec(); + return full_type::erase(std::forward(args)...); + } + + template + decltype(auto) clear(Args &&...args) + { + auto lock = LOCKER::lock_unique_rec(); + return full_type::clear(std::forward(args)...); + } +}; + +template , + class Allocator = std::allocator>> +using concurrent_map = concurrent_map_, Key, T, Compare, Allocator>; + +template , class Allocator = std::allocator>> +using concurrent_shared_map = concurrent_map_; + +#if ENABLE_THREADS + +template , + class Allocator = std::allocator>> +using maybe_concurrent_map = concurrent_map; + +#else + +template , + class Allocator = std::allocator>> +class not_concurrent_map : public std::map, + public dummy_locker +{ +public: + typedef typename std::map full_type; + typedef Key key_type; + typedef T mapped_type; + + mapped_type &get(const key_type &k) { return full_type::operator[](k); } +}; + +template , + class Allocator = std::allocator>> +using maybe_concurrent_map = not_concurrent_map; + +#endif \ No newline at end of file diff --git a/src/threading/lock.cpp b/src/threading/lock.cpp new file mode 100644 index 000000000..d80055447 --- /dev/null +++ b/src/threading/lock.cpp @@ -0,0 +1,131 @@ +#include "lock.h" +#include "log.h" +#include "profiler.h" + +#if !defined(NDEBUG) && !defined(LOCK_PROFILE) +//#define LOCK_PROFILE 1 +#endif + +#if LOCK_PROFILE +#define SCOPE_PROFILE(a) ScopeProfiler scp___(g_profiler, "Lock: " a); +#else +#define SCOPE_PROFILE(a) +#endif + +template +recursive_lock::recursive_lock(MUTEX & mtx, std::atomic & thread_id_, bool try_lock): + thread_id(thread_id_) { + auto thread_me = std::hash()(std::this_thread::get_id()); + if(thread_me != thread_id) { + if (try_lock) { + SCOPE_PROFILE("try_lock"); + lock = new GUARD(mtx, try_to_lock); + if (lock->owns_lock()) { + thread_id = thread_me; + return; + } else { +#if LOCK_PROFILE + g_profiler->add("Lock: try_lock fail", 1); +#endif + //infostream<<"not locked "<<" thread="<add("Lock: recursive", 1); +#endif + } + lock = nullptr; +} + +template +recursive_lock::~recursive_lock() { + unlock(); +} + +template +bool recursive_lock::owns_lock() { + if (lock) + return lock; + auto thread_me = std::hash()(std::this_thread::get_id()); + return thread_id == thread_me; +} + +template +void recursive_lock::unlock() { + if(lock) { + thread_id = 0; + lock->unlock(); + delete lock; + lock = nullptr; + } +} + + +template +locker::locker() { + thread_id = 0; +} + +template +std::unique_ptr locker::lock_unique() { + return std::make_unique(mtx); +} + +template +std::unique_ptr locker::try_lock_unique() { + SCOPE_PROFILE("locker::try_lock_unique"); + return std::make_unique(mtx, std::try_to_lock); +} + +template +std::unique_ptr locker::lock_shared() const { + SCOPE_PROFILE("locker::lock_shared"); + return std::make_unique(mtx); +} + +template +std::unique_ptr locker::try_lock_shared() { + SCOPE_PROFILE("locker::try_lock_shared"); + return std::make_unique(mtx, std::try_to_lock); +} + +template +std::unique_ptr> locker::lock_unique_rec() const { + SCOPE_PROFILE("locker::lock_unique_rec"); + return std::make_unique(mtx, thread_id); +} + +template +std::unique_ptr> locker::try_lock_unique_rec() { + SCOPE_PROFILE("locker::try_lock_unique_rec"); + return std::make_unique(mtx, thread_id, true); +} + +template +std::unique_ptr> locker::lock_shared_rec() const { + SCOPE_PROFILE("locker::lock_shared_rec"); + return std::make_unique(mtx, thread_id); +} + +template +std::unique_ptr> locker::try_lock_shared_rec() { + SCOPE_PROFILE("locker::try_lock_shared_rec"); + return std::make_unique(mtx, thread_id, true); +} + + +template class recursive_lock>; +template class locker<>; +#if LOCK_TWO +template class recursive_lock; +template class recursive_lock, try_shared_mutex>; + +template class locker, std::shared_lock>; +#endif \ No newline at end of file diff --git a/src/threading/lock.h b/src/threading/lock.h new file mode 100644 index 000000000..a3cd43e29 --- /dev/null +++ b/src/threading/lock.h @@ -0,0 +1,164 @@ +/* +This file is part of Freeminer. + +Freeminer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Freeminer 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Freeminer. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include + +#include "../config.h" + +#ifdef _WIN32 + +//#include "../threading/mutex.h" +using use_mutex = std::mutex; +using try_shared_mutex = use_mutex; +using try_shared_lock = std::unique_lock; +using unique_lock = std::unique_lock; +const auto try_to_lock = std::try_to_lock; + +#else + +typedef std::mutex use_mutex; + + +#if USE_BOOST // not finished + +//#include +#include +//#include +typedef boost::shared_mutex try_shared_mutex; +typedef boost::shared_lock try_shared_lock; +typedef boost::unique_lock unique_lock; +const auto try_to_lock = boost::try_to_lock; +#define LOCK_TWO 1 + +#elif HAVE_SHARED_MUTEX +//#elif __cplusplus >= 201305L + +#include +using try_shared_mutex = std::shared_mutex; +using try_shared_lock = std::shared_lock; +using unique_lock = std::unique_lock; +const auto try_to_lock = std::try_to_lock; +#define LOCK_TWO 1 + +#else + +using try_shared_mutex = use_mutex; +using try_shared_lock = std::unique_lock ; +using unique_lock = std::unique_lock ; +const auto try_to_lock = std::try_to_lock; +#endif + +#endif + + +// http://stackoverflow.com/questions/4792449/c0x-has-no-semaphores-how-to-synchronize-threads +/* uncomment when need +#include +class semaphore { +private: + std::mutex mtx; + std::condition_variable cv; + int count; + +public: + semaphore(int count_ = 0):count(count_){;} + void notify() { + std::unique_lock lck(mtx); + ++count; + cv.notify_one(); + } + void wait() { + std::unique_lock lck(mtx); + while(count == 0){ + cv.wait(lck); + } + count--; + } +}; +*/ + +template +class recursive_lock { +public: + GUARD * lock; + std::atomic & thread_id; + recursive_lock(MUTEX & mtx, std::atomic & thread_id_, bool try_lock = false); + ~recursive_lock(); + bool owns_lock(); + void unlock(); +}; + +template , class shared_lock = std::unique_lock > +class locker { +public: + using lock_rec_shared = recursive_lock; + using lock_rec_unique = recursive_lock; + + mutable mutex mtx; + mutable std::atomic thread_id; + + locker(); + std::unique_ptr lock_unique(); + std::unique_ptr try_lock_unique(); + std::unique_ptr lock_shared() const; + std::unique_ptr try_lock_shared(); + std::unique_ptr lock_unique_rec() const; + std::unique_ptr try_lock_unique_rec(); + std::unique_ptr lock_shared_rec() const; + std::unique_ptr try_lock_shared_rec(); +}; + +using shared_locker = locker; + +class dummy_lock { +public: + ~dummy_lock() {}; //no unused variable warning + bool owns_lock() {return true;} + bool operator!() {return true;} + dummy_lock * operator->() {return this; } + void unlock() {}; +}; + +class dummy_locker { +public: + dummy_lock lock_unique() { return {}; }; + dummy_lock try_lock_unique() { return {}; }; + dummy_lock lock_shared() { return {}; }; + dummy_lock try_lock_shared() { return {}; }; + dummy_lock lock_unique_rec() { return {}; }; + dummy_lock try_lock_unique_rec() { return {}; }; + dummy_lock lock_shared_rec() { return {}; }; + dummy_lock try_lock_shared_rec() { return {}; }; +}; + + +#if ENABLE_THREADS + +using maybe_locker = locker<>; +using maybe_shared_locker = shared_locker; + +#else + +using maybe_locker = dummy_locker; +using maybe_shared_locker = dummy_locker; + +#endif \ No newline at end of file diff --git a/src/threading/thread_vector.cpp b/src/threading/thread_vector.cpp new file mode 100644 index 000000000..5d206f58c --- /dev/null +++ b/src/threading/thread_vector.cpp @@ -0,0 +1,118 @@ +#include "thread_vector.h" +#include "fm_porting.h" +#include "log.h" +#include "porting.h" + +thread_vector::thread_vector(const std::string &name, int priority) : + m_name(name), m_priority(priority) +{ + request_stop = false; +}; + +thread_vector::~thread_vector() +{ + join(); +}; + +void thread_vector::func() +{ + reg(); + run(); +}; + +void thread_vector::reg(const std::string &name, int priority) +{ + if (!name.empty()) + m_name = name; + + porting::setThreadName(m_name.c_str()); + g_logger.registerThread(m_name); + + if (priority) + m_priority = priority; + if (m_priority) + porting::setThreadPriority(m_priority); +}; + +void thread_vector::start(const size_t n) +{ +#if !NDEBUG + infostream << "start thread " << m_name << " n=" << n << std::endl; +#endif + request_stop = false; + for (size_t i = 0; i < n; ++i) { + workers.emplace_back(&thread_vector::func, this); + } +} + +void thread_vector::stop() +{ + request_stop = true; +} + +void thread_vector::join() +{ + stop(); + for (auto &worker : workers) { + try { + if (worker.joinable()) { + worker.join(); + } + } catch (...) { + } + } + workers.clear(); +} + +void thread_vector::restart(size_t n) +{ + join(); + start(n); +} +void thread_vector::reanimate(size_t n) +{ + if (workers.empty()) { + start(n); + } +} + +void thread_vector::sleep(const int seconds) +{ + for (int i = 0; i <= seconds; ++i) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + if (request_stop) { + return; + } + } +} + +// JThread compat: +bool thread_vector::stopRequested() +{ + return request_stop; +} +bool thread_vector::isRunning() +{ + return !workers.empty(); +} +void thread_vector::wait() +{ + join(); +}; +void thread_vector::kill() +{ + join(); +}; +void *thread_vector::run() +{ + return nullptr; +}; + +bool thread_vector::isCurrentThread() +{ + auto thread_me = std::hash()(std::this_thread::get_id()); + for (auto &worker : workers) + if (thread_me == std::hash()(worker.get_id())) + return true; + return false; +} diff --git a/src/threading/thread_vector.h b/src/threading/thread_vector.h new file mode 100644 index 000000000..8172dd7b6 --- /dev/null +++ b/src/threading/thread_vector.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include + +class thread_vector { +public: + std::vector workers; + std::atomic_bool request_stop; + + thread_vector(const std::string &name = "Unnamed", int priority = 0); + virtual ~thread_vector(); + + virtual void func(); + + void reg (const std::string &name = "", int priority = 0); + void start (const size_t n = 1); + void restart (const size_t n = 1); + void reanimate (const size_t n = 1); + void stop (); + void join (); + + void sleep(const int second); +// Thread compat: + + bool stopRequested(); + bool isRunning(); + void wait(); + void kill(); + virtual void * run() = 0; + bool isCurrentThread(); +protected: + std::string m_name; + int m_priority = 0; +}; \ No newline at end of file