Prevent stale pointers in UI widgets

pull/3236/head
Evil Eye 3 months ago
parent ac21cf3bb3
commit 481e63ffa7

@ -238,6 +238,11 @@ namespace MWLua
} }
void LuaManager::synchronizedUpdate() void LuaManager::synchronizedUpdate()
{
mLua.protectedCall([&](LuaUtil::LuaView&) { synchronizedUpdateUnsafe(); });
}
void LuaManager::synchronizedUpdateUnsafe()
{ {
if (mNewGameStarted) if (mNewGameStarted)
{ {

@ -170,6 +170,7 @@ namespace MWLua
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr,
std::optional<LuaUtil::ScriptIdsWithInitializationData> autoStartConf = std::nullopt); std::optional<LuaUtil::ScriptIdsWithInitializationData> autoStartConf = std::nullopt);
void reloadAllScriptsImpl(); void reloadAllScriptsImpl();
void synchronizedUpdateUnsafe();
bool mInitialized = false; bool mInitialized = false;
bool mGlobalScriptsStarted = false; bool mGlobalScriptsStarted = false;

@ -113,6 +113,7 @@ namespace LuaUi
ContentView content(LuaUtil::cast<sol::table>(contentObj)); ContentView content(LuaUtil::cast<sol::table>(contentObj));
result.resize(content.size()); result.resize(content.size());
size_t minSize = std::min(children.size(), content.size()); size_t minSize = std::min(children.size(), content.size());
std::vector<WidgetExtension*> toDestroy;
for (size_t i = 0; i < minSize; i++) for (size_t i = 0; i < minSize; i++)
{ {
WidgetExtension* ext = children[i]; WidgetExtension* ext = children[i];
@ -121,7 +122,7 @@ namespace LuaUi
{ {
WidgetExtension* root = pluckElementRoot(child, depth); WidgetExtension* root = pluckElementRoot(child, depth);
if (ext != root) if (ext != root)
destroyChild(ext); toDestroy.emplace_back(ext);
result[i] = root; result[i] = root;
} }
else else
@ -133,14 +134,12 @@ namespace LuaUi
} }
else else
{ {
destroyChild(ext); toDestroy.emplace_back(ext);
ext = createWidget(newLayout, false, depth); ext = createWidget(newLayout, false, depth);
} }
result[i] = ext; result[i] = ext;
} }
} }
for (size_t i = minSize; i < children.size(); i++)
destroyChild(children[i]);
for (size_t i = minSize; i < content.size(); i++) for (size_t i = minSize; i < content.size(); i++)
{ {
sol::object child = content.at(i); sol::object child = content.at(i);
@ -149,6 +148,11 @@ namespace LuaUi
else else
result[i] = createWidget(child.as<sol::table>(), false, depth); result[i] = createWidget(child.as<sol::table>(), false, depth);
} }
// Don't destroy anything until element creation has had a chance to throw
for (size_t i = minSize; i < children.size(); i++)
destroyChild(children[i]);
for (WidgetExtension* ext : toDestroy)
destroyChild(ext);
return result; return result;
} }
@ -217,7 +221,9 @@ namespace LuaUi
std::string setLayer(WidgetExtension* ext, const sol::table& layout) std::string setLayer(WidgetExtension* ext, const sol::table& layout)
{ {
MyGUI::ILayer* layerNode = ext->widget()->getLayer(); MyGUI::ILayer* layerNode = ext->widget()->getLayer();
std::string currentLayer = layerNode ? layerNode->getName() : std::string(); std::string_view currentLayer;
if (layerNode)
currentLayer = layerNode->getName();
std::string newLayer = layout.get_or(LayoutKeys::layer, std::string()); std::string newLayer = layout.get_or(LayoutKeys::layer, std::string());
if (!newLayer.empty() && !MyGUI::LayerManager::getInstance().isExist(newLayer)) if (!newLayer.empty() && !MyGUI::LayerManager::getInstance().isExist(newLayer))
throw std::logic_error(std::string("Layer ") + newLayer + " doesn't exist"); throw std::logic_error(std::string("Layer ") + newLayer + " doesn't exist");
@ -278,9 +284,20 @@ namespace LuaUi
WidgetExtension* parent = mRoot->getParent(); WidgetExtension* parent = mRoot->getParent();
auto children = parent->children(); auto children = parent->children();
auto it = std::find(children.begin(), children.end(), mRoot); auto it = std::find(children.begin(), children.end(), mRoot);
mRoot = createWidget(layout(), true, 0);
assert(it != children.end()); assert(it != children.end());
*it = mRoot; try
{
mRoot = createWidget(layout(), true, 0);
*it = mRoot;
}
catch (...)
{
// Remove mRoot from its parent's children even if we couldn't replace it
children.erase(it);
parent->setChildren(children);
mRoot = nullptr;
throw;
}
parent->setChildren(children); parent->setChildren(children);
mRoot->updateCoord(); mRoot->updateCoord();
} }
@ -300,6 +317,10 @@ namespace LuaUi
{ {
if (mRoot != nullptr) if (mRoot != nullptr)
{ {
// If someone decided to destroy an element used as another element's content, we need to detach it
// first so the parent doesn't end up holding a stale pointer
if (WidgetExtension* parent = mRoot->getParent())
parent->detachChildrenIf([&](WidgetExtension* child) { return child == mRoot; });
destroyRoot(mRoot); destroyRoot(mRoot);
mRoot = nullptr; mRoot = nullptr;
} }

@ -126,6 +126,7 @@ namespace LuaUi
{ {
mParent = nullptr; mParent = nullptr;
widget()->detachFromWidget(); widget()->detachFromWidget();
widget()->detachFromLayer();
} }
WidgetExtension* WidgetExtension::findDeep(std::string_view flagName) WidgetExtension* WidgetExtension::findDeep(std::string_view flagName)

@ -179,7 +179,7 @@ namespace LuaUi
void updateVisible(); void updateVisible();
void detachChildrenIf(auto&& predicate, std::vector<WidgetExtension*> children) void detachChildrenIf(auto&& predicate, std::vector<WidgetExtension*>& children)
{ {
for (auto it = children.begin(); it != children.end();) for (auto it = children.begin(); it != children.end();)
{ {

Loading…
Cancel
Save