12#include <unordered_set>
15 ecs(ecs), identity_manager(identity_manager), peer_role(
Role::
NONE)
19 rtc::InitLogger(rtc::LogLevel::Verbose);
23void NetworkManager::setup(std::weak_ptr<BoardManager> board_manager, std::weak_ptr<GameTableManager> gametable_manager)
32 [
this]() -> std::string
128 signalingServer = std::make_shared<SignalingServer>(shared_from_this());
130 signalingClient = std::make_shared<SignalingClient>(shared_from_this());
320 for (
auto& [peerId, link] :
peers)
322 if (link && link->isConnected())
333 return !
peers.empty();
338 for (
auto& [
id, link] :
peers)
339 if (link && link->isConnected())
347 unsigned short port = 0;
348 std::string password;
351 if (!password.empty())
394 return "runic:" + ip +
":" + std::to_string(
port) +
"?" + pwd;
399 return "runic:" + ip +
":" + std::to_string(
port) +
"?" + pwd;
404 return url +
"?" + pwd;
409 return "runic:" + customHost +
":" + std::to_string(
port) +
"?" + pwd;
425 const std::string prefix =
"runic:";
426 if (connection_string.rfind(prefix, 0) == 0)
427 connection_string.erase(0, prefix.size());
430 std::string left = connection_string;
431 if (
auto q = connection_string.find(
'?'); q != std::string::npos)
433 left = connection_string.substr(0, q);
434 password = connection_string.substr(q + 1);
444 if (
auto colon = left.find(
':'); colon != std::string::npos)
446 server = left.substr(0, colon);
449 port =
static_cast<unsigned short>(std::stoi(left.substr(colon + 1)));
473 ImGui::OpenPopup(
"Port Forwarding Failed");
476 if (ImGui::BeginPopupModal(
"Port Forwarding Failed", p_open, ImGuiWindowFlags_AlwaysAutoResize))
478 ImGui::Text(
"Automatic port forwarding failed. This is likely because your router does not\n"
479 "support UPnP or it is disabled. You can still host by choosing one of the\n"
480 "following three options:");
484 if (ImGui::BeginTabBar(
"PortOptions"))
488 if (ImGui::BeginTabItem(
"Enable UPnP"))
490 ImGui::Text(
"This is the easiest solution and will allow automatic port forwarding to work.");
492 ImGui::BulletText(
"Access your router's administration page. This is usually done by\n"
493 "typing its IP address (e.g., 192.168.1.1) in your browser.");
494 ImGui::BulletText(
"Log in with your router's credentials.");
495 ImGui::BulletText(
"Look for a setting named 'UPnP' or 'Universal Plug and Play' and enable it.");
496 ImGui::BulletText(
"Save the settings and restart your router if necessary.");
497 ImGui::BulletText(
"Try hosting again. The application should now automatically forward the port.");
502 if (ImGui::BeginTabItem(
"Manual Port Forwarding"))
504 ImGui::Text(
"This method always works but requires you to configure your router manually.");
506 ImGui::BulletText(
"Find your local IP address. Open Command Prompt and type 'ipconfig' to find it\n"
507 "(e.g., 192.168.1.100).");
508 ImGui::BulletText(
"Access your router's administration page.");
509 ImGui::BulletText(
"Look for a setting named 'Port Forwarding', 'Virtual Server', or 'NAT'.");
510 ImGui::BulletText(
"Create a new rule with the following details:");
512 ImGui::BulletText(
"Protocol: TCP");
513 ImGui::BulletText(
"Internal Port: [Your application's port, e.g., 8080]");
514 ImGui::BulletText(
"External Port: [The same port]");
515 ImGui::BulletText(
"Internal IP: [Your local IP from step 1]");
517 ImGui::BulletText(
"Save the rule and try hosting again.");
522 if (ImGui::BeginTabItem(
"Use a VPN (Hamachi)"))
524 ImGui::Text(
"A VPN creates a virtual local network, bypassing the need for port forwarding.");
526 ImGui::BulletText(
"Download and install a VPN client like Hamachi.");
527 ImGui::BulletText(
"Create a new network within the VPN client.");
528 ImGui::BulletText(
"Share the network name and password with your friends.");
529 ImGui::BulletText(
"Instruct your friends to join your network.");
530 ImGui::BulletText(
"On the host screen, you should be able to see your VPN IP address.\n"
531 "Use this IP to host your session.");
538 if (ImGui::Button(
"Close"))
541 ImGui::CloseCurrentPopup();
553 std::vector<std::string> toErase;
554 std::vector<std::shared_ptr<PeerLink>> toClose;
555 toErase.reserve(
peers.size());
556 toClose.reserve(
peers.size());
559 for (
auto const& [pid, link] :
peers)
561 const bool shouldRemove = (!link) || link->isClosedOrFailed();
564 toErase.push_back(pid);
566 toClose.emplace_back(link);
571 for (
auto const& pid : toErase)
575 for (
auto& link : toClose)
581 catch (
const std::exception& e)
593 return toErase.size();
599 std::shared_ptr<PeerLink> link;
600 if (
auto it =
peers.find(peerId); it !=
peers.end())
602 link = std::move(it->second);
617 catch (
const std::exception& e)
634 if (
auto it =
peers.find(peerId); it !=
peers.end())
636 auto link = std::make_shared<PeerLink>(peerId, weak_from_this());
637 peers.emplace(peerId, link);
646 const std::string sdp = std::string(desc);
652 if (desc.type() == rtc::Description::Type::Offer)
657 else if (desc.type() == rtc::Description::Type::Answer)
680 const uint64_t now =
nowMs();
681 const uint64_t kGraceMs = 10'000;
683 static std::unordered_map<std::string, uint64_t> firstDiscPeerAt;
684 static std::unordered_map<std::string, uint64_t> firstDiscWsAt;
687 for (
auto it =
peers.begin(); it !=
peers.end();)
689 const std::string& pid = it->first;
690 auto& link = it->second;
692 const bool connected = (link && link->isConnected());
695 firstDiscPeerAt.erase(pid);
700 uint64_t& t0 = firstDiscPeerAt[pid];
704 if (now - t0 >= kGraceMs)
714 it =
peers.erase(it);
715 firstDiscPeerAt.erase(pid);
725 for (
auto it = wsClients.begin(); it != wsClients.end();)
727 const std::string& cid = it->first;
728 auto& ws = it->second;
730 const bool open = (ws && ws->isOpen());
733 firstDiscWsAt.erase(cid);
738 uint64_t& t0 = firstDiscWsAt[cid];
742 if (now - t0 >= kGraceMs)
751 it = wsClients.erase(it);
752 firstDiscWsAt.erase(cid);
762 const std::string& uniqueId,
763 const std::string& username)
769 if (
auto it =
peers.find(peerId); it !=
peers.end() && it->second)
770 it->second->setDisplayName(username);
778 return u.value_or(peerId);
784 std::unordered_map<std::string, std::shared_ptr<PeerLink>> moved = std::move(
peers);
787 for (
auto& [pid, link] : moved)
795 catch (
const std::exception& e)
824 std::unordered_map<std::string, std::shared_ptr<PeerLink>> moved = std::move(
peers);
827 for (
auto& [pid, link] : moved)
835 catch (
const std::exception& e)
879 std::vector<std::string> ids;
880 ids.reserve(
peers.size());
881 for (
auto& [pid, link] :
peers)
883 if (link && link->isConnected())
893 std::vector<std::string> user_names;
894 user_names.reserve(
peers.size());
895 for (
auto& [pid, link] :
peers)
897 if (link && link->isConnected())
908 std::ostringstream os;
909 os <<
"[Identity Snapshot]\n";
920 os <<
"gm.uniqueId=" <<
getGMId()
925 for (
const auto& [peerId, link] :
peers)
927 os <<
"- peerId=" << peerId;
929 std::string uid, uname;
939 os <<
" uniqueId=" << uid;
941 os <<
" username=\"" << uname <<
"\"";
944 os <<
" link.display=\"" << link->displayName() <<
"\"";
952 os <<
"\n[Peer→Unique map]\n";
957 os <<
" " << pid <<
" -> " << *u <<
"\n";
965 drag_.erase(markerId);
1035 const std::string& userUniqueId,
1036 const std::string& oldUsername,
1037 const std::string& newUsername,
1038 bool reboundFlag)
const
1050 for (
auto& [pid, link] :
peers)
1059 const std::vector<uint8_t>& payload)
1061 auto it =
peers.find(peerId);
1062 if (it ==
peers.end() || !it->second)
1065 std::vector<uint8_t> frame;
1066 frame.reserve(1 + payload.size());
1068 frame.insert(frame.end(), payload.begin(), payload.end());
1076 const std::string text = j.dump();
1078 for (
auto& [pid, link] :
peers)
1087 auto it =
peers.find(peerId);
1088 if (it ==
peers.end() || !it->second)
1090 auto& link = it->second;
1091 if (!link->isConnected())
1097 const std::string text = j.dump();
1099 for (
auto& pid : targets)
1101 auto it =
peers.find(pid);
1102 if (it ==
peers.end() || !it->second)
1104 auto& link = it->second;
1105 if (!link->isConnected())
1117 while (off < b.size())
1211 while (off < b.size())
1236 const auto& b =
reinterpret_cast<const std::vector<unsigned char>&
>(raw);
1276 const auto& b =
reinterpret_cast<const std::vector<unsigned char>&
>(raw);
1301 std::vector<unsigned char> out;
1303 const auto* pos = marker.get<
Position>();
1308 auto& s =
drag_[
id->id];
1309 if (s.locallyProposedEpoch == 0 && s.epoch == 0)
1311 s.locallyProposedEpoch = s.epoch + 1;
1312 s.epoch = s.locallyProposedEpoch;
1314 const uint32_t epoch = (s.locallyProposedEpoch ? s.locallyProposedEpoch : s.epoch);
1315 const uint64_t ts =
nowMs();
1328 std::vector<unsigned char> out;
1330 const auto* mv = marker.get<
Moving>();
1334 auto& s =
drag_[
id->id];
1335 if (s.locallyProposedEpoch == 0 && s.epoch == 0)
1337 s.locallyProposedEpoch = s.epoch + 1;
1338 s.epoch = s.locallyProposedEpoch;
1340 const uint32_t epoch = (s.locallyProposedEpoch ? s.locallyProposedEpoch : s.epoch);
1341 const uint32_t seq = ++s.localSeq;
1342 const uint64_t ts =
nowMs();
1353 if (!mv->isDragging && marker.has<
Position>())
1355 const auto* pos = marker.get<
Position>();
1364 std::vector<unsigned char> b;
1368 const auto id = marker.get<
Identifier>()->
id;
1369 const auto siz = *marker.get<
Size>();
1398 const uint64_t markerId = marker.get<
Identifier>()->
id;
1401 auto& s =
drag_[markerId];
1402 if (s.locallyProposedEpoch == 0 && s.epoch == 0)
1404 s.locallyProposedEpoch = s.epoch + 1;
1405 s.epoch = s.locallyProposedEpoch;
1407 const uint32_t seq = ++s.localSeq;
1408 s.lastTxMs =
nowMs();
1414 for (
auto& pid : toPeerIds)
1416 if (
auto it =
peers.find(pid); it !=
peers.end() && it->second)
1417 it->second->sendMarkerMove(frame);
1430 if (!marker.is_valid() || !marker.has<
Identifier>() || !marker.has<
Moving>())
1452 auto it =
drag_.find(markerId);
1453 if (it ==
drag_.end())
1455 const auto& s = it->second;
1458 if (!s.closed && s.locallyDragging)
1462 return !s.closed && s.ownerPeerId ==
getMyPeerId();
1468 auto& s =
drag_[markerId];
1471 s.locallyDragging =
true;
1472 s.locallyProposedEpoch = (s.closed ? (s.epoch + 1) : s.epoch);
1475 s.epochOpenedMs =
nowMs();
1477 if (s.locallyProposedEpoch > s.epoch)
1478 s.epoch = s.locallyProposedEpoch;
1482 s.locallyDragging =
false;
1488 auto it =
drag_.find(markerId);
1489 return (it !=
drag_.end() && !it->second.closed);
1494 auto it =
drag_.find(markerId);
1495 if (it ==
drag_.end())
1497 auto& s = it->second;
1499 s.locallyDragging =
false;
1504 const std::vector<std::string>& toPeerIds)
1506 if (!marker.is_valid() || !marker.has<
Identifier>())
1515 if (!m.markerId || !m.dragEpoch)
1517 auto& s =
drag_[*m.markerId];
1520 if (*m.dragEpoch < s.epoch)
1522 if (*m.dragEpoch > s.epoch)
1524 s.epoch = *m.dragEpoch;
1527 s.ownerPeerId = m.fromPeerId;
1533 if (!s.ownerPeerId.empty() && s.ownerPeerId != m.fromPeerId)
1537 s.ownerPeerId = m.fromPeerId;
1538 if (s.locallyDragging)
1540 s.locallyDragging =
false;
1546 s.ownerPeerId = m.fromPeerId;
1551 if (m.seq && *m.seq <= s.lastSeq)
1557 if (s.locallyDragging)
1565 if (!m.markerId || !m.dragEpoch || !m.mov || !m.mov->isDragging)
1567 auto& s =
drag_[*m.markerId];
1569 if (*m.dragEpoch < s.epoch)
1571 if (*m.dragEpoch > s.epoch)
1573 s.epoch = *m.dragEpoch;
1576 s.ownerPeerId = m.fromPeerId;
1582 if (!s.ownerPeerId.empty() && s.ownerPeerId != m.fromPeerId)
1586 s.ownerPeerId = m.fromPeerId;
1587 if (s.locallyDragging)
1589 s.locallyDragging =
false;
1595 s.ownerPeerId = m.fromPeerId;
1599 if (m.seq && *m.seq <= s.lastSeq)
1609 if (!m.markerId || !m.dragEpoch || !m.mov || m.mov->isDragging)
1611 auto& s =
drag_[*m.markerId];
1613 if (*m.dragEpoch < s.epoch)
1615 if (*m.dragEpoch > s.epoch)
1617 s.epoch = *m.dragEpoch;
1620 s.ownerPeerId = m.fromPeerId;
1626 if (!s.ownerPeerId.empty() && s.ownerPeerId != m.fromPeerId)
1630 s.ownerPeerId = m.fromPeerId;
1631 if (s.locallyDragging)
1633 s.locallyDragging =
false;
1639 s.ownerPeerId = m.fromPeerId;
1641 if (m.seq && *m.seq < s.lastSeq)
1643 if (m.seq && *m.seq >= s.lastSeq)
1649 s.locallyDragging =
false;
1657 const std::vector<std::string>& toPeerIds)
1659 if (!marker.is_valid() || !marker.has<
Identifier>())
1662 const uint64_t mid = marker.get<
Identifier>()->
id;
1668 const std::vector<std::string>& toPeerIds)
1670 if (!fog.is_valid() || !fog.has<
Identifier>())
1678 const std::vector<std::string>& toPeerIds)
1680 if (!fog.is_valid() || !fog.has<
Identifier>())
1683 const uint64_t fid = fog.get<
Identifier>()->
id;
1705 if (is_file_only || !is_path_like)
1709 auto image_path_ = map_folder / tex->image_path;
1710 image_path = image_path_.string();
1713 std::vector<unsigned char> img = tex ?
readFileBytes(image_path) : std::vector<unsigned char>{};
1727 board.children([&](flecs::entity child)
1730 uint64_t bid = board.get<Identifier>()->id;
1731 sendMarker(bid, child, toPeerIds);
1732 Logger::instance().log(
"localtunnel", Logger::Level::Info,
"SentMarker");
1735 uint64_t bid = board.get<Identifier>()->id;
1736 sendFog(bid, child, toPeerIds);
1737 Logger::instance().log(
"localtunnel", Logger::Level::Info,
"SentFog");
1748 if (is_file_only || !is_path_like)
1752 auto image_path_ = marker_folder / tex->image_path;
1753 image_path = image_path_.string();
1756 std::vector<unsigned char> img = tex ?
readFileBytes(image_path) : std::vector<unsigned char>{};
1796 " id=" + std::to_string(
id) +
" bytes=" + std::to_string(img.size()));
1800 while (sent < img.size())
1802 const size_t chunk = std::min(
kChunk, img.size() - sent);
1803 const auto frame =
buildImageChunkFrame(
static_cast<uint8_t
>(kind),
id, sent, img.data() + sent, chunk);
1807 for (
auto& pid : toPeerIds)
1814 std::this_thread::sleep_for(std::chrono::milliseconds(
kPaceMillis));
1821 " id=" + std::to_string(
id) +
" total=" + std::to_string(img.size()));
1901 const std::vector<std::string>& toPeerIds)
1903 if (!board.is_valid() || !board.has<
Grid>())
1906 const auto* g = board.get<
Grid>();
1913 std::vector<unsigned char> b;
1958 p.boardId = bm.boardId;
1964 p.buf.resize(
static_cast<size_t>(total));
1983 p.boardId = mm.boardId;
1989 p.buf.resize(
static_cast<size_t>(total));
2016 imagesRx_.emplace(boardId, std::move(p));
2020 it->second.commitRequested =
true;
2026 if (off + 1 > b.size())
2034 if (off + 8 + 8 + 4 > b.size())
2043 if (len < 0 || off +
static_cast<size_t>(len) > b.size())
2046 "ImageChunk: invalid len id=" + std::to_string(
id) +
" len=" + std::to_string(len));
2054 "ImageChunk: unknown id=" + std::to_string(
id));
2055 off +=
static_cast<size_t>(len);
2059 auto& p = it->second;
2060 if (p.kind != kind || p.total == 0)
2063 "ImageChunk: kind/total mismatch id=" + std::to_string(
id));
2064 off +=
static_cast<size_t>(len);
2068 if (off64 +
static_cast<uint64_t
>(len) > p.total)
2071 "ImageChunk: out-of-bounds id=" + std::to_string(
id) +
2072 " off=" + std::to_string(off64) +
" len=" + std::to_string(len) +
2073 " total=" + std::to_string(p.total));
2074 off +=
static_cast<size_t>(len);
2078 std::memcpy(p.buf.data() + off64, b.data() + off,
static_cast<size_t>(len));
2079 off +=
static_cast<size_t>(len);
2080 p.received +=
static_cast<uint64_t
>(len);
2083 if ((p.received % (1u << 20)) <
static_cast<uint64_t
>(len))
2086 "ImageChunk: id=" + std::to_string(
id) +
" " +
2087 std::to_string(p.received) +
"/" + std::to_string(p.total));
2102 p.boardId = boardId;
2103 imagesRx_.emplace(markerId, std::move(p));
2108 it->second.boardId = boardId;
2111 it->second.commitRequested =
true;
2129 r.userUniqueId = uniqueId;
2132 r.rebound = rebound;
2135 "UserNameUpdate: tbl=" + std::to_string(r.tableId.value_or(0)) +
2136 " uid=" + uniqueId +
" old=" + oldU +
" new=" + newU);
2180 auto it =
peers.find(ev.peerId);
2181 if (it !=
peers.end() && it->second)
2183 it->second->setOpen(ev.label,
true);
2188 auto it =
peers.find(ev.peerId);
2189 if (it !=
peers.end() && it->second)
2191 it->second->setOpen(ev.label,
false);
2192 it->second->markBootstrapReset();
2198 auto it =
peers.find(ev.peerId);
2199 if (it !=
peers.end() && it->second)
2213 for (
auto& [pid, link] :
peers)
2215 if (link && link->allRequiredOpen() && !link->bootstrapSent())
2221 catch (
const std::exception& e)
2223 const std::string
msg = std::string(
"Error bootstrapping peer: ") + e.what();
2229 const char*
msg =
"Error bootstrapping peer: unknown exception";
2240 using clock = std::chrono::steady_clock;
2391 const std::vector<uint8_t>& b)
2394 auto first_non_ws = std::find_if(b.begin(), b.end(), [](uint8_t c)
2395 { return !std::isspace((unsigned char)c); });
2396 if (first_non_ws != b.end() && *first_non_ws ==
'{')
2400 std::string s(b.begin(), b.end());
2416 if (j.contains(
"tableId"))
2417 r.tableId = (uint64_t)j[
"tableId"].get<uint64_t>();
2418 if (j.contains(
"groupId"))
2419 r.threadId = (uint64_t)j[
"groupId"].get<uint64_t>();
2426 if (j.contains(
"name"))
2427 r.name = j[
"name"].get<std::string>();
2428 if (j.contains(
"participants") && j[
"participants"].is_array())
2430 std::set<std::string> parts;
2431 for (
auto& e : j[
"participants"])
2432 parts.insert(e.get<std::string>());
2433 r.participants = std::move(parts);
2444 if (j.contains(
"ts"))
2445 r.ts = (uint64_t)j[
"ts"].get<uint64_t>();
2446 if (j.contains(
"username"))
2447 r.name = j[
"username"].get<std::string>();
2448 if (j.contains(
"text"))
2449 r.text = j[
"text"].get<std::string>();
2459 catch (
const std::exception& e)
2470 while (off < b.size())
2504 bool expected =
false;
2511 using clock = std::chrono::steady_clock;
2512 auto lastFlush = clock::now();
2517 bool didWork =
false;
2557 throw std::exception(
"[NetworkManager] GametableManager Expired!!");
2561 throw std::exception(
"[NetworkManager] BoardManager Expired!!");
2564 auto it =
peers.find(peerId);
2565 if (it ==
peers.end() || !it->second)
2567 auto& link = it->second;
2568 if (link->bootstrapSent())
2570 if (gm->active_game_table.is_valid() && gm->active_game_table.has<
GameTable>())
2576 if (bm->isBoardActive())
2578 auto boardEnt = bm->getActiveBoard();
2579 if (boardEnt.is_valid() && boardEnt.has<
Board>())
2586 link->markBootstrapSent();
2594 auto& p = it->second;
2595 if (!p.commitRequested || !p.isComplete())
2604 m.boardId = p.boardMeta->boardId;
2605 m.boardMeta = p.boardMeta;
2612 m.boardId = p.boardId;
2613 m.markerMeta = p.markerMeta;
2615 m.bytes = std::move(p.buf);
2619 std::string(
"tryFinalizeImage: finalized ") +
2621 " id=" + std::to_string(
id));
2630 std::vector<unsigned char> b;
2640 std::vector<unsigned char> b;
2645 const auto pos = *fog.get<
Position>();
2646 const auto siz = *fog.get<
Size>();
2658 std::vector<unsigned char> b;
2667 std::vector<unsigned char> b;
2676 std::vector<unsigned char> b;
2681 auto bd = *board.get<
Board>();
2682 auto pan = *board.get<
Panning>();
2683 auto grid = *board.get<
Grid>();
2684 auto size = *board.get<
Size>();
2711 std::vector<unsigned char> b;
2715 auto pos = *marker.get<
Position>();
2716 auto sz = *marker.get<
Size>();
2718 auto mov = *marker.get<
Moving>();
2720 std::string name =
"marker_" + std::to_string(mid);
2737 std::vector<unsigned char> b;
2742 auto sz = *fog.get<
Size>();
2755 std::vector<unsigned char> b;
2762 b.insert(b.end(), data, data + len);
2768 std::vector<unsigned char> b;
2776 std::vector<unsigned char> b;
2785 auto it =
peers.find(peerId);
2786 if (it ==
peers.end() || !it->second)
2788 it->second->sendGame(bytes);
2793 for (
auto& pid : toPeerIds)
static void setLocalTunnelHandlers(std::function< std::string()> startHandler, std::function< void()> stopHandler)
static void setIdentityLogger(std::function< std::string()> identityLogger)
static Logger & instance()
void markDraggingLocal(uint64_t markerId, bool dragging)
bool shouldApplyMarkerMoveStateFinal(const msg::ReadyMessage &m)
void handleFogUpdate(const std::vector< uint8_t > &b, size_t &off)
unsigned short getPort() const
void onPeerLocalCandidate(const std::string &peerId, const rtc::Candidate &cand)
std::vector< uint8_t > buildFogCreateFrame(uint64_t boardId, const flecs::entity &fog)
std::string local_ip_address
bool disconnectAllPeers()
void setup(std::weak_ptr< BoardManager > board_manager, std::weak_ptr< GameTableManager > gametable_manager)
bool isMarkerBeingDragged(uint64_t markerId) const
void handleFogDelete(const std::vector< uint8_t > &b, size_t &off)
const std::string & getGMId() const
std::shared_ptr< ImGuiToaster > toaster_
bool removePeer(std::string peerId)
void broadcastMarker(uint64_t boardId, const flecs::entity &marker)
std::string getLocalTunnelURL()
void startServer(ConnectionType mode, unsigned short port, bool tryUpnp)
const std::string & getMyPeerId() const
void clearDragState(uint64_t markerId)
bool disconectFromPeers()
std::vector< std::string > getConnectedPeerIds() const
std::vector< unsigned char > buildFogUpdateFrame(uint64_t boardId, const flecs::entity &fog)
std::vector< uint8_t > buildCommitBoardFrame(uint64_t boardId)
std::string getNetworkInfo(ConnectionType type)
bool shouldApplyMarkerMoveStateStart(const msg::ReadyMessage &m)
void broadcastGameTable(const flecs::entity &gameTable)
void buildUserNameUpdate(std::vector< uint8_t > &out, uint64_t tableId, const std::string &userUniqueId, const std::string &oldUsername, const std::string &newUsername, bool reboundFlag) const
void broadcastMarkerMove(uint64_t boardId, const flecs::entity &marker)
std::unordered_map< uint64_t, PendingImage > imagesRx_
std::string external_ip_address
void broadcastMarkerUpdate(uint64_t boardId, const flecs::entity &marker)
void sendMarkerMove(uint64_t boardId, const flecs::entity &marker, const std::vector< std::string > &toPeerIds)
static bool tieBreakWins(const std::string &challengerPeerId, const std::string ¤tOwnerPeerId)
std::shared_ptr< IdentityManager > identity_manager
void startRawDrainWorker()
std::shared_ptr< SignalingClient > signalingClient
bool broadcastChatJson(const msg::Json &j)
void sendFogUpdate(uint64_t boardId, const flecs::entity &fog, const std::vector< std::string > &toPeerIds)
std::string decodingFromPeer_
void forceCloseDrag(uint64_t markerId)
void sendMarkerUpdate(uint64_t boardId, const flecs::entity &marker, const std::vector< std::string > &toPeerIds)
void broadcastMarkerDelete(uint64_t boardId, const flecs::entity &marker)
void decodeRawNotesBuffer(const std::string &fromPeer, const std::vector< uint8_t > &b)
std::shared_ptr< PeerLink > ensurePeerLink(const std::string &peerId)
void broadcastPeerDisconnect(const std::string &targetId)
std::weak_ptr< GameTableManager > gametable_manager
void pushStatusToast(const std::string &msg, ImGuiToaster::Level lvl, float durationSec=5.0f)
std::unordered_map< uint64_t, DragState > drag_
void upsertPeerIdentityWithUnique(const std::string &peerId, const std::string &uniqueId, const std::string &username)
void ShowPortForwardingHelpPopup(bool *p_open)
void handleCommitMarker(const std::vector< uint8_t > &b, size_t &off)
void handleCommitBoard(const std::vector< uint8_t > &b, size_t &off)
void handleMarkerMoveState(const std::vector< uint8_t > &b, size_t &off)
void handleUserNameUpdate(const std::vector< uint8_t > &b, size_t &off)
void sendMarkerMoveState(uint64_t boardId, const flecs::entity &marker, const std::vector< std::string > &toPeerIds)
std::string getMyUsername() const
void broadcastMarkerMoveState(uint64_t boardId, const flecs::entity &marker)
void decodeRawGameBuffer(const std::string &fromPeer, const std::vector< uint8_t > &b)
void handleGridUpdate(const std::vector< uint8_t > &b, size_t &off)
std::vector< uint8_t > buildCommitMarkerFrame(uint64_t boardId, uint64_t markerId)
void setPort(unsigned int port)
std::unordered_map< std::string, std::shared_ptr< PeerLink > > peers
std::vector< unsigned char > buildGridUpdateFrame(uint64_t boardId, const Grid &grid)
void handleImageChunk(const std::vector< uint8_t > &b, size_t &off)
void onPeerLocalDescription(const std::string &peerId, const rtc::Description &desc)
bool shouldApplyMarkerMove(const msg::ReadyMessage &m)
NetworkManager(flecs::world ecs, std::shared_ptr< IdentityManager > identity_manager)
void broadcastFogDelete(uint64_t boardId, const flecs::entity &fog)
void broadcastBoard(const flecs::entity &board)
std::string displayNameForPeer(const std::string &peerId) const
static constexpr int kPaceMillis
void handleBoardMeta(const std::vector< uint8_t > &b, size_t &off)
void handleFogCreate(const std::vector< uint8_t > &b, size_t &off)
void sendMarker(uint64_t boardId, const flecs::entity &marker, const std::vector< std::string > &toPeerIds)
bool amIDragging(uint64_t markerId) const
void sendGameTo(const std::string &peerId, const std::vector< unsigned char > &bytes)
void parseConnectionString(std::string connection_string, std::string &server, unsigned short &port, std::string &password)
void decodeRawChatBuffer(const std::string &fromPeer, const std::vector< uint8_t > &b)
void broadcastFogUpdate(uint64_t boardId, const flecs::entity &fog)
void handleGameTableSnapshot(const std::vector< uint8_t > &b, size_t &off)
std::string debugIdentitySnapshot() const
std::weak_ptr< BoardManager > board_manager
std::atomic< bool > rawWorkerRunning_
static constexpr int kPaceEveryN
void stopRawDrainWorker()
void sendBoard(const flecs::entity &board, const std::vector< std::string > &toPeerIds)
void sendGridUpdate(uint64_t boardId, const flecs::entity &board, const std::vector< std::string > &toPeerIds)
void sendFogDelete(uint64_t boardId, const flecs::entity &fog, const std::vector< std::string > &toPeerIds)
char network_password[124]
void broadcastUserNameUpdate(const std::vector< uint8_t > &payload)
bool sendImageChunks(msg::ImageOwnerKind kind, uint64_t id, const std::vector< unsigned char > &img, const std::vector< std::string > &toPeerIds)
std::string getNetworkPassword() const
std::vector< unsigned char > buildMarkerMoveFrame(uint64_t boardId, const flecs::entity &marker, uint32_t seq)
MessageQueue< msg::ReadyMessage > inboundGame_
std::vector< uint8_t > buildCreateMarkerFrame(uint64_t boardId, const flecs::entity &marker, uint64_t imageBytesTotal)
std::vector< std::string > getConnectedUsernames() const
void tryFinalizeImage(msg::ImageOwnerKind kind, uint64_t id)
int connectedPeerCount() const
std::shared_ptr< SignalingServer > signalingServer
std::string getExternalIPAddress()
std::vector< unsigned char > readFileBytes(const std::string &path)
MessageQueue< msg::NetEvent > events_
void bootstrapPeerIfReady(const std::string &peerId)
bool connectToPeer(const std::string &connectionString)
void handleMarkerUpdate(const std::vector< uint8_t > &b, size_t &off)
std::vector< unsigned char > buildMarkerDeleteFrame(uint64_t boardId, uint64_t markerId)
std::string getLocalIPAddress()
static bool hasUrlScheme(const std::string &s)
void broadcastFog(uint64_t boardId, const flecs::entity &fog)
void sendFog(uint64_t boardId, const flecs::entity &fog, const std::vector< std::string > &toPeerIds)
std::vector< unsigned char > buildMarkerUpdateFrame(uint64_t boardId, const flecs::entity &marker)
std::string getMyUniqueId() const
const std::string & getCustomHost() const
std::size_t removeDisconnectedPeers()
void setNetworkPassword(const char *password)
void drainInboundRaw(int maxPerTick)
std::vector< uint8_t > buildSnapshotGameTableFrame(uint64_t gameTableId, const std::string &name)
std::atomic< bool > rawWorkerStop_
void handleMarkerMeta(const std::vector< uint8_t > &b, size_t &off)
std::vector< uint8_t > buildImageChunkFrame(uint8_t ownerKind, uint64_t id, uint64_t offset, const uint8_t *data, size_t len)
void sendUserNameUpdateTo(const std::string &peerId, const std::vector< uint8_t > &payload)
void decodeRawMarkerMoveBuffer(const std::string &fromPeer, const std::vector< uint8_t > &b)
std::vector< unsigned char > buildFogDeleteFrame(uint64_t boardId, uint64_t fogId)
std::vector< unsigned char > buildMarkerMoveStateFrame(uint64_t boardId, const flecs::entity &marker)
void sendGameTable(const flecs::entity &gameTable, const std::vector< std::string > &toPeerIds)
void broadcastGridUpdate(uint64_t boardId, const flecs::entity &board)
void broadcastGameFrame(const std::vector< unsigned char > &frame, const std::vector< std::string > &toPeerIds)
bool sendChatJsonTo(const std::string &peerId, const msg::Json &j)
static bool ensureRemaining(const std::vector< uint8_t > &b, size_t off, size_t need)
static constexpr size_t kChunk
std::vector< uint8_t > buildSnapshotBoardFrame(const flecs::entity &board, uint64_t imageBytesTotal)
void sendMarkerDelete(uint64_t boardId, const flecs::entity &marker, const std::vector< std::string > &toPeerIds)
void handleMarkerDelete(const std::vector< uint8_t > &b, size_t &off)
void handleMarkerMove(const std::vector< uint8_t > &b, size_t &off)
MessageQueue< msg::InboundRaw > inboundRaw_
static void stopLocalTunnel()
static std::string httpGet(const std::wstring &host, const std::wstring &path)
static std::string getLocalTunnelUrl()
static std::string getLocalIPv4Address()
static std::string startLocalTunnel(const std::string &subdomainBase, int port)
static void safeCloseWebSocket(std::shared_ptr< rtc::WebSocket > &ws)
static fs::path getMarkersPath()
static bool isFilenameOnly(const std::string &s)
static bool isPathLike(const std::string &s)
static fs::path getMapsPath()
static Size deserializeSize(const std::vector< unsigned char > &buffer, size_t &offset)
static uint32_t deserializeUInt32(const std::vector< unsigned char > &buffer, size_t &offset)
static void serializeBool(std::vector< unsigned char > &buffer, bool value)
static Visibility deserializeVisibility(const std::vector< unsigned char > &buffer, size_t &offset)
static void serializeUInt8(std::vector< unsigned char > &buffer, uint8_t value)
static int deserializeInt(const std::vector< unsigned char > &buffer, size_t &offset)
static std::string deserializeString(const std::vector< unsigned char > &buffer, size_t &offset)
static Grid deserializeGrid(const std::vector< unsigned char > &buffer, size_t &offset)
static void serializeVec2(std::vector< unsigned char > &buffer, const glm::vec2 &vec)
static Position deserializePosition(const std::vector< unsigned char > &buffer, size_t &offset)
static void serializeUInt64(std::vector< unsigned char > &buffer, uint64_t value)
static Panning deserializePanning(const std::vector< unsigned char > &buffer, size_t &offset)
static void serializeSize(std::vector< unsigned char > &buffer, const Size *size)
static void serializeString(std::vector< unsigned char > &buffer, const std::string &str)
static void serializeFloat(std::vector< unsigned char > &buffer, float value)
static void serializePosition(std::vector< unsigned char > &buffer, const Position *position)
static uint8_t deserializeUInt8(const std::vector< unsigned char > &buffer, size_t &offset)
static MarkerComponent deserializeMarkerComponent(const std::vector< unsigned char > &buffer, size_t &offset)
static void serializeMarkerComponent(std::vector< unsigned char > &buffer, const MarkerComponent *marker_component)
static uint64_t deserializeUInt64(const std::vector< unsigned char > &buffer, size_t &offset)
static void serializeMoving(std::vector< unsigned char > &buffer, const Moving *moving)
static void serializeUInt32(std::vector< unsigned char > &buffer, uint32_t value)
static Moving deserializeMoving(const std::vector< unsigned char > &buffer, size_t &offset)
static void serializeInt(std::vector< unsigned char > &buffer, int value)
static void serializeGrid(std::vector< unsigned char > &buffer, const Grid *grid)
static void serializeVisibility(std::vector< unsigned char > &buffer, const Visibility *visibility)
static bool addPortMapping(const std::string &internalIp, unsigned short internalPort, unsigned short externalPort, const std::string &protocol, const std::string &description="", unsigned int duration=0)
constexpr std::string Chat
constexpr std::string Game
constexpr std::string Notes
constexpr std::string MarkerMove
bool DCTypeFromJson(std::string_view s, DCType &out)
Json makeOffer(const std::string &from, const std::string &to, const std::string &sdp, const std::string &username, const std::string &uniqueId, const std::string &broadcast=msg::value::False)
std::string getString(const Json &j, std::string k, std::string def={})
nlohmann::json makePeerDisconnect(const std::string &targetPeerId, bool broadcast=true)
Json makeCandidate(const std::string &from, const std::string &to, const std::string &cand, const std::string &broadcast=msg::value::False)
Json makeAnswer(const std::string &from, const std::string &to, const std::string &sdp, const std::string &username, const std::string &uniqueId, const std::string &broadcast=msg::value::False)
std::string DCtypeString(DCType type)