From fb3917fc1ae00e2deb3dd5a3c85c6ed22c344172 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 27 Aug 2021 09:26:38 +0200 Subject: [PATCH] Lua callbacks --- apps/openmw/mwlua/asyncbindings.cpp | 9 +++++++- apps/openmw/mwlua/luabindings.hpp | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 15 +++++++++++++ apps/openmw/mwlua/luamanagerimp.hpp | 32 ++++++++++++++++++++++++++++ apps/openmw/mwlua/nearbybindings.cpp | 14 ++++++++++++ components/lua/scriptscontainer.cpp | 12 +++++++++++ components/lua/scriptscontainer.hpp | 4 +++- files/lua_api/openmw/async.lua | 7 ++++++ 8 files changed, 92 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/asyncbindings.cpp b/apps/openmw/mwlua/asyncbindings.cpp index fee6788b89..9fdda53d9d 100644 --- a/apps/openmw/mwlua/asyncbindings.cpp +++ b/apps/openmw/mwlua/asyncbindings.cpp @@ -1,5 +1,7 @@ #include "luabindings.hpp" +#include "luamanagerimp.hpp" + namespace sol { template <> @@ -48,11 +50,16 @@ namespace MWLua asyncId.mContainer->setupUnsavableTimer( TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScript, std::move(callback)); }; + api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn) + { + return Callback{std::move(fn), asyncId.mHiddenData}; + }; auto initializer = [](sol::table hiddenData) { LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY]; - return AsyncPackageId{id.mContainer, id.mPath}; + hiddenData[Callback::SCRIPT_NAME_KEY] = id.toString(); + return AsyncPackageId{id.mContainer, id.mPath, hiddenData}; }; return sol::make_object(context.mLua->sol(), initializer); } diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index d34ff3d727..d1c62e43e3 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -47,9 +47,9 @@ namespace MWLua // Implemented in asyncbindings.cpp struct AsyncPackageId { - // TODO: add ObjectId mLocalObject; LuaUtil::ScriptsContainer* mContainer; std::string mScript; + sol::table mHiddenData; }; sol::function getAsyncPackageInitializer(const Context&); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 1e009081ff..38055c99b7 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -74,6 +74,16 @@ namespace MWLua mInitialized = true; } + void Callback::operator()(sol::object arg) const + { + if (mHiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY] != sol::nil) + LuaUtil::call(mFunc, std::move(arg)); + else + { + Log(Debug::Debug) << "Ignored callback to removed script " << mHiddenData.get(SCRIPT_NAME_KEY); + } + } + void LuaManager::update(bool paused, float dt) { ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry(); @@ -126,6 +136,11 @@ namespace MWLua << ". Object not found or has no attached scripts"; } + // Run queued callbacks + for (CallbackWithData& c : mQueuedCallbacks) + c.mCallback(c.mArg); + mQueuedCallbacks.clear(); + // Engine handlers in local scripts PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); if (playerScripts) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 8fdfb02701..91f48171f3 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -19,6 +19,19 @@ namespace MWLua { + // Wrapper for a single-argument Lua function. + // Holds information about the script the function belongs to. + // Needed to prevent callback calls if the script was removed. + struct Callback + { + static constexpr std::string_view SCRIPT_NAME_KEY = "name"; + + sol::function mFunc; + sol::table mHiddenData; + + void operator()(sol::object arg) const; + }; + class LuaManager : public MWBase::LuaManager { public: @@ -67,6 +80,18 @@ namespace MWLua // Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script. void reloadAllScripts() override; + // Used to call Lua callbacks from C++ + void queueCallback(Callback callback, sol::object arg) { mQueuedCallbacks.push_back({std::move(callback), std::move(arg)}); } + + // Wraps Lua callback into an std::function. + // NOTE: Resulted function is not thread safe. Can not be used while LuaManager::update() or + // any other Lua-related function is running. + template + std::function wrapLuaCallback(const Callback& c) + { + return [this, c](Arg arg) { this->queueCallback(c, sol::make_object(c.mFunc.lua_state(), arg)); }; + } + private: LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr); @@ -100,6 +125,13 @@ namespace MWLua std::vector mInputEvents; std::vector mActorAddedEvents; + struct CallbackWithData + { + Callback mCallback; + sol::object mArg; + }; + std::vector mQueuedCallbacks; + struct LocalEngineEvent { ObjectId mDest; diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 16e55bd6b1..8eae46a9b7 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -79,6 +79,20 @@ namespace MWLua return rayCasting->castSphere(from, to, radius, collisionType); } }; + // TODO: async raycasting + /*api["asyncCastRay"] = [luaManager = context.mLuaManager, defaultCollisionType]( + const Callback& luaCallback, const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) + { + std::function callback = + luaManager->wrapLuaCallback(luaCallback); + MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); + + // Handle options the same way as in `castRay`. + + // NOTE: `callback` is not thread safe. If MWPhysics works in separate thread, it must put results to a queue + // and use this callback from the main thread at the beginning of the next frame processing. + rayCasting->asyncCastRay(callback, from, to, ignore, std::vector(), collisionType); + };*/ api["activators"] = LObjectList{worldView->getActivatorsInScene()}; api["actors"] = LObjectList{worldView->getActorsInScene()}; diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 6e0a19b120..b6882b0988 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -16,6 +16,15 @@ namespace LuaUtil static constexpr std::string_view REGISTERED_TIMER_CALLBACKS = "_timers"; static constexpr std::string_view TEMPORARY_TIMER_CALLBACKS = "_temp_timers"; + std::string ScriptsContainer::ScriptId::toString() const + { + std::string res = mContainer->mNamePrefix; + res.push_back('['); + res.append(mPath); + res.push_back(']'); + return res; + } + ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix) : mNamePrefix(namePrefix), mLua(*lua) { registerEngineHandlers({&mUpdateHandlers}); @@ -81,6 +90,7 @@ namespace LuaUtil auto scriptIter = mScripts.find(path); if (scriptIter == mScripts.end()) return false; // no such script + scriptIter->second.mHiddenData[ScriptId::KEY] = sol::nil; sol::object& script = scriptIter->second.mInterface; if (getFieldOrNil(script, INTERFACE_NAME) != sol::nil) { @@ -320,6 +330,8 @@ namespace LuaUtil void ScriptsContainer::removeAllScripts() { + for (auto& [_, script] : mScripts) + script.mHiddenData[ScriptId::KEY] = sol::nil; mScripts.clear(); mScriptOrder.clear(); for (auto& [_, handlers] : mEngineHandlers) diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index d3141a2ca3..0bf50b8793 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -66,6 +66,8 @@ namespace LuaUtil ScriptsContainer* mContainer; std::string mPath; + + std::string toString() const; }; using TimeUnit = ESM::LuaTimer::TimeUnit; @@ -73,7 +75,7 @@ namespace LuaUtil ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix); ScriptsContainer(const ScriptsContainer&) = delete; ScriptsContainer(ScriptsContainer&&) = delete; - virtual ~ScriptsContainer() {} + virtual ~ScriptsContainer() { removeAllScripts(); } // Adds package that will be available (via `require`) for all scripts in the container. // Automatically applies LuaUtil::makeReadOnly to the package. diff --git a/files/lua_api/openmw/async.lua b/files/lua_api/openmw/async.lua index 68f63b5193..cc3a233f8a 100644 --- a/files/lua_api/openmw/async.lua +++ b/files/lua_api/openmw/async.lua @@ -48,5 +48,12 @@ -- @param #number delay -- @param #function func +------------------------------------------------------------------------------- +-- Wraps Lua function with `Callback` object that can be used in async API calls. +-- @function [parent=#async] callback +-- @param self +-- @param #function func +-- @return #Callback + return nil