1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-31 15:32:45 +00:00

Add apps/openmw/mwlua

This commit is contained in:
Petr Mikheev 2020-12-18 23:21:10 +01:00
parent 7df500c385
commit 3d7e306064
24 changed files with 1180 additions and 1 deletions

View File

@ -55,6 +55,11 @@ add_openmw_dir (mwscript
animationextensions transformationextensions consoleextensions userextensions
)
add_openmw_dir (mwlua
luamanagerimp localscripts object worldview luabindings userdataserializer
objectbindings
)
add_openmw_dir (mwsound
soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output
loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater volumesettings

View File

@ -1,5 +1,6 @@
#include "engine.hpp"
#include <atomic>
#include <iomanip>
#include <fstream>
#include <chrono>
@ -46,6 +47,8 @@
#include "mwgui/windowmanagerimp.hpp"
#include "mwlua/luamanagerimp.hpp"
#include "mwscript/scriptmanagerimp.hpp"
#include "mwscript/interpretercontext.hpp"
@ -101,6 +104,7 @@ namespace
PhysicsWorker,
World,
Gui,
Lua,
Number,
};
@ -138,6 +142,9 @@ namespace
template <>
const UserStats UserStatsValue<UserStatsType::Gui>::sValue {"Gui", "gui"};
template <>
const UserStats UserStatsValue<UserStatsType::Lua>::sValue {"Lua", "lua"};
template <UserStatsType type>
struct ForEachUserStatsValue
{
@ -700,6 +707,9 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
mViewer->addEventHandler(mScreenCaptureHandler);
mLuaManager = new MWLua::LuaManager(mVFS.get());
mEnvironment.setLuaManager(mLuaManager);
// Create input and UI first to set up a bootstrapping environment for
// showing a loading screen and keeping the window responsive while doing so
@ -811,6 +821,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
<< 100*static_cast<double> (result.second)/result.first
<< "%)";
}
mLuaManager->init();
}
// Initialise and enter main loop.
@ -895,6 +907,28 @@ void OMW::Engine::go()
mEnvironment.getWindowManager()->executeInConsole(mStartupScript);
}
// Start Lua scripting thread
std::atomic_bool luaUpdateRequest;
double luaDt = 0;
std::thread scriptingThread([&]() {
const osg::Timer* const timer = osg::Timer::instance();
osg::Stats* const stats = mViewer->getViewerStats();
while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest())
{
while (!luaUpdateRequest)
std::this_thread::yield();
{
const osg::Timer_t frameStart = mViewer->getStartTick();
const unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber();
ScopedProfile<UserStatsType::Lua> profile(frameStart, frameNumber, *timer, *stats);
mLuaManager->update(mEnvironment.getWindowManager()->isGuiMode(), luaDt);
}
luaUpdateRequest = false;
}
});
// Start the main rendering loop
double simulationTime = 0.0;
Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(mEnvironment.getFrameRateLimit());
@ -920,8 +954,17 @@ void OMW::Engine::go()
mEnvironment.getWorld()->updateWindowManager();
// scriptingThread starts processing Lua scripts
luaDt = dt;
luaUpdateRequest = true;
mViewer->renderingTraversals();
// wait for scriptingThread to finish
while (luaUpdateRequest)
std::this_thread::yield();
mLuaManager->applyQueuedChanges();
bool guiActive = mEnvironment.getWindowManager()->isGuiMode();
if (!guiActive)
simulationTime += dt;
@ -943,6 +986,8 @@ void OMW::Engine::go()
frameRateLimiter.limit();
}
scriptingThread.join();
// Save user settings
settings.saveUser(settingspath);

View File

@ -33,6 +33,11 @@ namespace Compiler
class Context;
}
namespace MWLua
{
class LuaManager;
}
namespace Files
{
struct ConfigurationManager;
@ -85,6 +90,8 @@ namespace OMW
Compiler::Extensions mExtensions;
Compiler::Context *mScriptContext;
MWLua::LuaManager* mLuaManager;
Files::Collections mFileCollections;
bool mFSStrict;
Translation::Storage mTranslationDataStorage;

View File

@ -13,13 +13,14 @@
#include "inputmanager.hpp"
#include "windowmanager.hpp"
#include "statemanager.hpp"
#include "luamanager.hpp"
MWBase::Environment *MWBase::Environment::sThis = nullptr;
MWBase::Environment::Environment()
: mWorld (nullptr), mSoundManager (nullptr), mScriptManager (nullptr), mWindowManager (nullptr),
mMechanicsManager (nullptr), mDialogueManager (nullptr), mJournal (nullptr), mInputManager (nullptr),
mStateManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f)
mStateManager (nullptr), mLuaManager (nullptr), mResourceSystem (nullptr), mFrameDuration (0), mFrameRateLimit(0.f)
{
assert (!sThis);
sThis = this;
@ -76,6 +77,11 @@ void MWBase::Environment::setStateManager (StateManager *stateManager)
mStateManager = stateManager;
}
void MWBase::Environment::setLuaManager (LuaManager *luaManager)
{
mLuaManager = luaManager;
}
void MWBase::Environment::setResourceSystem (Resource::ResourceSystem *resourceSystem)
{
mResourceSystem = resourceSystem;
@ -150,6 +156,12 @@ MWBase::StateManager *MWBase::Environment::getStateManager() const
return mStateManager;
}
MWBase::LuaManager *MWBase::Environment::getLuaManager() const
{
assert (mLuaManager);
return mLuaManager;
}
Resource::ResourceSystem *MWBase::Environment::getResourceSystem() const
{
return mResourceSystem;
@ -188,6 +200,9 @@ void MWBase::Environment::cleanup()
delete mStateManager;
mStateManager = nullptr;
delete mLuaManager;
mLuaManager = nullptr;
}
const MWBase::Environment& MWBase::Environment::get()

View File

@ -22,6 +22,7 @@ namespace MWBase
class InputManager;
class WindowManager;
class StateManager;
class LuaManager;
/// \brief Central hub for mw-subsystems
///
@ -42,6 +43,7 @@ namespace MWBase
Journal *mJournal;
InputManager *mInputManager;
StateManager *mStateManager;
LuaManager *mLuaManager;
Resource::ResourceSystem *mResourceSystem;
float mFrameDuration;
float mFrameRateLimit;
@ -76,6 +78,8 @@ namespace MWBase
void setStateManager (StateManager *stateManager);
void setLuaManager (LuaManager *luaManager);
void setResourceSystem (Resource::ResourceSystem *resourceSystem);
void setFrameDuration (float duration);
@ -102,6 +106,8 @@ namespace MWBase
StateManager *getStateManager() const;
LuaManager *getLuaManager() const;
Resource::ResourceSystem *getResourceSystem() const;
float getFrameDuration() const;

View File

@ -0,0 +1,42 @@
#ifndef GAME_MWBASE_LUAMANAGER_H
#define GAME_MWBASE_LUAMANAGER_H
#include <SDL_events.h>
namespace MWWorld
{
class Ptr;
}
namespace MWBase
{
class LuaManager
{
public:
virtual ~LuaManager() = default;
virtual void newGameStarted() = 0;
virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0;
virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0;
virtual void keyPressed(const SDL_KeyboardEvent &arg) = 0;
struct ActorControls {
bool controlledFromLua;
bool jump;
bool run;
float movement;
float sideMovement;
float turn;
};
virtual const ActorControls* getActorControls(const MWWorld::Ptr&) const = 0;
virtual void clear() = 0;
virtual void setupPlayer(const MWWorld::Ptr&) = 0;
};
}
#endif // GAME_MWBASE_LUAMANAGER_H

View File

@ -0,0 +1,23 @@
#ifndef MWLUA_EVENTQUEUE_H
#define MWLUA_EVENTQUEUE_H
#include "object.hpp"
namespace MWLua
{
struct GlobalEvent
{
std::string eventName;
std::string eventData;
};
struct LocalEvent
{
ObjectId dest;
std::string eventName;
std::string eventData;
};
using GlobalEventQueue = std::vector<GlobalEvent>;
using LocalEventQueue = std::vector<LocalEvent>;
}
#endif // MWLUA_EVENTQUEUE_H

View File

@ -0,0 +1,36 @@
#ifndef MWLUA_GLOBALSCRIPTS_H
#define MWLUA_GLOBALSCRIPTS_H
#include <memory>
#include <set>
#include <string>
#include <components/lua/luastate.hpp>
#include <components/lua/scriptscontainer.hpp>
#include "object.hpp"
namespace MWLua
{
class GlobalScripts : public LuaUtil::ScriptsContainer
{
public:
GlobalScripts(LuaUtil::LuaState* lua) : LuaUtil::ScriptsContainer(lua, "Global")
{
registerEngineHandlers({&mActorActiveHandlers, &mNewGameHandlers, &mPlayerAddedHandlers});
}
void newGameStarted() { callEngineHandlers(mNewGameHandlers); }
void actorActive(const GObject& obj) { callEngineHandlers(mActorActiveHandlers, obj); }
void playerAdded(const GObject& obj) { callEngineHandlers(mPlayerAddedHandlers, obj); }
private:
EngineHandlerList mActorActiveHandlers{"onActorActive"};
EngineHandlerList mNewGameHandlers{"onNewGame"};
EngineHandlerList mPlayerAddedHandlers{"onPlayerAdded"};
};
}
#endif // MWLUA_GLOBALSCRIPTS_H

View File

@ -0,0 +1,44 @@
#include "localscripts.hpp"
namespace sol
{
template <>
struct is_automagical<MWBase::LuaManager::ActorControls> : std::false_type {};
template <>
struct is_automagical<MWLua::LocalScripts::SelfObject> : std::false_type {};
}
namespace MWLua
{
void LocalScripts::initializeSelfPackage(const Context& context)
{
using ActorControls = MWBase::LuaManager::ActorControls;
sol::usertype<ActorControls> controls = context.mLua->sol().new_usertype<ActorControls>("ActorControls");
controls["movement"] = &ActorControls::movement;
controls["sideMovement"] = &ActorControls::sideMovement;
controls["turn"] = &ActorControls::turn;
controls["run"] = &ActorControls::run;
controls["jump"] = &ActorControls::jump;
sol::usertype<SelfObject> selfAPI =
context.mLua->sol().new_usertype<SelfObject>("SelfObject", sol::base_classes, sol::bases<LObject>());
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["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; });
selfAPI["setDirectControl"] = [](SelfObject& self, bool v) { self.mControls.controlledFromLua = v; };
}
std::unique_ptr<LocalScripts> LocalScripts::create(LuaUtil::LuaState* lua, const LObject& obj)
{
return std::unique_ptr<LocalScripts>(new LocalScripts(lua, obj));
}
LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj)
: LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id())), mData(obj)
{
mData.mControls.controlledFromLua = false;
this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData));
}
}

View File

@ -0,0 +1,39 @@
#ifndef MWLUA_LOCALSCRIPTS_H
#define MWLUA_LOCALSCRIPTS_H
#include <memory>
#include <set>
#include <string>
#include <components/lua/luastate.hpp>
#include <components/lua/scriptscontainer.hpp>
#include "../mwbase/luamanager.hpp"
#include "object.hpp"
#include "luabindings.hpp"
namespace MWLua
{
class LocalScripts : public LuaUtil::ScriptsContainer
{
public:
static std::unique_ptr<LocalScripts> create(LuaUtil::LuaState* lua, const LObject& obj);
static void initializeSelfPackage(const Context&);
const MWBase::LuaManager::ActorControls* getActorControls() const { return &mData.mControls; }
struct SelfObject : public LObject
{
SelfObject(const LObject& obj) : LObject(obj) {}
MWBase::LuaManager::ActorControls mControls;
};
protected:
LocalScripts(LuaUtil::LuaState* lua, const LObject& obj);
SelfObject mData;
};
}
#endif // MWLUA_LOCALSCRIPTS_H

View File

@ -0,0 +1,39 @@
#include "luabindings.hpp"
#include <components/lua/luastate.hpp>
#include "eventqueue.hpp"
#include "worldview.hpp"
namespace MWLua
{
sol::table initCorePackage(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData)
{
context.mGlobalEventQueue->push_back({std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)});
};
return context.mLua->makeReadOnly(api);
}
sol::table initWorldPackage(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
WorldView* worldView = context.mWorldView;
api["activeActors"] = GObjectList{worldView->getActorsInScene()};
return context.mLua->makeReadOnly(api);
}
sol::table initNearbyPackage(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
WorldView* worldView = context.mWorldView;
api["actors"] = LObjectList{worldView->getActorsInScene()};
api["items"] = LObjectList{worldView->getItemsInScene()};
return context.mLua->makeReadOnly(api);
}
}

View File

@ -0,0 +1,36 @@
#ifndef MWLUA_LUABINDINGS_H
#define MWLUA_LUABINDINGS_H
#include <components/lua/luastate.hpp>
#include <components/lua/serialization.hpp>
#include "eventqueue.hpp"
#include "object.hpp"
#include "worldview.hpp"
namespace MWLua
{
class LuaManager;
struct Context
{
LuaManager* mLuaManager;
LuaUtil::LuaState* mLua;
LuaUtil::UserdataSerializer* mSerializer;
WorldView* mWorldView;
LocalEventQueue* mLocalEventQueue;
GlobalEventQueue* mGlobalEventQueue;
};
sol::table initCorePackage(const Context&);
sol::table initWorldPackage(const Context&);
sol::table initNearbyPackage(const Context&);
// Implemented in objectbindings.cpp
void initObjectBindingsForLocalScripts(const Context&);
void initObjectBindingsForGlobalScripts(const Context&);
// openmw.self package is implemented in localscripts.cpp
}
#endif // MWLUA_LUABINDINGS_H

View File

@ -0,0 +1,202 @@
#include "luamanagerimp.hpp"
#include <components/debug/debuglog.hpp>
#include <components/lua/utilpackage.hpp>
#include "../mwworld/class.hpp"
#include "../mwworld/ptr.hpp"
#include "luabindings.hpp"
#include "userdataserializer.hpp"
namespace MWLua
{
LuaManager::LuaManager(const VFS::Manager* vfs) : mLua(vfs)
{
Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion();
mGlobalSerializer = createUserdataSerializer(false, mWorldView.getObjectRegistry());
mLocalSerializer = createUserdataSerializer(true, mWorldView.getObjectRegistry());
mGlobalScripts.setSerializer(mGlobalSerializer.get());
Context context;
context.mLuaManager = this;
context.mLua = &mLua;
context.mWorldView = &mWorldView;
context.mLocalEventQueue = &mLocalEvents;
context.mGlobalEventQueue = &mGlobalEvents;
context.mSerializer = mGlobalSerializer.get();
Context localContext = context;
localContext.mSerializer = mLocalSerializer.get();
initObjectBindingsForGlobalScripts(context);
initObjectBindingsForLocalScripts(localContext);
LocalScripts::initializeSelfPackage(localContext);
mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol()));
mLua.addCommonPackage("openmw.core", initCorePackage(context));
mGlobalScripts.addPackage("openmw.world", initWorldPackage(context));
mNearbyPackage = initNearbyPackage(localContext);
}
void LuaManager::init()
{
mKeyPressEvents.clear();
if (mGlobalScripts.addNewScript("test.lua"))
Log(Debug::Info) << "Global script started: test.lua";
}
void LuaManager::update(bool paused, float dt)
{
mWorldView.update();
if (paused)
{
mKeyPressEvents.clear();
return;
}
std::vector<GlobalEvent> globalEvents = std::move(mGlobalEvents);
std::vector<LocalEvent> localEvents = std::move(mLocalEvents);
mGlobalEvents = std::vector<GlobalEvent>();
mLocalEvents = std::vector<LocalEvent>();
for (GlobalEvent& e : globalEvents)
mGlobalScripts.receiveEvent(e.eventName, e.eventData);
for (LocalEvent& e : localEvents)
{
LObject obj(e.dest, mWorldView.getObjectRegistry());
LocalScripts* scripts = obj.isValid() ? obj.ptr().getRefData().getLuaScripts() : nullptr;
if (scripts)
scripts->receiveEvent(e.eventName, e.eventData);
else
Log(Debug::Debug) << "Ignored event " << e.eventName << " to L" << idToString(e.dest)
<< ". Object not found or has no attached scripts";
}
if (mPlayerChanged)
{
mPlayerChanged = false;
mGlobalScripts.playerAdded(GObject(getId(mPlayer), mWorldView.getObjectRegistry()));
}
if (mPlayerScripts)
{
for (const SDL_Keysym key : mKeyPressEvents)
mPlayerScripts->keyPress(key.sym, key.mod);
}
mKeyPressEvents.clear();
for (ObjectId id : mActorAddedEvents)
mGlobalScripts.actorActive(GObject(id, mWorldView.getObjectRegistry()));
mActorAddedEvents.clear();
mGlobalScripts.update(dt);
for (LocalScripts* scripts : mActiveLocalScripts)
scripts->update(dt);
}
void LuaManager::applyQueuedChanges()
{
}
void LuaManager::clear()
{
mActiveLocalScripts.clear();
mLocalEvents.clear();
mGlobalEvents.clear();
mKeyPressEvents.clear();
mActorAddedEvents.clear();
mPlayerChanged = false;
mPlayerScripts = nullptr;
mWorldView.clear();
if (!mPlayer.isEmpty())
{
mPlayer.getCellRef().unsetRefNum();
mPlayer.getRefData().setLuaScripts(nullptr);
mPlayer = MWWorld::Ptr();
}
}
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
{
mWorldView.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
if (localScripts)
mActiveLocalScripts.insert(localScripts);
if (ptr.getClass().isActor() && ptr != mPlayer)
mActorAddedEvents.push_back(getId(ptr));
}
void LuaManager::setupPlayer(const MWWorld::Ptr& ptr)
{
if (!mPlayer.isEmpty())
throw std::logic_error("Player is initialized twice");
mWorldView.objectAddedToScene(ptr);
mPlayer = ptr;
MWWorld::RefData& refData = ptr.getRefData();
if (!refData.getLuaScripts())
createLocalScripts(ptr);
if (!mPlayerScripts)
throw std::logic_error("mPlayerScripts not initialized");
mActiveLocalScripts.insert(mPlayerScripts);
mPlayerChanged = true;
}
void LuaManager::objectRemovedFromScene(const MWWorld::Ptr& ptr)
{
mWorldView.objectRemovedFromScene(ptr);
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
if (localScripts)
mActiveLocalScripts.erase(localScripts);
// TODO: call mWorldView.objectUnloaded if object is unloaded from memory (does it ever happen?) and ptr becomes invalid.
}
void LuaManager::keyPressed(const SDL_KeyboardEvent& arg)
{
mKeyPressEvents.push_back(arg.keysym);
}
const MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const
{
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
if (!localScripts)
return nullptr;
return localScripts->getActorControls();
}
void LuaManager::addLocalScript(const MWWorld::Ptr& ptr, const std::string& scriptPath)
{
MWWorld::RefData& refData = ptr.getRefData();
if (!refData.getLuaScripts())
mActiveLocalScripts.insert(createLocalScripts(ptr));
refData.getLuaScripts()->addNewScript(scriptPath);
}
LocalScripts* LuaManager::createLocalScripts(const MWWorld::Ptr& ptr)
{
std::unique_ptr<LocalScripts> scripts;
// When loading a game, it can be called before LuaManager::setPlayer,
// so we can't just check ptr == mPlayer here.
if (*ptr.getCellRef().getRefIdPtr() == "player")
{
mPlayerScripts = new PlayerScripts(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()));
scripts = std::unique_ptr<LocalScripts>(mPlayerScripts);
// TODO: scripts->addPackage("openmw.ui", ...);
// TODO: scripts->addPackage("openmw.camera", ...);
}
else
scripts = LocalScripts::create(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()));
scripts->addPackage("openmw.nearby", mNearbyPackage);
scripts->setSerializer(mLocalSerializer.get());
MWWorld::RefData& refData = ptr.getRefData();
refData.setLuaScripts(std::move(scripts));
return refData.getLuaScripts();
}
}

View File

@ -0,0 +1,78 @@
#ifndef MWLUA_LUAMANAGERIMP_H
#define MWLUA_LUAMANAGERIMP_H
#include <map>
#include <set>
#include <components/lua/luastate.hpp>
#include "../mwbase/luamanager.hpp"
#include "object.hpp"
#include "eventqueue.hpp"
#include "globalscripts.hpp"
#include "localscripts.hpp"
#include "playerscripts.hpp"
#include "worldview.hpp"
namespace MWLua
{
class LuaManager : public MWBase::LuaManager
{
public:
LuaManager(const VFS::Manager* vfs);
~LuaManager() {}
// Called by engine.cpp when environment is fully initialized.
void init();
// Called by engine.cpp every frame. For performance reasons it works in a separate
// thread (in parallel with osg Cull). Can not use scene graph.
void update(bool paused, float dt);
// Called by engine.cpp from the main thread. Can use scene graph.
void applyQueuedChanges();
// Available everywhere through the MWBase::LuaManager interface.
// LuaManager queues these events and propagates to scripts on the next `update` call.
void newGameStarted() override { mGlobalScripts.newGameStarted(); }
void objectAddedToScene(const MWWorld::Ptr& ptr) override;
void objectRemovedFromScene(const MWWorld::Ptr& ptr) override;
void keyPressed(const SDL_KeyboardEvent &arg) override;
const MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override;
void clear() override; // should be called before loading game or starting a new game to reset internal state.
void setupPlayer(const MWWorld::Ptr& ptr) override; // Should be called once after each "clear".
// Used only in luabindings.cpp
void addLocalScript(const MWWorld::Ptr&, const std::string& scriptPath);
private:
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr);
LuaUtil::LuaState mLua;
sol::table mNearbyPackage;
GlobalScripts mGlobalScripts{&mLua};
std::set<LocalScripts*> mActiveLocalScripts;
WorldView mWorldView;
bool mPlayerChanged = false;
MWWorld::Ptr mPlayer;
PlayerScripts* mPlayerScripts = nullptr;
GlobalEventQueue mGlobalEvents;
LocalEventQueue mLocalEvents;
std::unique_ptr<LuaUtil::UserdataSerializer> mGlobalSerializer;
std::unique_ptr<LuaUtil::UserdataSerializer> mLocalSerializer;
std::vector<SDL_Keysym> mKeyPressEvents;
std::vector<ObjectId> mActorAddedEvents;
};
}
#endif // MWLUA_LUAMANAGERIMP_H

View File

@ -0,0 +1,111 @@
#include "object.hpp"
#include <components/esm/loadnpc.hpp>
#include <components/esm/loadcrea.hpp>
namespace MWLua
{
std::string idToString(const ObjectId& id)
{
return std::to_string(id.mIndex) + "_" + std::to_string(id.mContentFile);
}
std::string Object::toString() const
{
std::string res = idToString(mId);
if (isValid())
{
res.append(" (");
res.append(type());
res.append(", ");
res.append(*ptr().getCellRef().getRefIdPtr());
res.append(")");
}
else
res.append(" (not found)");
return res;
}
std::string_view Object::type() const
{
if (*ptr().getCellRef().getRefIdPtr() == "player")
return "Player";
const std::string& typeName = ptr().getTypeName();
if (typeName == typeid(ESM::NPC).name())
return "NPC";
else if (typeName == typeid(ESM::Creature).name())
return "Creature";
else
return typeName;
}
bool Object::isValid() const
{
if (mLastUpdate < mObjectRegistry->mUpdateCounter)
{
updatePtr();
mLastUpdate = mObjectRegistry->mUpdateCounter;
}
return !mPtr.isEmpty();
}
const MWWorld::Ptr& Object::ptr() const
{
if (!isValid())
throw std::runtime_error("Object is not available: " + idToString(mId));
return mPtr;
}
void ObjectRegistry::update()
{
if (mChanged)
{
mUpdateCounter++;
mChanged = false;
}
}
void ObjectRegistry::clear()
{
mObjectMapping.clear();
mChanged = false;
mUpdateCounter = 0;
mLastAssignedId.unset();
}
MWWorld::Ptr ObjectRegistry::getPtr(ObjectId id, bool onlyActive)
{
MWWorld::Ptr ptr;
auto it = mObjectMapping.find(id);
if (it != mObjectMapping.end())
ptr = it->second;
if (onlyActive)
{
// TODO: add flag `isActive` to LiveCellRefBase. Return empty Ptr if the flag is not set.
// Needed because in multiplayer mode inactive objects will not be synchronized, so will likely be out of date.
}
else
{
// TODO: If Ptr is empty then try to load the object from esp/esm.
}
return ptr;
}
ObjectId ObjectRegistry::registerPtr(const MWWorld::Ptr& ptr)
{
ObjectId id = ptr.getCellRef().getOrAssignRefNum(mLastAssignedId);
mChanged = true;
mObjectMapping[id] = ptr;
return id;
}
ObjectId ObjectRegistry::deregisterPtr(const MWWorld::Ptr& ptr)
{
ObjectId id = getId(ptr);
mChanged = true;
mObjectMapping.erase(id);
return id;
}
}

View File

@ -0,0 +1,103 @@
#ifndef MWLUA_OBJECT_H
#define MWLUA_OBJECT_H
#include <components/esm/cellref.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/ptr.hpp"
namespace MWLua
{
// ObjectId is a unique identifier of a game object.
// It can change only if the order of content files was change.
using ObjectId = ESM::RefNum;
inline const ObjectId& getId(const MWWorld::Ptr& ptr) { return ptr.getCellRef().getRefNum(); }
std::string idToString(const ObjectId& id);
// Holds a mapping ObjectId -> MWWord::Ptr.
class ObjectRegistry
{
public:
ObjectRegistry() { mLastAssignedId.unset(); }
void update(); // Should be called every frame.
void clear(); // Should be called before starting or loading a new game.
ObjectId registerPtr(const MWWorld::Ptr& ptr);
ObjectId deregisterPtr(const MWWorld::Ptr& ptr);
// Returns Ptr by id. If object is not found, returns empty Ptr.
// If onlyActive = true, returns non-empty ptr only if it is registered and is in an active cell.
// If onlyActive = false, tries to load and register the object if it is not loaded yet.
// NOTE: `onlyActive` logic is not yet implemented.
MWWorld::Ptr getPtr(ObjectId id, bool onlyActive);
// Needed only for saving/loading.
const ObjectId& getLastAssignedId() const { return mLastAssignedId; }
void setLastAssignedId(ObjectId id) { mLastAssignedId = id; }
private:
friend class Object;
friend class GObject;
friend class LObject;
bool mChanged = false;
int64_t mUpdateCounter = 0;
std::map<ObjectId, MWWorld::Ptr> mObjectMapping;
ObjectId mLastAssignedId;
};
// Lua scripts can't use MWWorld::Ptr directly, because lifetime of a script can be longer than lifetime of Ptr.
// `GObject` and `LObject` are intended to be passed to Lua as a userdata.
// It automatically updates the underlying Ptr when needed.
class Object
{
public:
Object(ObjectId id, ObjectRegistry* reg) : mId(id), mObjectRegistry(reg) {}
virtual ~Object() {}
ObjectId id() const { return mId; }
std::string toString() const;
std::string_view type() const;
// Updates and returns the underlying Ptr. Throws an exception if object is not available.
const MWWorld::Ptr& ptr() const;
// Returns `true` if calling `ptr()` is safe.
bool isValid() const;
protected:
virtual void updatePtr() const = 0;
const ObjectId mId;
ObjectRegistry* mObjectRegistry;
mutable MWWorld::Ptr mPtr;
mutable int64_t mLastUpdate = -1;
};
// Used only in local scripts
class LObject : public Object
{
using Object::Object;
void updatePtr() const final { mPtr = mObjectRegistry->getPtr(mId, true); }
};
// Used only in global scripts
class GObject : public Object
{
using Object::Object;
void updatePtr() const final { mPtr = mObjectRegistry->getPtr(mId, false); }
};
using ObjectIdList = std::shared_ptr<std::vector<ObjectId>>;
template <typename Obj>
struct ObjectList { ObjectIdList mIds; };
using GObjectList = ObjectList<GObject>;
using LObjectList = ObjectList<LObject>;
}
#endif // MWLUA_OBJECT_H

View File

@ -0,0 +1,106 @@
#include "luabindings.hpp"
#include <components/lua/luastate.hpp>
#include "eventqueue.hpp"
#include "luamanagerimp.hpp"
namespace sol
{
template <>
struct is_automagical<MWLua::LObject> : std::false_type {};
template <>
struct is_automagical<MWLua::GObject> : std::false_type {};
template <>
struct is_automagical<MWLua::LObjectList> : std::false_type {};
template <>
struct is_automagical<MWLua::GObjectList> : std::false_type {};
}
namespace MWLua
{
template <class ObjectT>
static void registerObjectList(const std::string& prefix, const Context& context)
{
using ListT = ObjectList<ObjectT>;
sol::state& lua = context.mLua->sol();
ObjectRegistry* registry = context.mWorldView->getObjectRegistry();
sol::usertype<ListT> listT = lua.new_usertype<ListT>(prefix + "ObjectList");
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] = [registry](const ListT& list, size_t index)
{
if (index > 0 && index <= list.mIds->size())
return ObjectT((*list.mIds)[index - 1], registry);
else
throw std::runtime_error("Index out of range");
};
listT["ipairs"] = [registry](const ListT& list)
{
auto iter = [registry](const ListT& l, int64_t i) -> sol::optional<std::tuple<int64_t, ObjectT>>
{
if (i >= 0 && i < static_cast<int64_t>(l.mIds->size()))
return std::make_tuple(i + 1, ObjectT((*l.mIds)[i], registry));
else
return sol::nullopt;
};
return std::make_tuple(iter, list, 0);
};
}
template <class ObjectT>
static void addBasicBindings(sol::usertype<ObjectT>& objectT, const Context& context)
{
objectT["isValid"] = [](const ObjectT& o) { return o.isValid(); };
objectT["recordId"] = sol::readonly_property([](const ObjectT& o) -> std::string
{
return o.ptr().getCellRef().getRefId();
});
objectT["position"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f
{
return o.ptr().getRefData().getPosition().asVec3();
});
objectT["rotation"] = sol::readonly_property([](const ObjectT& o) -> osg::Vec3f
{
return o.ptr().getRefData().getPosition().asRotationVec3();
});
objectT["type"] = sol::readonly_property(&ObjectT::type);
objectT[sol::meta_function::equal_to] = [](const ObjectT& a, const ObjectT& b) { return a.id() == b.id(); };
objectT[sol::meta_function::to_string] = &ObjectT::toString;
objectT["sendEvent"] = [context](const ObjectT& dest, std::string eventName, const sol::object& eventData)
{
context.mLocalEventQueue->push_back({dest.id(), std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)});
};
if constexpr (std::is_same_v<ObjectT, GObject>)
{ // Only for global scripts
objectT["addScript"] = [luaManager=context.mLuaManager](const GObject& object, const std::string& path)
{
luaManager->addLocalScript(object.ptr(), path);
};
}
}
template <class ObjectT>
static void initObjectBindings(const std::string& prefix, const Context& context)
{
sol::usertype<ObjectT> objectT = context.mLua->sol().new_usertype<ObjectT>(prefix + "Object");
addBasicBindings<ObjectT>(objectT, context);
registerObjectList<ObjectT>(prefix, context);
}
void initObjectBindingsForLocalScripts(const Context& context)
{
initObjectBindings<LObject>("L", context);
}
void initObjectBindingsForGlobalScripts(const Context& context)
{
initObjectBindings<GObject>("G", context);
}
}

View File

@ -0,0 +1,25 @@
#ifndef MWLUA_PLAYERSCRIPTS_H
#define MWLUA_PLAYERSCRIPTS_H
#include "localscripts.hpp"
namespace MWLua
{
class PlayerScripts : public LocalScripts
{
public:
PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj)
{
registerEngineHandlers({&mKeyPressHandlers});
}
void keyPress(int sym, int mod) { callEngineHandlers(mKeyPressHandlers, sym, mod); }
private:
EngineHandlerList mKeyPressHandlers{"onKeyPress"};
};
}
#endif // MWLUA_PLAYERSCRIPTS_H

View File

@ -0,0 +1,64 @@
#include "userdataserializer.hpp"
#include <components/lua/serialization.hpp>
#include <components/misc/endianness.hpp>
#include "object.hpp"
namespace MWLua
{
class Serializer final : public LuaUtil::UserdataSerializer
{
public:
explicit Serializer(bool localSerializer, ObjectRegistry* registry)
: mLocalSerializer(localSerializer), mObjectRegistry(registry) {}
private:
// Appends serialized sol::userdata to the end of BinaryData.
// Returns false if this type of userdata is not supported by this serializer.
bool serialize(LuaUtil::BinaryData& out, const sol::userdata& data) const override
{
if (data.is<GObject>() || data.is<LObject>())
{
ObjectId id = data.as<Object>().id();
static_assert(sizeof(ObjectId) == 8);
id.mIndex = Misc::toLittleEndian(id.mIndex);
id.mContentFile = Misc::toLittleEndian(id.mContentFile);
append(out, "o", &id, sizeof(ObjectId));
return true;
}
return false;
}
// Deserializes userdata of type "typeName" from binaryData. Should push the result on stack using sol::stack::push.
// Returns false if this type is not supported by this serializer.
bool deserialize(std::string_view typeName, std::string_view binaryData, sol::state& lua) const override
{
if (typeName == "o")
{
if (binaryData.size() != sizeof(ObjectId))
throw std::runtime_error("Incorrect serialization format. Size of ObjectId doesn't match.");
ObjectId id;
std::memcpy(&id, binaryData.data(), sizeof(ObjectId));
id.mIndex = Misc::fromLittleEndian(id.mIndex);
id.mContentFile = Misc::fromLittleEndian(id.mContentFile);
if (mLocalSerializer)
sol::stack::push<LObject>(lua, LObject(id, mObjectRegistry));
else
sol::stack::push<GObject>(lua, GObject(id, mObjectRegistry));
return true;
}
return false;
}
bool mLocalSerializer;
ObjectRegistry* mObjectRegistry;
};
std::unique_ptr<LuaUtil::UserdataSerializer> createUserdataSerializer(bool local, ObjectRegistry* registry)
{
return std::make_unique<Serializer>(local, registry);
}
}

View File

@ -0,0 +1,19 @@
#ifndef MWLUA_USERDATASERIALIZER_H
#define MWLUA_USERDATASERIALIZER_H
#include "object.hpp"
namespace LuaUtil
{
class UserdataSerializer;
}
namespace MWLua
{
// UserdataSerializer is an extension for components/lua/serialization.hpp
// Needed to serialize references to objects.
// If local=true, then during deserialization creates LObject, otherwise creates GObject.
std::unique_ptr<LuaUtil::UserdataSerializer> createUserdataSerializer(bool local, ObjectRegistry* registry);
}
#endif // MWLUA_USERDATASERIALIZER_H

View File

@ -0,0 +1,68 @@
#include "worldview.hpp"
#include "../mwworld/class.hpp"
namespace MWLua
{
void WorldView::update()
{
mObjectRegistry.update();
mActorsInScene.updateList();
mItemsInScene.updateList();
}
void WorldView::clear()
{
mObjectRegistry.clear();
mActorsInScene.clear();
mItemsInScene.clear();
}
void WorldView::objectAddedToScene(const MWWorld::Ptr& ptr)
{
if (ptr.getClass().isActor())
addToGroup(mActorsInScene, ptr);
else
addToGroup(mItemsInScene, ptr);
}
void WorldView::objectRemovedFromScene(const MWWorld::Ptr& ptr)
{
if (ptr.getClass().isActor())
removeFromGroup(mActorsInScene, ptr);
else
removeFromGroup(mItemsInScene, ptr);
}
void WorldView::ObjectGroup::updateList()
{
if (mChanged)
{
mList->clear();
for (const ObjectId& id : mSet)
mList->push_back(id);
mChanged = false;
}
}
void WorldView::ObjectGroup::clear()
{
mChanged = false;
mList->clear();
mSet.clear();
}
void WorldView::addToGroup(ObjectGroup& group, const MWWorld::Ptr& ptr)
{
group.mSet.insert(getId(ptr));
group.mChanged = true;
}
void WorldView::removeFromGroup(ObjectGroup& group, const MWWorld::Ptr& ptr)
{
group.mSet.erase(getId(ptr));
group.mChanged = true;
}
}

View File

@ -0,0 +1,47 @@
#ifndef MWLUA_WORLDVIEW_H
#define MWLUA_WORLDVIEW_H
#include "object.hpp"
namespace MWLua
{
// Tracks all used game objects.
class WorldView
{
public:
void update(); // Should be called every frame.
void clear(); // Should be called every time before starting or loading a new game.
ObjectIdList getActorsInScene() const { return mActorsInScene.mList; }
ObjectIdList getItemsInScene() const { return mItemsInScene.mList; }
ObjectRegistry* getObjectRegistry() { return &mObjectRegistry; }
void objectUnloaded(const MWWorld::Ptr& ptr) { mObjectRegistry.deregisterPtr(ptr); }
void objectAddedToScene(const MWWorld::Ptr& ptr);
void objectRemovedFromScene(const MWWorld::Ptr& ptr);
private:
struct ObjectGroup
{
void updateList();
void clear();
bool mChanged = false;
ObjectIdList mList = std::make_shared<std::vector<ObjectId>>();
std::set<ObjectId> mSet;
};
void addToGroup(ObjectGroup& group, const MWWorld::Ptr& ptr);
void removeFromGroup(ObjectGroup& group, const MWWorld::Ptr& ptr);
ObjectRegistry mObjectRegistry;
ObjectGroup mActorsInScene;
ObjectGroup mItemsInScene;
};
}
#endif // MWLUA_WORLDVIEW_H

View File

@ -8,6 +8,8 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwlua/localscripts.hpp"
namespace
{
enum RefDataFlags
@ -21,6 +23,12 @@ enum RefDataFlags
namespace MWWorld
{
void RefData::setLuaScripts(std::unique_ptr<MWLua::LocalScripts>&& scripts)
{
mChanged = true;
mLuaScripts = std::move(scripts);
}
void RefData::copy (const RefData& refData)
{
mBaseNode = refData.mBaseNode;
@ -36,12 +44,14 @@ namespace MWWorld
mAnimationState = refData.mAnimationState;
mCustomData = refData.mCustomData ? refData.mCustomData->clone() : nullptr;
mLuaScripts = refData.mLuaScripts;
}
void RefData::cleanup()
{
mBaseNode = nullptr;
mCustomData = nullptr;
mLuaScripts = nullptr;
}
RefData::RefData()

View File

@ -22,6 +22,11 @@ namespace ESM
struct ObjectState;
}
namespace MWLua
{
class LocalScripts;
}
namespace MWWorld
{
@ -32,6 +37,7 @@ namespace MWWorld
SceneUtil::PositionAttitudeTransform* mBaseNode;
MWScript::Locals mLocals;
std::shared_ptr<MWLua::LocalScripts> mLuaScripts;
/// separate delete flag used for deletion by a content file
/// @note not stored in the save game file.
@ -96,6 +102,9 @@ namespace MWWorld
void setLocals (const ESM::Script& script);
MWLua::LocalScripts* getLuaScripts() { return mLuaScripts.get(); }
void setLuaScripts(std::unique_ptr<MWLua::LocalScripts>&&);
void setCount (int count);
///< Set object count (an object pile is a simple object with a count >1).
///