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
mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings dialoguebindings
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/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

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>());
selfAPI[sol::meta_function::to_string]
= [](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["isActive"] = [](SelfObject& self) -> bool { return self.mIsActive; };
selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; };

View File

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

View File

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

View File

@ -170,11 +170,11 @@ namespace MWLua
listT[sol::meta_function::to_string]
= [](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::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())
return ObjectT((*list.mIds)[LuaUtil::fromLuaIndex(index)]);
return makeOrGet<ObjectT>((*list.mIds)[LuaUtil::fromLuaIndex(index)], lua);
else
return sol::nullopt;
return sol::nil;
};
listT[sol::meta_function::pairs] = 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>
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["contentFile"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<std::string> {
int contentFileIndex = o.id().mContentFile;
@ -296,13 +297,13 @@ namespace MWLua
else
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();
MWWorld::WorldModel* wm = MWBase::Environment::get().getWorldModel();
if (ptr.isInCell() && ptr.getCell() != &wm->getDraftCell())
return Cell<ObjectT>{ ptr.getCell() };
return makeOrGet<Cell<ObjectT>>(ptr.getCell(), lua);
else
return sol::nullopt;
return sol::nil;
});
objectT["parentContainer"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<ObjectT> {
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)
{
using InventoryT = Inventory<ObjectT>;
sol::state_view lua = context.sol();
sol::usertype<InventoryT> inventoryT = context.sol().new_usertype<InventoryT>(prefix + "Inventory");
inventoryT[sol::meta_function::to_string]
@ -651,7 +653,7 @@ namespace MWLua
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
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();
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
auto itemId = ESM::RefId::deserializeText(recordId);
@ -660,10 +662,10 @@ namespace MWLua
if (item.getCellRef().getRefId() == itemId)
{
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) {
const MWWorld::Ptr& ptr = inventory.mObj.ptr();

View File

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

View File

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

View File

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