From bdf16c97f6c7a64a3cb1e8d78e6c8c085f2f8d97 Mon Sep 17 00:00:00 2001 From: Tom Anderson Date: Fri, 9 Aug 2024 11:45:49 -0600 Subject: [PATCH] fix level-loading crashes When new levels are loaded the Hull and Level/Set menus are re-created from scratch. This would leave dangling pointers in nanogui::Screen (mDragWidget and mFocusPath). The "best" fix would probably be to replace all Widget pointers with shared_ptr. But this is a quick fix that gets the job done. I also added "/dbg rload X" which will load a new level every X seconds. This was useful for recreating this bug but also might be useful for discovering other random level-load issues in the future so I left it in. --- src/game/CAvaraGame.cpp | 11 +++++++++++ src/game/CAvaraGame.h | 1 + vendor/nanogui/nanogui/screen.h | 2 ++ vendor/nanogui/nanogui/widget.h | 13 +++++++++++-- vendor/nanogui/screen.cpp | 15 +++++++++++++-- vendor/nanogui/widget.cpp | 11 +++++++++++ 6 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/game/CAvaraGame.cpp b/src/game/CAvaraGame.cpp index f1a0436f..55143d95 100755 --- a/src/game/CAvaraGame.cpp +++ b/src/game/CAvaraGame.cpp @@ -142,6 +142,7 @@ void CAvaraGame::IAvaraGame(CAvaraApp *theApp) { statusRequest = kNoVehicleStatus; nextPingTime = 0; + nextLoadTime = 0; showNewHUD = gApplication ? gApplication->Get(kShowNewHUD) : false; // CalcGameRect(); @@ -843,6 +844,16 @@ bool CAvaraGame::GameTick() { nextPingTime = startTime + pingInterval; } + int randLoadPeriod = Debug::GetValue("rload"); // randomly load a level every `rload` seconds + if (randLoadPeriod > 0) { + if (startTime > nextLoadTime) { + auto p = CPlayerManagerImpl::LocalPlayer(); + auto *tui = itsApp->GetTui(); + tui->ExecuteMatchingCommand("/rand", p); + nextLoadTime = startTime + 1000*randLoadPeriod; + } + } + // Not playing? Nothing to do! if (statusRequest != kPlayingStatus) return false; diff --git a/src/game/CAvaraGame.h b/src/game/CAvaraGame.h index feeab9cc..8d6d3ac3 100644 --- a/src/game/CAvaraGame.h +++ b/src/game/CAvaraGame.h @@ -179,6 +179,7 @@ class CAvaraGame { uint32_t nextScheduledFrame; uint32_t nextPingTime; + uint32_t nextLoadTime; long lastFrameTime; Boolean canPreSend; diff --git a/vendor/nanogui/nanogui/screen.h b/vendor/nanogui/nanogui/screen.h index e7b58dfd..6cee1638 100644 --- a/vendor/nanogui/nanogui/screen.h +++ b/vendor/nanogui/nanogui/screen.h @@ -188,6 +188,8 @@ class NANOGUI_EXPORT Screen : public Widget { void moveWindowToFront(Window *window); void drawWidgets(); + virtual void removeNotifyParent(const Widget *w) override; + protected: SDL_Window *mSDLWindow; uint32_t mWindowID; diff --git a/vendor/nanogui/nanogui/widget.h b/vendor/nanogui/nanogui/widget.h index f36f491a..242d15fd 100755 --- a/vendor/nanogui/nanogui/widget.h +++ b/vendor/nanogui/nanogui/widget.h @@ -63,10 +63,16 @@ class NANOGUI_EXPORT Widget : public Object { /// Set the position relative to the parent widget void setPosition(const Vector2i &pos) { mPos = pos; } + /// return position of parent or zeros if no parent + Vector2i parentPosition() const { + static Vector2i zeroPos = {}; + return mParent ? + (mParent->absolutePosition()) : zeroPos; + } + /// Return the absolute position on screen Vector2i absolutePosition() const { - return mParent ? - (parent()->absolutePosition() + mPos) : mPos; + return parentPosition() + mPos; } /// Return the size of the widget @@ -148,6 +154,9 @@ class NANOGUI_EXPORT Widget : public Object { /// Remove a child widget by value void removeChild(const Widget *widget); + /// notify other widgets/parents + virtual void removeNotifyParent(const Widget *w); + /// Retrieves the child at the specific position const Widget* childAt(int index) const { return mChildren[index]; } diff --git a/vendor/nanogui/screen.cpp b/vendor/nanogui/screen.cpp index b500da0f..b0fa5fe6 100755 --- a/vendor/nanogui/screen.cpp +++ b/vendor/nanogui/screen.cpp @@ -444,7 +444,7 @@ bool Screen::cursorPosCallbackEvent(double x, double y) { } } else { ret = mDragWidget->mouseDragEvent( - p - mDragWidget->parent()->absolutePosition(), p - mMousePos, + p - mDragWidget->parentPosition(), p - mMousePos, mMouseState, mModifiers); } @@ -482,7 +482,7 @@ bool Screen::mouseButtonCallbackEvent(int button, int action, int modifiers) { if (mDragActive && action == SDL_RELEASED && dropWidget != mDragWidget) mDragWidget->mouseButtonEvent( - mMousePos - mDragWidget->parent()->absolutePosition(), button, + mMousePos - mDragWidget->parentPosition(), button, false, mModifiers); if (dropWidget != nullptr && dropWidget->cursor() != mCursor) { @@ -694,4 +694,15 @@ bool Screen::handleSDLEvent(SDL_Event &event) { return false; } + +void Screen::removeNotifyParent(const Widget *w) { + // remove widget from various places before deletion + if (w == mDragWidget) { + mDragWidget = nullptr; + mDragActive = false; + } + mFocusPath.erase(std::remove(mFocusPath.begin(), mFocusPath.end(), w), mFocusPath.end()); + Widget::removeNotifyParent(w); +} + NAMESPACE_END(nanogui) diff --git a/vendor/nanogui/widget.cpp b/vendor/nanogui/widget.cpp index 5553751d..e980afc4 100755 --- a/vendor/nanogui/widget.cpp +++ b/vendor/nanogui/widget.cpp @@ -160,10 +160,21 @@ void Widget::removeChild(const Widget *widget) { void Widget::removeChild(int index) { Widget *widget = mChildren[index]; + removeNotifyParent(widget); mChildren.erase(mChildren.begin() + index); widget->decRef(); } +// climb the tree and let everyone (Screen) know this object is being removed. +// ideally we would change everything to use shared_ptr and maybe some weak_ptr references... +// but, i'm lazy and this code should all get replaced soon (right?) +void Widget::removeNotifyParent(const Widget *w) { + if (mParent) { + mParent->removeNotifyParent(w); + } + return; +} + int Widget::childIndex(Widget *widget) const { auto it = std::find(mChildren.begin(), mChildren.end(), widget); if (it == mChildren.end())