diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7b50a0c06..15f84461e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -247,10 +247,10 @@ set(mmapper_SRCS mainwindow/AudioVolumeSlider.h mainwindow/MapZoomSlider.cpp mainwindow/MapZoomSlider.h - mainwindow/UpdateDialog.cpp - mainwindow/UpdateDialog.h mainwindow/ThemeManager.cpp mainwindow/ThemeManager.h + mainwindow/UpdateDialog.cpp + mainwindow/UpdateDialog.h mainwindow/aboutdialog.cpp mainwindow/aboutdialog.h mainwindow/findroomsdlg.cpp @@ -592,9 +592,9 @@ set(mmapper_SRCS syntax/Value.h timers/CTimers.cpp timers/CTimers.h + timers/TimerDelegate.cpp timers/TimerModel.cpp timers/TimerModel.h - timers/TimerDelegate.cpp timers/TimerDelegate.h timers/TimerWidget.cpp timers/TimerWidget.h diff --git a/src/clock/mumeclockwidget.cpp b/src/clock/mumeclockwidget.cpp index 862b664a8..c4a1ad4b3 100644 --- a/src/clock/mumeclockwidget.cpp +++ b/src/clock/mumeclockwidget.cpp @@ -28,20 +28,22 @@ MumeClockWidget::MumeClockWidget(GameObserver &observer, MumeClock &clock, QWidg assert(testAttribute(Qt::WA_Hover)); observer.sig2_timeOfDayChanged.connect(m_lifetime, - [this](MumeTimeEnum time) { updateTime(time); }); + [this](const MumeTimeEnum time) { updateTime(time); }); observer.sig2_moonPhaseChanged.connect(m_lifetime, [this](MumeMoonPhaseEnum phase) { updateMoonPhase(phase); }); observer.sig2_moonVisibilityChanged.connect(m_lifetime, - [this](MumeMoonVisibilityEnum visibility) { + [this](const MumeMoonVisibilityEnum visibility) { updateMoonVisibility(visibility); }); - observer.sig2_seasonChanged.connect(m_lifetime, - [this](MumeSeasonEnum season) { updateSeason(season); }); - observer.sig2_weatherChanged.connect(m_lifetime, [this](PromptWeatherEnum weather) { + observer.sig2_seasonChanged.connect(m_lifetime, [this](const MumeSeasonEnum season) { + updateSeason(season); + }); + observer.sig2_weatherChanged.connect(m_lifetime, [this](const PromptWeatherEnum weather) { updateWeather(weather); }); - observer.sig2_fogChanged.connect(m_lifetime, [this](PromptFogEnum fog) { updateFog(fog); }); + observer.sig2_fogChanged.connect(m_lifetime, + [this](const PromptFogEnum fog) { updateFog(fog); }); observer.sig2_tick.connect(m_lifetime, [this](const MumeMoment &moment) { updateCountdown(moment); }); diff --git a/src/display/Connections.cpp b/src/display/Connections.cpp index 656420361..dea36f946 100644 --- a/src/display/Connections.cpp +++ b/src/display/Connections.cpp @@ -246,19 +246,6 @@ void ConnectionDrawer::drawRoomConnectionsAndDoors(const RoomHandle &room) if (sourceWithinBounds) { for (const RoomId outTargetId : sourceExit.getOutgoingSet()) { const auto &targetRoom = map.getRoomHandle(outTargetId); - if (!targetRoom) { - /* DEAD CODE */ - qWarning() << "Source room" << sourceId.asUint32() << "(" - << room.getName().toQString() - << ") dir=" << mmqt::toQStringUtf8(to_string_view(sourceDir)) - << "has target room with internal identifier" - << outTargetId.asUint32() << "which does not exist!"; - // This would cause a segfault in the old map scheme, but maps are now rigorously - // validated, so it should be impossible to have an exit to a room that does - // not exist. - assert(false); - continue; - } const auto &target_coord = targetRoom.getPosition(); const bool targetOutsideBounds = !m_bounds.contains(target_coord); @@ -300,19 +287,6 @@ void ConnectionDrawer::drawRoomConnectionsAndDoors(const RoomHandle &room) // incoming connections for (const RoomId inTargetId : sourceExit.getIncomingSet()) { const auto &targetRoom = map.getRoomHandle(inTargetId); - if (!targetRoom) { - /* DEAD CODE */ - qWarning() << "Source room" << sourceId.asUint32() << "(" - << room.getName().toQString() << ") fromdir=" - << mmqt::toQStringUtf8(to_string_view(opposite(sourceDir))) - << " has target room with internal identifier" << inTargetId.asUint32() - << "which does not exist!"; - // This would cause a segfault in the old map scheme, but maps are now rigorously - // validated, so it should be impossible to have an exit to a room that does - // not exist. - assert(false); - continue; - } // Only draw the connection if the target room is within the bounds const Coordinate target_coord = targetRoom.getPosition(); diff --git a/src/global/MakeQPointer.h b/src/global/MakeQPointer.h index d21d40920..076402e85 100644 --- a/src/global/MakeQPointer.h +++ b/src/global/MakeQPointer.h @@ -22,4 +22,12 @@ NODISCARD auto makeQPointer(Args &&...args) // transfer ownership, now that we've confirmed that the parent object owns it return QPointer{ptr.release()}; } + +template +NODISCARD QScopedPointer makeQScopedPointer(Args &&...args) +{ + static_assert(std::is_base_of_v); + auto ptr = std::make_unique(std::forward(args)...); + return QScopedPointer{ptr.release()}; +} } // namespace mmqt diff --git a/src/global/logging.h b/src/global/logging.h index 1a4faee7e..ce8d5c912 100644 --- a/src/global/logging.h +++ b/src/global/logging.h @@ -54,11 +54,11 @@ struct NODISCARD AbstractDebugOStream public: AbstractDebugOStream &operator<<(const char *const s) { - assert(s != nullptr); - auto &self = *this; - if (s != nullptr) { - self.writeUtf8(s); + if (s == nullptr) { + throw NullPointerException(); } + auto &self = *this; + self.writeUtf8(s); return self; } diff --git a/src/global/progresscounter.h b/src/global/progresscounter.h index d14cd4e95..bbce6b7ac 100644 --- a/src/global/progresscounter.h +++ b/src/global/progresscounter.h @@ -37,7 +37,7 @@ class NODISCARD ProgressCounter final if (expected == 0) { return 0; } - return std::clamp((100 * seen) / expected, 0, 99); + return std::clamp((100 * seen) / expected, 0, 100); } void reset(size_t expected_ = 0) { diff --git a/src/global/utils.cpp b/src/global/utils.cpp index 70efa053d..5766eaf5b 100644 --- a/src/global/utils.cpp +++ b/src/global/utils.cpp @@ -14,6 +14,45 @@ #include #include +namespace utils::details { + +static_assert(!cpp17::IsValidCircularSize_v<0, bool>); +static_assert(!cpp17::IsValidCircularSize_v<1, bool>); +static_assert(cpp17::IsValidCircularSize_v<2, bool>); +static_assert(!cpp17::IsValidCircularSize_v<3, bool>); + +static_assert(!cpp17::IsValidCircularSize_v<0, uint8_t>); +static_assert(!cpp17::IsValidCircularSize_v<1, uint8_t>); +static_assert(cpp17::IsValidCircularSize_v<2, uint8_t>); +static_assert(cpp17::IsValidCircularSize_v<3, uint8_t>); +static_assert(cpp17::IsValidCircularSize_v<255, uint8_t>); +static_assert(cpp17::IsValidCircularSize_v<256, uint8_t>); +static_assert(!cpp17::IsValidCircularSize_v<257, uint8_t>); + +static_assert(cpp17::IsValidCircularSize_v<65536, uint16_t>); +static_assert(!cpp17::IsValidCircularSize_v<65537, uint16_t>); + +static_assert(circular_increment<2, bool>(false) == true); +static_assert(circular_increment<2, bool>(true) == false); +static_assert(circular_decrement<2, bool>(false) == true); +static_assert(circular_decrement<2, bool>(true) == false); + +static_assert(circular_increment<255, uint8_t>(254) == 0); +static_assert(circular_increment<256, uint8_t>(255) == 0); + +static_assert(circular_increment<3, uint32_t>(0) == 1); +static_assert(circular_increment<3, uint32_t>(1) == 2); +static_assert(circular_increment<3, uint32_t>(2) == 0); +static_assert(circular_increment<3, uint32_t>(3) == 1); // doesn't throw; just wraps. + +static_assert(circular_decrement<3, uint32_t>(0) == 2); +static_assert(circular_decrement<3, uint32_t>(1) == 0); +static_assert(circular_decrement<3, uint32_t>(2) == 1); +static_assert(circular_decrement<3, uint32_t>(3) == 2); // doesn't throw; just wraps. +static_assert(circular_decrement<3, uint32_t>(4) == 0); // doesn't throw; just wraps. + +} // namespace utils::details + namespace utils::details::tests { static_assert(!isBitMask()); diff --git a/src/global/utils.h b/src/global/utils.h index 6e3145c4d..4ae04ce68 100644 --- a/src/global/utils.h +++ b/src/global/utils.h @@ -167,6 +167,55 @@ NODISCARD bool isSet(const T src, const T bit) return (src & bit) != T{}; } +namespace details { +namespace cpp11 { +template +struct IsValidCircularSize + : std::bool_constant<(N > 1 and std::is_unsigned_v + and (N <= std::numeric_limits::max() + or (sizeof(T) < sizeof(size_t) + and N == static_cast(std::numeric_limits::max()) + 1)))> + // + {}; + +template<> +struct IsValidCircularSize<2, bool> : std::true_type +{}; + +} // namespace cpp11 + +namespace cpp17 { +template +inline constexpr bool IsValidCircularSize_v = cpp11::IsValidCircularSize::value; + +} // namespace cpp17 +} // namespace details + +template +NODISCARD constexpr T circular_increment(const T x) +{ + if constexpr (std::is_unsigned_v && details::cpp17::IsValidCircularSize_v) { + return static_cast((x + 1) % N); + } else if constexpr (std::is_same_v && N == 2) { + return !x; + } else { + static_assert(std::is_same_v); + std::abort(); + } +} +template +NODISCARD constexpr T circular_decrement(const T x) +{ + if constexpr (std::is_unsigned_v && details::cpp17::IsValidCircularSize_v) { + return static_cast((x + N - 1) % N); + } else if constexpr (std::is_same_v && N == 2) { + return !x; + } else { + static_assert(std::is_same_v); + std::abort(); + } +} + } // namespace utils template diff --git a/src/mainwindow/mainwindow.cpp b/src/mainwindow/mainwindow.cpp index f58393c27..5ae1c783f 100644 --- a/src/mainwindow/mainwindow.cpp +++ b/src/mainwindow/mainwindow.cpp @@ -122,10 +122,13 @@ MainWindow::MainWindow() addApplicationFont(); registerMetatypes(); - m_mapData = new MapData(this); - MapData &mapData = deref(m_mapData); + std::invoke([this] { + auto *const mapData = new MapData(this); + mapData->setObjectName("MapData"); + + m_mapData = mapData; + }); - m_mapData->setObjectName("MapData"); setCurrentFile(""); m_prespammedPath = new PrespammedPath(this); @@ -135,100 +138,144 @@ MainWindow::MainWindow() m_gameObserver = std::make_unique(); - m_mapWindow = new MapWindow(mapData, + m_mapWindow = new MapWindow(deref(m_mapData), deref(m_gameObserver), deref(m_prespammedPath), deref(m_groupManager), this); setCentralWidget(m_mapWindow); - m_pathMachine = new Mmapper2PathMachine(mapData, this); - m_pathMachine->setObjectName("Mmapper2PathMachine"); + std::invoke([this] { + auto *const pathMachine = new Mmapper2PathMachine(deref(m_mapData), this); + pathMachine->setObjectName("Mmapper2PathMachine"); + + m_pathMachine = pathMachine; + }); m_mediaLibrary = new MediaLibrary(this); - m_adventureTracker = new AdventureTracker(deref(m_gameObserver), this); m_audioManager = new AudioManager(deref(m_mediaLibrary), deref(m_gameObserver), this); // View -> Side Panels -> Log Panel - m_dockDialogLog = new QDockWidget(tr("Log Panel"), this); - m_dockDialogLog->setObjectName("DockWidgetLog"); - m_dockDialogLog->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea); - m_dockDialogLog->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable - | QDockWidget::DockWidgetClosable); - m_dockDialogLog->toggleViewAction()->setShortcut(tr("Ctrl+L")); - addDockWidget(Qt::BottomDockWidgetArea, m_dockDialogLog); - - logWindow = new QTextBrowser(m_dockDialogLog); - logWindow->setReadOnly(true); - logWindow->setObjectName("LogWindow"); - m_dockDialogLog->setWidget(logWindow); - m_dockDialogLog->hide(); + std::invoke([this] { + auto *const dock = new QDockWidget(tr("Log Panel"), this); + dock->setObjectName("DockWidgetLog"); + dock->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea); + dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable + | QDockWidget::DockWidgetClosable); + dock->toggleViewAction()->setShortcut(tr("Ctrl+L")); + addDockWidget(Qt::BottomDockWidgetArea, dock); + + auto *const logWindow = new QTextBrowser(m_dockDialogLog); + logWindow->setReadOnly(true); + logWindow->setObjectName("LogWindow"); + dock->setWidget(logWindow); + dock->hide(); + + m_dockDialogLog = dock; + m_logWindow = logWindow; + }); // View -> Side Panels -> Group Panel and Tools -> Group Manager - m_groupWidget = new GroupWidget(m_groupManager, m_mapData, this); - m_dockDialogGroup = new QDockWidget(tr("Group Panel"), this); - m_dockDialogGroup->setObjectName("DockWidgetGroup"); - m_dockDialogGroup->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea); - m_dockDialogGroup->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable - | QDockWidget::DockWidgetClosable); - addDockWidget(Qt::TopDockWidgetArea, m_dockDialogGroup); - m_dockDialogGroup->setWidget(m_groupWidget); - connect(m_groupWidget, &GroupWidget::sig_center, m_mapWindow, &MapWindow::slot_centerOnWorldPos); + std::invoke([this] { + auto *const w = new GroupWidget(m_groupManager, m_mapData, this); + auto *const dock = new QDockWidget(tr("Group Panel"), this); + dock->setObjectName("DockWidgetGroup"); + dock->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea); + dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable + | QDockWidget::DockWidgetClosable); + addDockWidget(Qt::TopDockWidgetArea, dock); + dock->setWidget(w); + connect(w, &GroupWidget::sig_center, m_mapWindow, &MapWindow::slot_centerOnWorldPos); + + m_groupWidget = w; + m_dockDialogGroup = dock; + }); // View -> Side Panels -> Room Panel (Mobs) - m_roomManager = new RoomManager(this); - m_roomManager->setObjectName("RoomManager"); - deref(m_gameObserver).sig2_sentToUserGmcp.connect(m_lifetime, [this](const GmcpMessage &gmcp) { - deref(m_roomManager).slot_parseGmcpInput(gmcp); + std::invoke([this] { + auto *const roomManager = new RoomManager(this); + roomManager->setObjectName("RoomManager"); + + deref(m_gameObserver) + .sig2_sentToUserGmcp.connect(m_lifetime, [this](const GmcpMessage &gmcp) { + deref(m_roomManager).slot_parseGmcpInput(gmcp); + }); + + m_roomManager = roomManager; + }); + + std::invoke([this] { + auto *const w = new RoomWidget(deref(m_roomManager), this); + auto *const dock = new QDockWidget(tr("Room Panel"), this); + dock->setObjectName("DockWidgetRoom"); + dock->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea); + dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable + | QDockWidget::DockWidgetClosable); + addDockWidget(Qt::BottomDockWidgetArea, dock); + dock->setWidget(w); + dock->hide(); + + m_roomWidget = w; + m_dockDialogRoom = dock; }); - m_roomWidget = new RoomWidget(deref(m_roomManager), this); - m_dockDialogRoom = new QDockWidget(tr("Room Panel"), this); - m_dockDialogRoom->setObjectName("DockWidgetRoom"); - m_dockDialogRoom->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea); - m_dockDialogRoom->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable - | QDockWidget::DockWidgetClosable); - addDockWidget(Qt::BottomDockWidgetArea, m_dockDialogRoom); - m_dockDialogRoom->setWidget(m_roomWidget); - m_dockDialogRoom->hide(); // Find Room Dialog - m_findRoomsDlg = new FindRoomsDlg(*m_mapData, this); - m_findRoomsDlg->setObjectName("FindRoomsDlg"); - - // View -> Side Panels -> Adventure Panel (Trophy XP, Achievements, Hints, etc) - m_dockDialogAdventure = new QDockWidget(tr("Adventure Panel"), this); - m_dockDialogAdventure->setObjectName("DockWidgetGameConsole"); - m_dockDialogAdventure->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea); - m_dockDialogAdventure->setFeatures(QDockWidget::DockWidgetClosable - | QDockWidget::DockWidgetFloatable - | QDockWidget::DockWidgetMovable); - addDockWidget(Qt::BottomDockWidgetArea, m_dockDialogAdventure); - m_adventureWidget = new AdventureWidget(deref(m_adventureTracker), this); - m_dockDialogAdventure->setWidget(m_adventureWidget); - m_dockDialogAdventure->hide(); + std::invoke([this] { + auto *const findRoomsDlg = new FindRoomsDlg(deref(m_mapData), this); + findRoomsDlg->setObjectName("FindRoomsDlg"); + + m_findRoomsDlg = findRoomsDlg; + }); + + std::invoke([this] { + auto *const adv = new AdventureTracker(deref(m_gameObserver), this); + auto *const w = new AdventureWidget(deref(adv), this); + // View -> Side Panels -> Adventure Panel (Trophy XP, Achievements, Hints, etc) + auto *const dock = new QDockWidget(tr("Adventure Panel"), this); + dock->setObjectName("DockWidgetGameConsole"); + dock->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea); + dock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable + | QDockWidget::DockWidgetMovable); + addDockWidget(Qt::BottomDockWidgetArea, dock); + dock->setWidget(w); + dock->hide(); + + m_adventureTracker = adv; + m_adventureWidget = w; + m_dockDialogAdventure = dock; + }); // View -> Side Panels -> Description / Area Panel - m_descriptionWidget = new DescriptionWidget(deref(m_mediaLibrary), this); - m_dockDialogDescription = new QDockWidget(tr("Description Panel"), this); + std::invoke([this] { + auto *const w = new DescriptionWidget(deref(m_mediaLibrary), this); + auto *const dock = new QDockWidget(tr("Description Panel"), this); + dock->setObjectName("DockWidgetDescription"); + dock->setAllowedAreas(Qt::AllDockWidgetAreas); + dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable + | QDockWidget::DockWidgetClosable); + addDockWidget(Qt::RightDockWidgetArea, dock); + dock->setWidget(w); + + m_descriptionWidget = w; + m_dockDialogDescription = dock; + }); m_hotkeyManager = std::make_unique(); - m_dockDialogDescription->setObjectName("DockWidgetDescription"); - m_dockDialogDescription->setAllowedAreas(Qt::AllDockWidgetAreas); - m_dockDialogDescription->setFeatures(QDockWidget::DockWidgetMovable - | QDockWidget::DockWidgetFloatable - | QDockWidget::DockWidgetClosable); - addDockWidget(Qt::RightDockWidgetArea, m_dockDialogDescription); - m_dockDialogDescription->setWidget(m_descriptionWidget); - - m_timers = new CTimers(this); - m_timerWidget = new TimerWidget(deref(m_timers), this); - m_dockDialogTimers = new QDockWidget(tr("Timers Panel"), this); - m_dockDialogTimers->setObjectName("DockWidgetTimers"); - m_dockDialogTimers->setFeatures(QDockWidget::DockWidgetMovable - | QDockWidget::DockWidgetFloatable - | QDockWidget::DockWidgetClosable); - m_dockDialogTimers->setWidget(m_timerWidget); - m_dockDialogTimers->hide(); + + std::invoke([this] { + auto *const timers = new CTimers(this); + auto *const w = new TimerWidget(deref(timers), this); + auto *const dock = new QDockWidget(tr("Timers Panel"), this); + dock->setObjectName("DockWidgetTimers"); + dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable + | QDockWidget::DockWidgetClosable); + dock->setWidget(w); + dock->hide(); + + m_timers = timers; + m_timerWidget = w; + m_dockDialogTimers = dock; + }); m_mumeClock = new MumeClock(getConfig().mumeClock.startEpoch, deref(m_gameObserver), this); if constexpr (!NO_UPDATER) { @@ -238,43 +285,54 @@ MainWindow::MainWindow() m_logger = new AutoLogger(this); // TODO move this connect() wiring into AutoLogger::ctor ? - GameObserver &observer = deref(m_gameObserver); - observer.sig2_connected.connect(m_lifetime, [this]() { - // - deref(m_logger).slot_onConnected(); - }); - observer.sig2_toggledEchoMode.connect(m_lifetime, [this](bool echo) { - deref(m_logger).slot_shouldLog(echo); - }); - observer.sig2_sentToMudString.connect(m_lifetime, [this](const QString &msg) { - deref(m_logger).slot_writeToLog(msg); - }); - observer.sig2_sentToUserString.connect(m_lifetime, [this](const QString &msg) { - deref(m_logger).slot_writeToLog(msg); + std::invoke([this] { + GameObserver &observer = deref(m_gameObserver); + observer.sig2_connected.connect(m_lifetime, [this]() { + // + deref(m_logger).slot_onConnected(); + }); + observer.sig2_toggledEchoMode.connect(m_lifetime, [this](bool echo) { + deref(m_logger).slot_shouldLog(echo); + }); + observer.sig2_sentToMudString.connect(m_lifetime, [this](const QString &msg) { + deref(m_logger).slot_writeToLog(msg); + }); + observer.sig2_sentToUserString.connect(m_lifetime, [this](const QString &msg) { + deref(m_logger).slot_writeToLog(msg); + }); }); - m_listener = new ConnectionListener(deref(m_mapData), - deref(m_pathMachine), - deref(m_prespammedPath), - deref(m_groupManager), - deref(m_mumeClock), - deref(m_timers), - deref(getCanvas()), - deref(m_gameObserver), - this); - // View -> Side Panels -> Client Panel - m_clientWidget = new ClientWidget(deref(m_listener), deref(m_hotkeyManager), this); - m_clientWidget->setObjectName("InternalMudClientWidget"); - m_dockDialogClient = new QDockWidget("Client Panel", this); - m_dockDialogClient->setObjectName("DockWidgetClient"); - m_dockDialogClient->setAllowedAreas(Qt::AllDockWidgetAreas); - m_dockDialogClient->setFeatures(QDockWidget::DockWidgetMovable - | QDockWidget::DockWidgetFloatable - | QDockWidget::DockWidgetClosable); - addDockWidget(Qt::RightDockWidgetArea, m_dockDialogTimers); - addDockWidget(Qt::LeftDockWidgetArea, m_dockDialogClient); - m_dockDialogClient->setWidget(m_clientWidget); + std::invoke([this] { + auto *const timers = m_dockDialogTimers; + std::ignore = deref(timers); + + auto *const listener = new ConnectionListener(deref(m_mapData), + deref(m_pathMachine), + deref(m_prespammedPath), + deref(m_groupManager), + deref(m_mumeClock), + deref(m_timers), + deref(getCanvas()), + deref(m_gameObserver), + this); + + auto *const w = new ClientWidget(deref(listener), deref(m_hotkeyManager), this); + w->setObjectName("InternalMudClientWidget"); + + auto *const dock = new QDockWidget("Client Panel", this); + dock->setObjectName("DockWidgetClient"); + dock->setAllowedAreas(Qt::AllDockWidgetAreas); + dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable + | QDockWidget::DockWidgetClosable); + addDockWidget(Qt::RightDockWidgetArea, timers); // caution: this is different + addDockWidget(Qt::LeftDockWidgetArea, dock); + dock->setWidget(w); + + m_listener = listener; + m_clientWidget = w; + m_dockDialogClient = dock; + }); createActions(); setupToolBars(); @@ -539,6 +597,7 @@ void MainWindow::wireConnections() void MainWindow::slot_log(const QString &mod, const QString &message) { + QTextBrowser *const logWindow = m_logWindow; logWindow->append(QString("[%1] %2").arg(mod, message)); logWindow->moveCursor(QTextCursor::MoveOperation::End); logWindow->ensureCursorVisible(); @@ -1465,15 +1524,16 @@ void MainWindow::setupStatusBar() deref(m_mumeClock), this)); - XPStatusWidget *xpStatus = new XPStatusWidget(*m_adventureTracker, statusBar(), this); + auto *const xpStatus = new XPStatusWidget(deref(m_adventureTracker), statusBar(), this); xpStatus->setToolTip("Click to toggle the Adventure Panel."); connect(xpStatus, &QPushButton::clicked, [this]() { - m_dockDialogAdventure->setVisible(!m_dockDialogAdventure->isVisible()); + auto &dock = deref(m_dockDialogAdventure); + dock.setVisible(!dock.isVisible()); }); statusBar()->insertPermanentWidget(0, xpStatus); - QLabel *pathmachineStatus = new QLabel(statusBar()); + auto *const pathmachineStatus = new QLabel(statusBar()); connect(m_pathMachine, &Mmapper2PathMachine::sig_state, pathmachineStatus, &QLabel::setText); statusBar()->insertPermanentWidget(0, pathmachineStatus); } @@ -1481,24 +1541,32 @@ void MainWindow::setupStatusBar() void MainWindow::slot_onPreferences() { if (m_configDialog == nullptr) { - m_configDialog = std::make_unique(this); + auto unique_configDialog = std::make_unique(this); + auto *const configDialog = unique_configDialog.get(); - connect(m_configDialog.get(), + std::ignore = deref(configDialog); + std::ignore = deref(m_mapWindow); + std::ignore = deref(m_groupManager); + + connect(configDialog, &ConfigDialog::sig_graphicsSettingsChanged, m_mapWindow, &MapWindow::slot_graphicsSettingsChanged); - connect(m_configDialog.get(), + connect(configDialog, &ConfigDialog::sig_groupSettingsChanged, m_groupManager, &Mmapper2Group::slot_groupSettingsChanged); - connect(m_configDialog.get(), &QDialog::finished, this, [this](MAYBE_UNUSED int result) { + connect(configDialog, &QDialog::finished, this, [this](MAYBE_UNUSED int result) { m_configDialog.reset(); }); + + m_configDialog = std::move(unique_configDialog); } - m_configDialog->show(); - m_configDialog->raise(); - m_configDialog->activateWindow(); + auto &configDialog = deref(m_configDialog); + configDialog.show(); + configDialog.raise(); + configDialog.activateWindow(); } void MainWindow::slot_newRoomSelection(const SigRoomSelection &rs) @@ -1525,14 +1593,15 @@ void MainWindow::slot_newConnectionSelection(ConnectionSelection *const cs) void MainWindow::slot_newInfomarkSelection(InfomarkSelection *const is) { - m_infoMarkSelection = (is != nullptr) ? is->shared_from_this() : nullptr; - infomarkActions.infomarkGroup->setEnabled(m_infoMarkSelection != nullptr); + const bool isNonNull = (is != nullptr); + m_infoMarkSelection = isNonNull ? is->shared_from_this() : nullptr; + deref(infomarkActions.infomarkGroup).setEnabled(isNonNull); - if (m_infoMarkSelection != nullptr) { - showStatusLong(QString("Selection: %1 mark%2") - .arg(m_infoMarkSelection->size()) - .arg((m_infoMarkSelection->size() != 1) ? "s" : "")); - if (m_infoMarkSelection->empty()) { + if (isNonNull) { + auto &ref = *is; + showStatusLong( + QString("Selection: %1 mark%2").arg(ref.size()).arg((ref.size() != 1) ? "s" : "")); + if (ref.empty()) { // Create a new infomark if its an empty selection slot_onEditInfomarkSelection(); } diff --git a/src/mainwindow/mainwindow.h b/src/mainwindow/mainwindow.h index 0229650c0..97bd3d84a 100644 --- a/src/mainwindow/mainwindow.h +++ b/src/mainwindow/mainwindow.h @@ -84,7 +84,7 @@ class NODISCARD_QOBJECT MainWindow final : public QMainWindow private: MapWindow *m_mapWindow = nullptr; - QTextBrowser *logWindow = nullptr; + QTextBrowser *m_logWindow = nullptr; QDockWidget *m_dockDialogRoom = nullptr; QDockWidget *m_dockDialogLog = nullptr; diff --git a/src/mainwindow/utils.cpp b/src/mainwindow/utils.cpp index 92e3f9532..f2dd9f8dc 100644 --- a/src/mainwindow/utils.cpp +++ b/src/mainwindow/utils.cpp @@ -6,25 +6,25 @@ #include "../display/mapcanvas.h" #include "../display/mapwindow.h" -CanvasDisabler::CanvasDisabler(MapWindow &in_window) - : window{in_window} +CanvasDisabler::CanvasDisabler(MapWindow &window) + : m_window{window} { - window.setCanvasEnabled(false); + m_window.setCanvasEnabled(false); } CanvasDisabler::~CanvasDisabler() { - window.setCanvasEnabled(true); - window.hideSplashImage(); + m_window.setCanvasEnabled(true); + m_window.hideSplashImage(); } -MapFrontendBlocker::MapFrontendBlocker(MapFrontend &in_data) - : data(in_data) +MapFrontendBlocker::MapFrontendBlocker(MapFrontend &data) + : m_data(data) { - data.block(); + m_data.block(); } MapFrontendBlocker::~MapFrontendBlocker() { - data.unblock(); + m_data.unblock(); } diff --git a/src/mainwindow/utils.h b/src/mainwindow/utils.h index 4c86ea6ce..21c8236d4 100644 --- a/src/mainwindow/utils.h +++ b/src/mainwindow/utils.h @@ -12,24 +12,22 @@ class MapWindow; class NODISCARD CanvasDisabler final { private: - MapWindow &window; + MapWindow &m_window; public: - explicit CanvasDisabler(MapWindow &in_window); + explicit CanvasDisabler(MapWindow &window); ~CanvasDisabler(); DELETE_CTORS_AND_ASSIGN_OPS(CanvasDisabler); }; class NODISCARD MapFrontendBlocker final { -public: - explicit MapFrontendBlocker(MapFrontend &in_data); - ~MapFrontendBlocker(); +private: + MapFrontend &m_data; public: MapFrontendBlocker() = delete; + explicit MapFrontendBlocker(MapFrontend &data); + ~MapFrontendBlocker(); DELETE_CTORS_AND_ASSIGN_OPS(MapFrontendBlocker); - -private: - MapFrontend &data; }; diff --git a/src/mapdata/shortestpath.cpp b/src/mapdata/shortestpath.cpp index 534ea654a..795bfae35 100644 --- a/src/mapdata/shortestpath.cpp +++ b/src/mapdata/shortestpath.cpp @@ -152,21 +152,6 @@ void MapData::shortestPathSearch(const RoomHandle &origin, const RoomId nextrId = e.getOutgoingSet().first(); const auto &nextr = map.getRoomHandle(nextrId); - - if (!nextr) { - /* DEAD CODE */ - qWarning() << "Source room" << thisr.getIdExternal().asUint32() << "(" - << thisr.getName().toQString() - << ") dir=" << mmqt::toQStringLatin1(to_string_view(dir)) - << "has target room with internal identifier" << nextrId.asUint32() - << "which does not exist!"; - qWarning() << mmqt::toQStringUtf8(thisr.toStdStringUtf8()); - // This would cause a segfault in the old map scheme, but maps are now rigorously - // validated, so it should be impossible to have an exit to a room that does - // not exist. - assert(false); - continue; - } if (visited.contains(nextr.getId())) { continue; } diff --git a/src/mapstorage/MapDestination.cpp b/src/mapstorage/MapDestination.cpp index 8810ee193..1d8fd7a25 100644 --- a/src/mapstorage/MapDestination.cpp +++ b/src/mapstorage/MapDestination.cpp @@ -4,16 +4,18 @@ #include "MapDestination.h" #include "../global/ConfigConsts-Computed.h" +#include "../global/utils.h" #include "filesaver.h" #include #include +#include #include #include #include -std::shared_ptr MapDestination::alloc(const QString fileName, SaveFormatEnum format) +std::shared_ptr MapDestination::alloc(QString fileName, SaveFormatEnum format) { std::shared_ptr fileSaver = nullptr; std::shared_ptr buffer = nullptr; @@ -51,19 +53,41 @@ std::shared_ptr MapDestination::alloc(const QString fileName, Sa } MapDestination::MapDestination(Badge, - const QString fileName, + QString fileName, std::shared_ptr fileSaver, std::shared_ptr buffer) : m_fileName(std::move(fileName)) , m_fileSaver(std::move(fileSaver)) , m_buffer(std::move(buffer)) -{} +{ + if (CURRENT_PLATFORM == PlatformEnum::Wasm) { + assert(isFileWasm()); + std::ignore = deref(m_buffer); + } else if (isFileNative()) { + std::ignore = deref(m_fileSaver); + } else { + assert(isDirectory()); + } +} + +MapDestination::~MapDestination() +{ + // finalize() calls FileSaver::close() which can throw on rename failure; + // catch here to avoid std::terminate() if this destructor runs during + // stack unwinding from a prior finalize() error. + try { + finalize(); + } catch (const std::exception &ex) { + qWarning() << "MapDestination::~MapDestination() caught exception:" << ex.what(); + } catch (...) { + qWarning() << "MapDestination::~MapDestination() caught unknown exception"; + } +} std::shared_ptr MapDestination::getIODevice() const { if (isFileNative()) { - assert(m_fileSaver); - return m_fileSaver->getSharedFile(); + return deref(m_fileSaver).getSharedFile(); } if (isFileWasm()) { return m_buffer; @@ -71,23 +95,21 @@ std::shared_ptr MapDestination::getIODevice() const return nullptr; } -const QByteArray MapDestination::getWasmBufferData() const +QByteArray MapDestination::getWasmBufferData() const { if (isFileWasm()) { - assert(m_buffer); - return m_buffer->data(); + return deref(m_buffer).data(); } return {}; } void MapDestination::finalize() { - if constexpr (CURRENT_PLATFORM == PlatformEnum::Wasm) { + if (CURRENT_PLATFORM == PlatformEnum::Wasm) { assert(isFileWasm()); - assert(m_buffer); + std::ignore = deref(m_buffer); } else if (isFileNative()) { - assert(m_fileSaver); - m_fileSaver->close(); + deref(m_fileSaver).close(); } else { assert(isDirectory()); } diff --git a/src/mapstorage/MapDestination.h b/src/mapstorage/MapDestination.h index 1600071ee..3ce5150bb 100644 --- a/src/mapstorage/MapDestination.h +++ b/src/mapstorage/MapDestination.h @@ -23,14 +23,15 @@ class NODISCARD MapDestination final : public std::enable_shared_from_this m_buffer; public: - NODISCARD static std::shared_ptr alloc(const QString fileName, + NODISCARD static std::shared_ptr alloc(QString fileName, SaveFormatEnum format) CAN_THROW; public: explicit MapDestination(Badge, - const QString fileName, + QString fileName, std::shared_ptr fileSaver, std::shared_ptr buffer); + ~MapDestination(); DELETE_CTORS(MapDestination); DELETE_COPY_ASSIGN_OP(MapDestination); @@ -45,7 +46,7 @@ class NODISCARD MapDestination final : public std::enable_shared_from_this getIODevice() const; - NODISCARD const QByteArray getWasmBufferData() const; + NODISCARD QByteArray getWasmBufferData() const; void finalize(); }; diff --git a/src/mapstorage/abstractmapstorage.cpp b/src/mapstorage/abstractmapstorage.cpp index 4aac7bbea..6bbd49e24 100644 --- a/src/mapstorage/abstractmapstorage.cpp +++ b/src/mapstorage/abstractmapstorage.cpp @@ -63,7 +63,7 @@ const QString &AbstractMapStorage::getFilename() const QIODevice &AbstractMapStorage::getDevice() const { if (m_data.saveDestination) { - if (m_data.destinationType == Data::Type::Directory) { + if (m_data.destinationType == Data::TypeEnum::Directory) { throw std::runtime_error("QIODevice not available for directory-based saves"); } auto device = m_data.saveDestination->getIODevice(); diff --git a/src/mapstorage/abstractmapstorage.h b/src/mapstorage/abstractmapstorage.h index c9ed68beb..c59a7e5c1 100644 --- a/src/mapstorage/abstractmapstorage.h +++ b/src/mapstorage/abstractmapstorage.h @@ -36,9 +36,9 @@ class NODISCARD_QOBJECT AbstractMapStorage : public QObject public: struct NODISCARD Data final { - enum class Type { File, Directory }; + enum class NODISCARD TypeEnum : uint8_t { File, Directory }; - Type destinationType = Type::File; + TypeEnum destinationType = TypeEnum::File; const std::shared_ptr progressCounter; std::shared_ptr loadSource; std::shared_ptr saveDestination; @@ -67,7 +67,7 @@ class NODISCARD_QOBJECT AbstractMapStorage : public QObject if (!saveDestination) { throw std::invalid_argument("dest"); } - destinationType = saveDestination->isDirectory() ? Type::Directory : Type::File; + destinationType = saveDestination->isDirectory() ? TypeEnum::Directory : TypeEnum::File; } }; diff --git a/src/mpi/remoteeditsession.h b/src/mpi/remoteeditsession.h index 204dce768..52e3d45ad 100644 --- a/src/mpi/remoteeditsession.h +++ b/src/mpi/remoteeditsession.h @@ -71,8 +71,8 @@ class NODISCARD_QOBJECT RemoteEditSession : public QObject friend class RemoteEditInternalSession; public: - explicit RemoteEditSession(const RemoteInternalId internalId, - const RemoteSessionId sessionId, + explicit RemoteEditSession(RemoteInternalId internalId, + RemoteSessionId sessionId, RemoteEdit *remoteEdit); public: @@ -105,8 +105,8 @@ class NODISCARD_QOBJECT RemoteEditInternalSession final : public RemoteEditSessi QPointer m_widget; public: - explicit RemoteEditInternalSession(const RemoteInternalId internalId, - const RemoteSessionId sessionId, + explicit RemoteEditInternalSession(RemoteInternalId internalId, + RemoteSessionId sessionId, const QString &title, const QString &body, RemoteEdit *remoteEdit); @@ -122,8 +122,8 @@ class NODISCARD_QOBJECT RemoteEditExternalSession final : public RemoteEditSessi QPointer m_process; public: - explicit RemoteEditExternalSession(const RemoteInternalId internalId, - const RemoteSessionId sessionId, + explicit RemoteEditExternalSession(RemoteInternalId internalId, + RemoteSessionId sessionId, const QString &title, const QString &body, RemoteEdit *remoteEdit); diff --git a/src/opengl/UboManager.h b/src/opengl/UboManager.h index 9640f5b60..fded6f10f 100644 --- a/src/opengl/UboManager.h +++ b/src/opengl/UboManager.h @@ -7,9 +7,9 @@ #include "../global/View.h" #include "../global/logging.h" #include "../global/utils.h" +#include "./legacy/Legacy.h" +#include "./legacy/VBO.h" #include "UboBlocks.h" -#include "legacy/Legacy.h" -#include "legacy/VBO.h" #include #include diff --git a/src/opengl/Weather.cpp b/src/opengl/Weather.cpp index e64ff19b3..e8c864cc0 100644 --- a/src/opengl/Weather.cpp +++ b/src/opengl/Weather.cpp @@ -9,9 +9,9 @@ #include "../global/Badge.h" #include "../map/coordinate.h" #include "../mapdata/mapdata.h" +#include "./legacy/Legacy.h" #include "OpenGL.h" #include "OpenGLTypes.h" -#include "legacy/Legacy.h" #include #include diff --git a/src/opengl/Weather.h b/src/opengl/Weather.h index 9e7b47bf7..a03bd2664 100644 --- a/src/opengl/Weather.h +++ b/src/opengl/Weather.h @@ -9,18 +9,17 @@ #include "../map/PromptFlags.h" #include "../map/coordinate.h" #include "../observer/gameobserver.h" +#include "./legacy/Legacy.h" +#include "./legacy/WeatherMeshes.h" #include "OpenGL.h" - -class FrameManager; #include "OpenGLTypes.h" -#include "legacy/Legacy.h" -#include "legacy/WeatherMeshes.h" #include +#include #undef TRANSPARENT // Bad dog, Microsoft; bad dog!!! -#include +class FrameManager; class MapData; struct MapCanvasTextures; diff --git a/src/opengl/legacy/Legacy.cpp b/src/opengl/legacy/Legacy.cpp index 36043cf0a..56103ec89 100644 --- a/src/opengl/legacy/Legacy.cpp +++ b/src/opengl/legacy/Legacy.cpp @@ -318,6 +318,7 @@ void Functions::cleanup() getSharedVbos().resetAll(); getSharedVaos().resetAll(); getTexLookup().clear(); + m_fbo.reset(); } ShaderPrograms &Functions::getShaderPrograms() diff --git a/src/parser/AbstractParser-Actions.cpp b/src/parser/AbstractParser-Actions.cpp index 419f70108..dfbb432b9 100644 --- a/src/parser/AbstractParser-Actions.cpp +++ b/src/parser/AbstractParser-Actions.cpp @@ -127,14 +127,14 @@ bool MumeXmlParserBase::evalActionMap(StringView line) auto range = map.equal_range(line.firstChar()); for (auto it = range.first; it != range.second; ++it) { - const std::unique_ptr &action = it->second; - action->match(line); + const ActionHolder &action = it->second; + action.match(line); } range = map.equal_range(0); for (auto it = range.first; it != range.second; ++it) { - const std::unique_ptr &action = it->second; - action->match(line); + const ActionHolder &action = it->second; + action.match(line); } return false; diff --git a/src/parser/Action.h b/src/parser/Action.h index a3d24f558..81438cd45 100644 --- a/src/parser/Action.h +++ b/src/parser/Action.h @@ -36,9 +36,9 @@ class NODISCARD StartsWithAction : public IAction const ActionCallback m_callback; public: - explicit StartsWithAction(std::string moved_str, const ActionCallback &callback) + explicit StartsWithAction(std::string moved_str, ActionCallback moved_callback) : m_match{std::move(moved_str)} - , m_callback{callback} + , m_callback{std::move(moved_callback)} {} private: @@ -52,9 +52,9 @@ class NODISCARD EndsWithAction : public IAction const ActionCallback m_callback; public: - explicit EndsWithAction(std::string moved_str, const ActionCallback &callback) + explicit EndsWithAction(std::string moved_str, ActionCallback moved_callback) : m_match{std::move(moved_str)} - , m_callback{callback} + , m_callback{std::move(moved_callback)} {} private: @@ -75,4 +75,21 @@ class NODISCARD RegexAction : public IAction }; using ActionHint = char; -using ActionRecordMap = std::unordered_multimap>; +struct NODISCARD ActionHolder final +{ +private: + std::unique_ptr m_ptr; + +public: + ActionHolder() noexcept = default; + ~ActionHolder() = default; + DEFAULT_MOVES_DELETE_COPIES(ActionHolder); + explicit ActionHolder(std::unique_ptr ptr) CAN_THROW : m_ptr{std::move(ptr)} + { + std::ignore = deref(m_ptr); // called for side effect + } + +public: + void match(const StringView line) const { deref(m_ptr).match(line); } +}; +using ActionRecordMap = std::unordered_multimap; diff --git a/src/parser/abstractparser.cpp b/src/parser/abstractparser.cpp index 217f083f3..6894ca725 100644 --- a/src/parser/abstractparser.cpp +++ b/src/parser/abstractparser.cpp @@ -452,8 +452,8 @@ ExitDirEnum AbstractParser::tryGetDir(StringView &view) auto word = view.takeFirstWord(); for (const ExitDirEnum dir : ALL_EXITS_NESWUD) { - auto lower = lowercaseDirection(dir); - if (lower != nullptr && Abbrev{lower, 1}.matches(word)) { + if (const char *const lower = lowercaseDirection(dir); + lower != nullptr && Abbrev{lower, 1}.matches(word)) { return dir; } } @@ -486,11 +486,13 @@ bool AbstractParser::setCommandPrefix(const char prefix) } // TODO: use something safer than a raw C string -void AbstractParser::showSyntax(const char *rest) +void AbstractParser::showSyntax(const char *const rest) { - assert(rest != nullptr); + if (rest == nullptr) { + throw std::invalid_argument("rest"); + } sendToUser(SendToUserSourceEnum::FromMMapper, - QString::asprintf("Usage: %c%s\n", getPrefixChar(), (rest != nullptr) ? rest : "")); + QString::asprintf("Usage: %c%s\n", getPrefixChar(), rest)); } void AbstractParser::doSearchCommand(const StringView view) diff --git a/src/proxy/connectionlistener.cpp b/src/proxy/connectionlistener.cpp index 11bcf9ec6..8a6f704f2 100644 --- a/src/proxy/connectionlistener.cpp +++ b/src/proxy/connectionlistener.cpp @@ -51,7 +51,21 @@ ConnectionListener::ConnectionListener(MapData &md, , m_gameOberver{go} {} -ConnectionListener::~ConnectionListener() = default; +ConnectionListener::~ConnectionListener() +{ + // Note: We're the owner of the proxy, so we're allowed to delete it. + // Deleting the proxy here somehow prevents the proxy's destructor from triggering + // use-after-free undefined behavior when the proxy tries to send a goodbye message + // to the user. + // + // Another option would be to make m_proxy an owning pointer (e.g. unique_ptr or QScopedPointer), + // and then make sure it's declared last so it'll be deleted first, and then make this dtor + // a default dtor again. + if (Proxy *const pProxy = m_proxy) { + MMLOG() << "ConnectionListener is deleting the proxy..."; + delete pProxy; + } +} void ConnectionListener::listen() { diff --git a/src/proxy/proxy.cpp b/src/proxy/proxy.cpp index cd93da563..e8f9611b9 100644 --- a/src/proxy/proxy.cpp +++ b/src/proxy/proxy.cpp @@ -86,42 +86,59 @@ struct NODISCARD Proxy::UserSocketOutputs Proxy::UserSocketOutputs::~UserSocketOutputs() = default; +using AbstractSocketGetter = std::function; + class NODISCARD Proxy::UserSocket final : public QObject { private: - std::unique_ptr &m_socket; + // Indirect getter so getSocket() re-evaluates m_userSocket on every call, allowing + // disconnectFromHost/sendToSocket to handle a null socket gracefully after teardown. + AbstractSocketGetter m_get_socket; Proxy::UserSocketOutputs &m_outputs; public: - explicit UserSocket(std::unique_ptr &socket, - QObject *parent, - UserSocketOutputs &outputs) + explicit UserSocket(AbstractSocketGetter get_socket, QObject *parent, UserSocketOutputs &outputs) : QObject(parent) - , m_socket{socket} + , m_get_socket{std::move(get_socket)} , m_outputs{outputs} { - QObject::connect(socket.get(), &AbstractSocket::sig_disconnected, this, [this]() { + QObject::connect(getSocket(), &AbstractSocket::sig_disconnected, this, [this]() { m_outputs.onDisconnected(); }); - QObject::connect(socket.get(), &QIODevice::readyRead, this, [this]() { + QObject::connect(getSocket(), &QIODevice::readyRead, this, [this]() { m_outputs.onReadyRead(); }); } ~UserSocket() final; +private: + NODISCARD AbstractSocket *getSocket() { return m_get_socket(); } + public: void disconnectFromHost() { - m_socket->flush(); - m_socket->disconnectFromHost(); + auto *const pSocket = getSocket(); + if (pSocket == nullptr) { + qWarning() << "tried to disconnect from a non-existent user socket"; + return; + } + auto &socket = deref(pSocket); + socket.flush(); + socket.disconnectFromHost(); } void sendToSocket(const TelnetIacBytes &bytes) { - if (!m_socket->isConnected()) { + auto *const pSocket = getSocket(); + if (pSocket == nullptr) { + qWarning() << "tried to send bytes to non-existent user socket"; + return; + } + auto &socket = deref(pSocket); + if (!socket.isConnected()) { qWarning() << "tried to send bytes to closed user socket"; return; } - m_socket->write(bytes.getQByteArray()); + socket.write(bytes.getQByteArray()); } }; @@ -277,7 +294,10 @@ void Proxy::allocUserSocket() auto &pipe = getPipeline(); auto &out = pipe.outputs.user.userSocketOutputs = std::make_unique( *this); - pipe.user.userSocket = std::make_unique(m_userSocket, this, deref(out)); + pipe.user.userSocket + = std::make_unique([this]() -> AbstractSocket * { return m_userSocket.get(); }, + this, + deref(out)); } void Proxy::allocMudSocket() diff --git a/src/proxy/proxy.h b/src/proxy/proxy.h index 580f9d3ac..04e7295db 100644 --- a/src/proxy/proxy.h +++ b/src/proxy/proxy.h @@ -326,7 +326,4 @@ class NODISCARD_QOBJECT Proxy final : public QObject { return deref(getPipeline().user.userTelnet); } - -private: - NODISCARD bool hasConnectedUserSocket() const; };