more generic + cell cache

This commit is contained in:
Kuyondo 2025-09-02 21:29:51 +08:00
parent d66be6ac70
commit 99ecdf74b3
5 changed files with 69 additions and 46 deletions

View File

@ -4,62 +4,81 @@ namespace MWLua
{
namespace
{
constexpr std::string_view REG_KEY = "LUA_GAMEOBJECTS_REGISTRY";
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>;
inline int64_t toInt64(const ObjectId& id)
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 static_cast<int64_t>(id.mIndex) | (static_cast<int64_t>(id.mContentFile) << 32);
return id.mIndex | (static_cast<uint64_t>(id.mContentFile) << 24);
else
return static_cast<int64_t>(id.mIndex) | (static_cast<int64_t>(-id.mContentFile - 1) << 32);
return id.mIndex | (static_cast<uint64_t>(-id.mContentFile - 1) << 32);
}
inline sol::table getRegistry(sol::state_view lua)
inline sol::table getRegistry(std::string_view key, sol::state_view lua)
{
if (sol::object reg = lua.registry()[REG_KEY]; reg.valid()) // LUA_REGISTRYINDEX
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()[REG_KEY] = t;
lua.registry()[key] = t;
return t;
}
template <typename Obj, typename PtrOrId>
sol::object makeOrGetImpl(PtrOrId input, sol::state_view lua)
template <typename userT, typename ArgT>
sol::object makeOrGetImpl(ArgT arg, sol::state_view lua)
{
static_assert(std::is_constructible_v<Obj, PtrOrId>);
static_assert(std::is_constructible_v<userT, ArgT>);
ObjectId id;
if constexpr (std::is_same_v<PtrOrId, MWWorld::Ptr>)
id = getId(input);
else
id = input;
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
int64_t key = toInt64(id);
sol::table registry = getRegistry(lua);
sol::object existing = registry[key];
if (existing.valid())
return existing;
sol::object obj = sol::make_object(lua, Obj(input));
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 Obj, typename ptrOrId>
sol::object makeOrGet(ptrOrId input, sol::state_view lua)
template <typename userT, typename ArgT>
sol::object makeOrGet(ArgT arg, sol::state_view lua)
{
return makeOrGetImpl<Obj>(input, 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

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

View File

@ -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();

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

@ -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>();