Merge branch 'intern' into 'master'

Draft: Cache Lua GameObjects to prevent duplicate table keys #8677

Closes #8677

See merge request OpenMW/openmw!4902
This commit is contained in:
Kuyondo 2025-09-20 19:43:16 +00:00
commit 302ac70d87
10 changed files with 130 additions and 38 deletions

View File

@ -63,7 +63,7 @@ add_openmw_dir (mwlua
context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings coremwscriptbindings context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings coremwscriptbindings
mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings dialoguebindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings dialoguebindings
postprocessingbindings stats recordstore debugbindings corebindings worldbindings worker landbindings magicbindings factionbindings postprocessingbindings stats recordstore debugbindings corebindings worldbindings worker landbindings magicbindings factionbindings
classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings markupbindings weatherbindings regionbindings classbindings intern itemdata inputprocessor animationbindings birthsignbindings racebindings markupbindings weatherbindings regionbindings
types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc
types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus
types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/potion types/ingredient types/misc types/repair types/armor types/light types/static

View File

@ -0,0 +1,84 @@
#include "object.hpp"
namespace MWLua
{
namespace
{
template <typename T>
inline constexpr bool is_gameObject = std::is_same_v<LObject, T> || std::is_same_v<GObject, T>;
template <typename T>
inline constexpr bool is_cell = std::is_same_v<LCell, T> || std::is_same_v<GCell, T>;
constexpr std::string_view gameObjects = "LUA_GAMEOBJECTS_REGISTRY";
constexpr std::string_view cells = "LUA_CELLS_REGISTRY";
inline constexpr uint64_t toUInt64(const ObjectId& id)
{
if (id.hasContentFile())
return id.mIndex | (static_cast<uint64_t>(id.mContentFile) << 24);
else
return id.mIndex | (static_cast<uint64_t>(-id.mContentFile - 1) << 32);
}
inline sol::table getRegistry(std::string_view key, sol::state_view lua)
{
if (sol::object reg = lua.registry()[key]; reg.valid()) // LUA_REGISTRYINDEX
return reg.as<sol::table>();
sol::table t(lua, sol::create);
t[sol::metatable_key] = lua.create_table_with("__mode", "v"); // weak table
lua.registry()[key] = t;
return t;
}
template <typename userT, typename ArgT>
sol::object makeOrGetImpl(ArgT arg, sol::state_view lua)
{
static_assert(std::is_constructible_v<userT, ArgT>);
sol::table registry;
sol::object key;
if constexpr (is_gameObject<userT>)
{
registry = getRegistry(gameObjects, lua);
if constexpr (std::is_same_v<ArgT, MWWorld::Ptr>)
key = sol::make_object(lua, toUInt64(getId(arg)));
else
key = sol::make_object(lua, toUInt64(arg));
}
else if constexpr (is_cell<userT>)
{
registry = getRegistry(cells, lua);
key = sol::make_object(lua, sol::make_light(arg));
}
// else compile error
sol::object existing = registry[key];
if (existing.valid())
return existing;
sol::object obj = sol::make_object(lua, userT(arg));
registry[key] = obj; // stored in weak table so garbage collector should work on this
return obj;
}
} // namespace
template <typename userT, typename ArgT>
sol::object makeOrGet(ArgT arg, sol::state_view lua)
{
return makeOrGetImpl<userT>(arg, lua);
}
// Game Objects
template sol::object MWLua::makeOrGet<LObject, MWWorld::Ptr>(MWWorld::Ptr, sol::state_view);
template sol::object MWLua::makeOrGet<LObject, ObjectId>(ObjectId, sol::state_view);
template sol::object MWLua::makeOrGet<GObject, MWWorld::Ptr>(MWWorld::Ptr, sol::state_view);
template sol::object MWLua::makeOrGet<GObject, ObjectId>(ObjectId, sol::state_view);
// Cells
template sol::object MWLua::makeOrGet<LCell, MWWorld::CellStore*>(MWWorld::CellStore*, sol::state_view);
template sol::object MWLua::makeOrGet<GCell, MWWorld::CellStore*>(MWWorld::CellStore*, sol::state_view);
}

View File

@ -58,7 +58,8 @@ namespace MWLua
= lua.new_usertype<SelfObject>("SelfObject", sol::base_classes, sol::bases<LObject, Object>()); = lua.new_usertype<SelfObject>("SelfObject", sol::base_classes, sol::bases<LObject, Object>());
selfAPI[sol::meta_function::to_string] selfAPI[sol::meta_function::to_string]
= [](SelfObject& self) { return "openmw.self[" + self.toString() + "]"; }; = [](SelfObject& self) { return "openmw.self[" + self.toString() + "]"; };
selfAPI["object"] = sol::readonly_property([](SelfObject& self) -> LObject { return LObject(self); }); selfAPI["object"] = sol::readonly_property(
[lua](SelfObject& self) -> sol::object { return makeOrGet<LObject>(self.ptr(), lua); });
selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; });
selfAPI["isActive"] = [](SelfObject& self) -> bool { return self.mIsActive; }; selfAPI["isActive"] = [](SelfObject& self) -> bool { return self.mIsActive; };
selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; }; selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; };

View File

@ -791,9 +791,8 @@ namespace MWLua
{ {
sol::object selected = sol::nil; sol::object selected = sol::nil;
if (!selectedPtr.isEmpty()) if (!selectedPtr.isEmpty())
mLua.protectedCall([&](LuaUtil::LuaView& view) { mLua.protectedCall(
selected = sol::make_object(view.sol(), LObject(getId(selectedPtr))); [&](LuaUtil::LuaView& view) { selected = makeOrGet<LObject>(selectedPtr, view.sol()); });
});
if (playerScripts->consoleCommand(consoleMode, command, selected)) if (playerScripts->consoleCommand(consoleMode, command, selected))
processed = true; processed = true;
} }

View File

@ -130,18 +130,18 @@ namespace MWLua
mwscript["isRunning"] = sol::readonly_property([](const MWScriptRef& s) { return s.isRunning(); }); mwscript["isRunning"] = sol::readonly_property([](const MWScriptRef& s) { return s.isRunning(); });
mwscript["recordId"] = sol::readonly_property([](const MWScriptRef& s) { return s.mId.serializeText(); }); mwscript["recordId"] = sol::readonly_property([](const MWScriptRef& s) { return s.mId.serializeText(); });
mwscript["variables"] = sol::readonly_property([](const MWScriptRef& s) { return MWScriptVariables{ s }; }); mwscript["variables"] = sol::readonly_property([](const MWScriptRef& s) { return MWScriptVariables{ s }; });
mwscript["object"] = sol::readonly_property([](const MWScriptRef& s) -> sol::optional<GObject> { mwscript["object"] = sol::readonly_property([lua](const MWScriptRef& s) -> sol::object {
if (s.mObj) if (s.mObj)
return s.mObj; return makeOrGet<GObject>(s.mObj->ptr(), lua);
const MWScript::GlobalScriptDesc* script const MWScript::GlobalScriptDesc* script
= MWBase::Environment::get().getScriptManager()->getGlobalScripts().getScriptIfPresent(s.mId); = MWBase::Environment::get().getScriptManager()->getGlobalScripts().getScriptIfPresent(s.mId);
if (!script) if (!script)
throw std::runtime_error("Invalid MWScriptRef"); throw std::runtime_error("Invalid MWScriptRef");
const MWWorld::Ptr* ptr = script->getPtrIfPresent(); const MWWorld::Ptr* ptr = script->getPtrIfPresent();
if (ptr && !ptr->isEmpty()) if (ptr && !ptr->isEmpty())
return GObject(*ptr); return makeOrGet<GObject>(*ptr, lua);
else else
return sol::nullopt; return sol::nil;
}); });
mwscript["player"] = sol::readonly_property( mwscript["player"] = sol::readonly_property(
[](const MWScriptRef&) { return GObject(MWBase::Environment::get().getWorld()->getPlayerPtr()); }); [](const MWScriptRef&) { return GObject(MWBase::Environment::get().getWorld()->getPlayerPtr()); });

View File

@ -35,6 +35,9 @@ namespace MWLua
} }
}; };
template <typename userT, typename ArgT>
sol::object makeOrGet(ArgT arg, sol::state_view lua);
// Used only in local scripts // Used only in local scripts
struct LCell struct LCell
{ {

View File

@ -170,11 +170,11 @@ namespace MWLua
listT[sol::meta_function::to_string] listT[sol::meta_function::to_string]
= [](const ListT& list) { return "{" + std::to_string(list.mIds->size()) + " objects}"; }; = [](const ListT& list) { return "{" + std::to_string(list.mIds->size()) + " objects}"; };
listT[sol::meta_function::length] = [](const ListT& list) { return list.mIds->size(); }; listT[sol::meta_function::length] = [](const ListT& list) { return list.mIds->size(); };
listT[sol::meta_function::index] = [](const ListT& list, size_t index) -> sol::optional<ObjectT> { listT[sol::meta_function::index] = [lua](const ListT& list, size_t index) -> sol::object {
if (index > 0 && index <= list.mIds->size()) if (index > 0 && index <= list.mIds->size())
return ObjectT((*list.mIds)[LuaUtil::fromLuaIndex(index)]); return makeOrGet<ObjectT>((*list.mIds)[LuaUtil::fromLuaIndex(index)], lua);
else else
return sol::nullopt; return sol::nil;
}; };
listT[sol::meta_function::pairs] = lua["ipairsForArray"].template get<sol::function>(); listT[sol::meta_function::pairs] = lua["ipairsForArray"].template get<sol::function>();
listT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get<sol::function>(); listT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get<sol::function>();
@ -278,6 +278,7 @@ namespace MWLua
template <class ObjectT> template <class ObjectT>
void addBasicBindings(sol::usertype<ObjectT>& objectT, const Context& context) void addBasicBindings(sol::usertype<ObjectT>& objectT, const Context& context)
{ {
sol::state_view lua = context.sol();
objectT["id"] = sol::readonly_property([](const ObjectT& o) -> std::string { return o.id().toString(); }); objectT["id"] = sol::readonly_property([](const ObjectT& o) -> std::string { return o.id().toString(); });
objectT["contentFile"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<std::string> { objectT["contentFile"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<std::string> {
int contentFileIndex = o.id().mContentFile; int contentFileIndex = o.id().mContentFile;
@ -296,13 +297,13 @@ namespace MWLua
else else
return ESM::RefId::stringRefId(globalVariable).serializeText(); return ESM::RefId::stringRefId(globalVariable).serializeText();
}); });
objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<Cell<ObjectT>> { objectT["cell"] = sol::readonly_property([lua](const ObjectT& o) -> sol::object {
const MWWorld::Ptr& ptr = o.ptr(); const MWWorld::Ptr& ptr = o.ptr();
MWWorld::WorldModel* wm = MWBase::Environment::get().getWorldModel(); MWWorld::WorldModel* wm = MWBase::Environment::get().getWorldModel();
if (ptr.isInCell() && ptr.getCell() != &wm->getDraftCell()) if (ptr.isInCell() && ptr.getCell() != &wm->getDraftCell())
return Cell<ObjectT>{ ptr.getCell() }; return makeOrGet<Cell<ObjectT>>(ptr.getCell(), lua);
else else
return sol::nullopt; return sol::nil;
}); });
objectT["parentContainer"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<ObjectT> { objectT["parentContainer"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<ObjectT> {
const MWWorld::Ptr& ptr = o.ptr(); const MWWorld::Ptr& ptr = o.ptr();
@ -555,6 +556,7 @@ namespace MWLua
void addInventoryBindings(sol::usertype<ObjectT>& objectT, const std::string& prefix, const Context& context) void addInventoryBindings(sol::usertype<ObjectT>& objectT, const std::string& prefix, const Context& context)
{ {
using InventoryT = Inventory<ObjectT>; using InventoryT = Inventory<ObjectT>;
sol::state_view lua = context.sol();
sol::usertype<InventoryT> inventoryT = context.sol().new_usertype<InventoryT>(prefix + "Inventory"); sol::usertype<InventoryT> inventoryT = context.sol().new_usertype<InventoryT>(prefix + "Inventory");
inventoryT[sol::meta_function::to_string] inventoryT[sol::meta_function::to_string]
@ -651,7 +653,7 @@ namespace MWLua
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
return store.isResolved(); return store.isResolved();
}; };
inventoryT["find"] = [](const InventoryT& inventory, std::string_view recordId) -> sol::optional<ObjectT> { inventoryT["find"] = [lua](const InventoryT& inventory, std::string_view recordId) -> sol::object {
const MWWorld::Ptr& ptr = inventory.mObj.ptr(); const MWWorld::Ptr& ptr = inventory.mObj.ptr();
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
auto itemId = ESM::RefId::deserializeText(recordId); auto itemId = ESM::RefId::deserializeText(recordId);
@ -660,10 +662,10 @@ namespace MWLua
if (item.getCellRef().getRefId() == itemId) if (item.getCellRef().getRefId() == itemId)
{ {
MWBase::Environment::get().getWorldModel()->registerPtr(item); MWBase::Environment::get().getWorldModel()->registerPtr(item);
return ObjectT(getId(item)); return makeOrGet<ObjectT>(getId(item), lua);
} }
} }
return sol::nullopt; return sol::nil;
}; };
inventoryT["findAll"] = [](const InventoryT& inventory, std::string_view recordId) { inventoryT["findAll"] = [](const InventoryT& inventory, std::string_view recordId) {
const MWWorld::Ptr& ptr = inventory.mObj.ptr(); const MWWorld::Ptr& ptr = inventory.mObj.ptr();

View File

@ -96,9 +96,9 @@ namespace MWLua
return sol::nil; return sol::nil;
MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getCell(cellRef.getDestCell()); MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getCell(cellRef.getDestCell());
if (dynamic_cast<const GObject*>(&o)) if (dynamic_cast<const GObject*>(&o))
return sol::make_object(thisState, GCell{ &cell }); return makeOrGet<GCell>(&cell, thisState);
else else
return sol::make_object(thisState, LCell{ &cell }); return makeOrGet<LCell>(&cell, thisState);
}; };
addRecordFunctionBinding<ESM::Door>(door, context); addRecordFunctionBinding<ESM::Door>(door, context);
@ -132,9 +132,9 @@ namespace MWLua
return sol::nil; return sol::nil;
MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getCell(cellRef.getDestCell()); MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getCell(cellRef.getDestCell());
if (dynamic_cast<const GObject*>(&o)) if (dynamic_cast<const GObject*>(&o))
return sol::make_object(lua, GCell{ &cell }); return makeOrGet<GCell>(&cell, lua);
else else
return sol::make_object(lua, LCell{ &cell }); return makeOrGet<LCell>(&cell, lua);
}; };
addRecordFunctionBinding<ESM4::Door>(door, context, "ESM4Door"); addRecordFunctionBinding<ESM4::Door>(door, context, "ESM4Door");

View File

@ -77,9 +77,9 @@ namespace MWLua
ObjectId id = loadRefNum(binaryData); ObjectId id = loadRefNum(binaryData);
adjustRefNum(id); adjustRefNum(id);
if (mLocalSerializer) if (mLocalSerializer)
sol::stack::push<LObject>(lua, LObject(id)); sol::stack::push(lua, makeOrGet<LObject>(id, lua));
else else
sol::stack::push<GObject>(lua, GObject(id)); sol::stack::push(lua, makeOrGet<GObject>(id, lua));
return true; return true;
} }
if (typeName == sObjListTypeName) if (typeName == sObjListTypeName)

View File

@ -81,14 +81,16 @@ namespace MWLua
static void addCellGetters(sol::table& api, const Context& context) static void addCellGetters(sol::table& api, const Context& context)
{ {
api["getCellByName"] = [](std::string_view name) { sol::state_view lua = context.sol();
return GCell{ &MWBase::Environment::get().getWorldModel()->getCell(name, /*forceLoad=*/false) }; api["getCellByName"] = [lua](std::string_view name) {
return makeOrGet<GCell>(
&MWBase::Environment::get().getWorldModel()->getCell(name, /*forceLoad=*/false), lua);
}; };
api["getCellById"] = [](std::string_view stringId) { api["getCellById"] = [lua](std::string_view stringId) {
return GCell{ &MWBase::Environment::get().getWorldModel()->getCell( ESM::RefId id = ESM::RefId::deserializeText(stringId);
ESM::RefId::deserializeText(stringId), /*forceLoad=*/false) }; return makeOrGet<GCell>(&MWBase::Environment::get().getWorldModel()->getCell(id, /*forceLoad=*/false), lua);
}; };
api["getExteriorCell"] = [](int x, int y, sol::object cellOrName) { api["getExteriorCell"] = [lua](int x, int y, sol::object cellOrName) {
ESM::RefId worldspace; ESM::RefId worldspace;
if (cellOrName.is<GCell>()) if (cellOrName.is<GCell>())
worldspace = cellOrName.as<GCell>().mStore->getCell()->getWorldSpace(); worldspace = cellOrName.as<GCell>().mStore->getCell()->getWorldSpace();
@ -100,8 +102,9 @@ namespace MWLua
->getWorldSpace(); ->getWorldSpace();
else else
worldspace = ESM::Cell::sDefaultWorldspaceId; worldspace = ESM::Cell::sDefaultWorldspaceId;
return GCell{ &MWBase::Environment::get().getWorldModel()->getExterior( auto location = ESM::ExteriorCellLocation(x, y, worldspace);
ESM::ExteriorCellLocation(x, y, worldspace), /*forceLoad=*/false) }; return makeOrGet<GCell>(
&MWBase::Environment::get().getWorldModel()->getExterior(location, /*forceLoad=*/false), lua);
}; };
const MWWorld::Store<ESM::Cell>* cells3Store = &MWBase::Environment::get().getESMStore()->get<ESM::Cell>(); const MWWorld::Store<ESM::Cell>* cells3Store = &MWBase::Environment::get().getESMStore()->get<ESM::Cell>();
@ -111,22 +114,22 @@ namespace MWLua
cells[sol::meta_function::length] cells[sol::meta_function::length]
= [cells3Store, cells4Store](const CellsStore&) { return cells3Store->getSize() + cells4Store->getSize(); }; = [cells3Store, cells4Store](const CellsStore&) { return cells3Store->getSize() + cells4Store->getSize(); };
cells[sol::meta_function::index] cells[sol::meta_function::index]
= [cells3Store, cells4Store](const CellsStore&, size_t index) -> sol::optional<GCell> { = [cells3Store, cells4Store, lua](const CellsStore&, size_t index) -> sol::object {
if (index > cells3Store->getSize() + cells3Store->getSize() || index == 0) if (index > cells3Store->getSize() + cells3Store->getSize() || index == 0)
return sol::nullopt; return sol::nil;
index--; // Translate from Lua's 1-based indexing. index--; // Translate from Lua's 1-based indexing.
if (index < cells3Store->getSize()) if (index < cells3Store->getSize())
{ {
const ESM::Cell* cellRecord = cells3Store->at(index); const ESM::Cell* cellRecord = cells3Store->at(index);
return GCell{ &MWBase::Environment::get().getWorldModel()->getCell( return makeOrGet<GCell>(
cellRecord->mId, /*forceLoad=*/false) }; &MWBase::Environment::get().getWorldModel()->getCell(cellRecord->mId, /*forceLoad=*/false), lua);
} }
else else
{ {
const ESM4::Cell* cellRecord = cells4Store->at(index - cells3Store->getSize()); const ESM4::Cell* cellRecord = cells4Store->at(index - cells3Store->getSize());
return GCell{ &MWBase::Environment::get().getWorldModel()->getCell( return makeOrGet<GCell>(
cellRecord->mId, /*forceLoad=*/false) }; &MWBase::Environment::get().getWorldModel()->getCell(cellRecord->mId, /*forceLoad=*/false), lua);
} }
}; };
cells[sol::meta_function::pairs] = view["ipairsForArray"].template get<sol::function>(); cells[sol::meta_function::pairs] = view["ipairsForArray"].template get<sol::function>();