diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 9a5669c870..bfaeaf2cf3 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -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 diff --git a/apps/openmw/mwlua/intern.cpp b/apps/openmw/mwlua/intern.cpp new file mode 100644 index 0000000000..164319ab54 --- /dev/null +++ b/apps/openmw/mwlua/intern.cpp @@ -0,0 +1,84 @@ +#include "object.hpp" + +namespace MWLua +{ + namespace + { + template + inline constexpr bool is_gameObject = std::is_same_v || std::is_same_v; + template + inline constexpr bool is_cell = std::is_same_v || std::is_same_v; + + 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(id.mContentFile) << 24); + else + return id.mIndex | (static_cast(-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 t(lua, sol::create); + t[sol::metatable_key] = lua.create_table_with("__mode", "v"); // weak table + + lua.registry()[key] = t; + return t; + } + + template + sol::object makeOrGetImpl(ArgT arg, sol::state_view lua) + { + static_assert(std::is_constructible_v); + + sol::table registry; + sol::object key; + + if constexpr (is_gameObject) + { + registry = getRegistry(gameObjects, lua); + if constexpr (std::is_same_v) + key = sol::make_object(lua, toUInt64(getId(arg))); + else + key = sol::make_object(lua, toUInt64(arg)); + } + else if constexpr (is_cell) + { + 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 + sol::object makeOrGet(ArgT arg, sol::state_view lua) + { + return makeOrGetImpl(arg, lua); + } + + // Game Objects + template sol::object MWLua::makeOrGet(MWWorld::Ptr, sol::state_view); + template sol::object MWLua::makeOrGet(ObjectId, sol::state_view); + template sol::object MWLua::makeOrGet(MWWorld::Ptr, sol::state_view); + template sol::object MWLua::makeOrGet(ObjectId, sol::state_view); + + // Cells + template sol::object MWLua::makeOrGet(MWWorld::CellStore*, sol::state_view); + template sol::object MWLua::makeOrGet(MWWorld::CellStore*, sol::state_view); +} diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 99c994304b..522cd39212 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -58,7 +58,8 @@ namespace MWLua = lua.new_usertype("SelfObject", sol::base_classes, sol::bases()); 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(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; }; diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index c0146234f5..3cdafe6445 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -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(selectedPtr, view.sol()); }); if (playerScripts->consoleCommand(consoleMode, command, selected)) processed = true; } diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index 179b35aa5c..81d7727bde 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -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 { + mwscript["object"] = sol::readonly_property([lua](const MWScriptRef& s) -> sol::object { if (s.mObj) - return s.mObj; + return makeOrGet(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(*ptr, lua); else - return sol::nullopt; + return sol::nil; }); mwscript["player"] = sol::readonly_property( [](const MWScriptRef&) { return GObject(MWBase::Environment::get().getWorld()->getPlayerPtr()); }); diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index 4a50dee8b6..171fa147cb 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -35,6 +35,9 @@ namespace MWLua } }; + template + sol::object makeOrGet(ArgT arg, sol::state_view lua); + // Used only in local scripts struct LCell { diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 2a0aa87d15..14170270d5 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -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 { + 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((*list.mIds)[LuaUtil::fromLuaIndex(index)], lua); else - return sol::nullopt; + return sol::nil; }; listT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); listT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); @@ -278,6 +278,7 @@ namespace MWLua template void addBasicBindings(sol::usertype& 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 { 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> { + 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{ ptr.getCell() }; + return makeOrGet>(ptr.getCell(), lua); else - return sol::nullopt; + return sol::nil; }); objectT["parentContainer"] = sol::readonly_property([](const ObjectT& o) -> sol::optional { const MWWorld::Ptr& ptr = o.ptr(); @@ -555,6 +556,7 @@ namespace MWLua void addInventoryBindings(sol::usertype& objectT, const std::string& prefix, const Context& context) { using InventoryT = Inventory; + sol::state_view lua = context.sol(); sol::usertype inventoryT = context.sol().new_usertype(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 { + 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(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(); diff --git a/apps/openmw/mwlua/types/door.cpp b/apps/openmw/mwlua/types/door.cpp index 61dcc66a45..9d2602143a 100644 --- a/apps/openmw/mwlua/types/door.cpp +++ b/apps/openmw/mwlua/types/door.cpp @@ -96,9 +96,9 @@ namespace MWLua return sol::nil; MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getCell(cellRef.getDestCell()); if (dynamic_cast(&o)) - return sol::make_object(thisState, GCell{ &cell }); + return makeOrGet(&cell, thisState); else - return sol::make_object(thisState, LCell{ &cell }); + return makeOrGet(&cell, thisState); }; addRecordFunctionBinding(door, context); @@ -132,9 +132,9 @@ namespace MWLua return sol::nil; MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getCell(cellRef.getDestCell()); if (dynamic_cast(&o)) - return sol::make_object(lua, GCell{ &cell }); + return makeOrGet(&cell, lua); else - return sol::make_object(lua, LCell{ &cell }); + return makeOrGet(&cell, lua); }; addRecordFunctionBinding(door, context, "ESM4Door"); diff --git a/apps/openmw/mwlua/userdataserializer.cpp b/apps/openmw/mwlua/userdataserializer.cpp index 478f725d7c..b2a7349d35 100644 --- a/apps/openmw/mwlua/userdataserializer.cpp +++ b/apps/openmw/mwlua/userdataserializer.cpp @@ -77,9 +77,9 @@ namespace MWLua ObjectId id = loadRefNum(binaryData); adjustRefNum(id); if (mLocalSerializer) - sol::stack::push(lua, LObject(id)); + sol::stack::push(lua, makeOrGet(id, lua)); else - sol::stack::push(lua, GObject(id)); + sol::stack::push(lua, makeOrGet(id, lua)); return true; } if (typeName == sObjListTypeName) diff --git a/apps/openmw/mwlua/worldbindings.cpp b/apps/openmw/mwlua/worldbindings.cpp index d80631c273..62a3fbad8e 100644 --- a/apps/openmw/mwlua/worldbindings.cpp +++ b/apps/openmw/mwlua/worldbindings.cpp @@ -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( + &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(&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()) worldspace = cellOrName.as().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( + &MWBase::Environment::get().getWorldModel()->getExterior(location, /*forceLoad=*/false), lua); }; const MWWorld::Store* cells3Store = &MWBase::Environment::get().getESMStore()->get(); @@ -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 { + = [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( + &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( + &MWBase::Environment::get().getWorldModel()->getCell(cellRecord->mId, /*forceLoad=*/false), lua); } }; cells[sol::meta_function::pairs] = view["ipairsForArray"].template get();