mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-02-13 12:40:04 +00:00
Merge branch 'lua' into 'master'
A few lua-related fixes Closes #6456 See merge request OpenMW/openmw!1478
This commit is contained in:
commit
78daf638ef
@ -296,7 +296,7 @@ bool OMW::Engine::frame(float frametime)
|
||||
|
||||
// Should be called after input manager update and before any change to the game world.
|
||||
// It applies to the game world queued changes from the previous frame.
|
||||
mLuaManager->synchronizedUpdate(mEnvironment.getWindowManager()->isGuiMode(), frametime);
|
||||
mLuaManager->synchronizedUpdate();
|
||||
|
||||
// update game state
|
||||
{
|
||||
@ -873,10 +873,8 @@ public:
|
||||
mThread = std::thread([this]{ threadBody(); });
|
||||
};
|
||||
|
||||
void allowUpdate(double dt)
|
||||
void allowUpdate()
|
||||
{
|
||||
mDt = dt;
|
||||
mIsGuiMode = mEngine->mEnvironment.getWindowManager()->isGuiMode();
|
||||
if (!mThread)
|
||||
return;
|
||||
{
|
||||
@ -918,7 +916,7 @@ private:
|
||||
const unsigned int frameNumber = viewer->getFrameStamp()->getFrameNumber();
|
||||
ScopedProfile<UserStatsType::Lua> profile(frameStart, frameNumber, *osg::Timer::instance(), *viewer->getViewerStats());
|
||||
|
||||
mEngine->mLuaManager->update(mIsGuiMode, mDt);
|
||||
mEngine->mLuaManager->update();
|
||||
}
|
||||
|
||||
void threadBody()
|
||||
@ -943,8 +941,6 @@ private:
|
||||
std::condition_variable mCV;
|
||||
bool mUpdateRequest = false;
|
||||
bool mJoinRequest = false;
|
||||
double mDt = 0;
|
||||
bool mIsGuiMode = false;
|
||||
std::optional<std::thread> mThread;
|
||||
};
|
||||
|
||||
@ -1057,7 +1053,7 @@ void OMW::Engine::go()
|
||||
|
||||
mEnvironment.getWorld()->updateWindowManager();
|
||||
|
||||
luaWorker.allowUpdate(dt); // if there is a separate Lua thread, it starts the update now
|
||||
luaWorker.allowUpdate(); // if there is a separate Lua thread, it starts the update now
|
||||
|
||||
mViewer->renderingTraversals();
|
||||
|
||||
|
@ -4,6 +4,14 @@
|
||||
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
namespace sol
|
||||
{
|
||||
template <>
|
||||
struct is_automagical<MWLua::LCell> : std::false_type {};
|
||||
template <>
|
||||
struct is_automagical<MWLua::GCell> : std::false_type {};
|
||||
}
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
|
||||
|
@ -54,7 +54,7 @@ namespace MWLua
|
||||
{
|
||||
return input->isControllerButtonPressed(static_cast<SDL_GameControllerButton>(button));
|
||||
};
|
||||
api["isMouseButtonPressed"] = [input](int button) -> bool
|
||||
api["isMouseButtonPressed"] = [](int button) -> bool
|
||||
{
|
||||
return SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(button);
|
||||
};
|
||||
|
@ -25,7 +25,7 @@ namespace MWLua
|
||||
{
|
||||
auto* lua = context.mLua;
|
||||
sol::table api(lua->sol(), sol::create);
|
||||
api["API_REVISION"] = 10;
|
||||
api["API_REVISION"] = 11;
|
||||
api["quit"] = [lua]()
|
||||
{
|
||||
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
|
||||
@ -37,6 +37,7 @@ namespace MWLua
|
||||
};
|
||||
api["getGameTimeInSeconds"] = [world=context.mWorldView]() { return world->getGameTimeInSeconds(); };
|
||||
api["getGameTimeInHours"] = [world=context.mWorldView]() { return world->getGameTimeInHours(); };
|
||||
api["isWorldPaused"] = [world=context.mWorldView]() { return world->isPaused(); };
|
||||
api["OBJECT_TYPE"] = definitionList(*lua,
|
||||
{
|
||||
"Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient",
|
||||
|
@ -78,11 +78,12 @@ namespace MWLua
|
||||
mInitialized = true;
|
||||
}
|
||||
|
||||
void LuaManager::update(bool paused, float dt)
|
||||
void LuaManager::update()
|
||||
{
|
||||
if (mPlayer.isEmpty())
|
||||
return; // The game is not started yet.
|
||||
|
||||
float frameDuration = MWBase::Environment::get().getFrameDuration();
|
||||
ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry();
|
||||
|
||||
MWWorld::Ptr newPlayerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
@ -101,9 +102,9 @@ namespace MWLua
|
||||
mGlobalEvents = std::vector<GlobalEvent>();
|
||||
mLocalEvents = std::vector<LocalEvent>();
|
||||
|
||||
if (!paused)
|
||||
if (!mWorldView.isPaused())
|
||||
{ // Update time and process timers
|
||||
double seconds = mWorldView.getGameTimeInSeconds() + dt;
|
||||
double seconds = mWorldView.getGameTimeInSeconds() + frameDuration;
|
||||
mWorldView.setGameTimeInSeconds(seconds);
|
||||
double hours = mWorldView.getGameTimeInHours();
|
||||
|
||||
@ -146,10 +147,10 @@ namespace MWLua
|
||||
}
|
||||
mLocalEngineEvents.clear();
|
||||
|
||||
if (!paused)
|
||||
if (!mWorldView.isPaused())
|
||||
{
|
||||
for (LocalScripts* scripts : mActiveLocalScripts)
|
||||
scripts->update(dt);
|
||||
scripts->update(frameDuration);
|
||||
}
|
||||
|
||||
// Engine handlers in global scripts
|
||||
@ -168,24 +169,25 @@ namespace MWLua
|
||||
mGlobalScripts.actorActive(GObject(id, objectRegistry));
|
||||
mActorAddedEvents.clear();
|
||||
|
||||
if (!paused)
|
||||
mGlobalScripts.update(dt);
|
||||
if (!mWorldView.isPaused())
|
||||
mGlobalScripts.update(frameDuration);
|
||||
}
|
||||
|
||||
void LuaManager::synchronizedUpdate(bool paused, float dt)
|
||||
void LuaManager::synchronizedUpdate()
|
||||
{
|
||||
if (mPlayer.isEmpty())
|
||||
return; // The game is not started yet.
|
||||
|
||||
// We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency.
|
||||
PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts());
|
||||
if (playerScripts && !paused)
|
||||
if (playerScripts && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu))
|
||||
{
|
||||
for (const auto& event : mInputEvents)
|
||||
playerScripts->processInputEvent(event);
|
||||
playerScripts->inputUpdate(dt);
|
||||
}
|
||||
mInputEvents.clear();
|
||||
if (playerScripts && !mWorldView.isPaused())
|
||||
playerScripts->inputUpdate(MWBase::Environment::get().getFrameDuration());
|
||||
|
||||
MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager();
|
||||
for (const std::string& message : mUIMessages)
|
||||
|
@ -29,10 +29,10 @@ namespace MWLua
|
||||
|
||||
// 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);
|
||||
void update();
|
||||
|
||||
// Called by engine.cpp from the main thread. Can use scene graph.
|
||||
void synchronizedUpdate(bool paused, float dt);
|
||||
void synchronizedUpdate();
|
||||
|
||||
// Available everywhere through the MWBase::LuaManager interface.
|
||||
// LuaManager queues these events and propagates to scripts on the next `update` call.
|
||||
|
@ -33,7 +33,7 @@ namespace MWLua
|
||||
|
||||
// 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
|
||||
bool deserialize(std::string_view typeName, std::string_view binaryData, lua_State* lua) const override
|
||||
{
|
||||
if (typeName == "o")
|
||||
{
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include <components/esm/esmwriter.hpp>
|
||||
#include <components/esm/loadcell.hpp>
|
||||
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include "../mwclass/container.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
@ -20,6 +22,7 @@ namespace MWLua
|
||||
mContainersInScene.updateList();
|
||||
mDoorsInScene.updateList();
|
||||
mItemsInScene.updateList();
|
||||
mPaused = MWBase::Environment::get().getWindowManager()->isGuiMode();
|
||||
}
|
||||
|
||||
void WorldView::clear()
|
||||
|
@ -19,6 +19,9 @@ namespace MWLua
|
||||
void update(); // Should be called every frame.
|
||||
void clear(); // Should be called every time before starting or loading a new game.
|
||||
|
||||
// Whether the world is paused (i.e. game time is not changing and actors don't move).
|
||||
bool isPaused() const { return mPaused; }
|
||||
|
||||
// Returns the number of seconds passed from the beginning of the game.
|
||||
double getGameTimeInSeconds() const { return mGameSeconds; }
|
||||
void setGameTimeInSeconds(double t) { mGameSeconds = t; }
|
||||
@ -74,6 +77,7 @@ namespace MWLua
|
||||
ObjectGroup mItemsInScene;
|
||||
|
||||
double mGameSeconds = 0;
|
||||
bool mPaused = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -121,14 +121,25 @@ namespace
|
||||
std::string serialized = LuaUtil::serialize(table);
|
||||
EXPECT_EQ(serialized.size(), 123);
|
||||
sol::table res_table = LuaUtil::deserialize(lua, serialized);
|
||||
sol::table res_readonly_table = LuaUtil::deserialize(lua, serialized, nullptr, true);
|
||||
|
||||
EXPECT_EQ(res_table.get<int>("aa"), 1);
|
||||
EXPECT_EQ(res_table.get<bool>("ab"), true);
|
||||
EXPECT_EQ(res_table.get<sol::table>("nested").get<int>("aa"), 2);
|
||||
EXPECT_EQ(res_table.get<sol::table>("nested").get<std::string>("bb"), "something");
|
||||
EXPECT_FLOAT_EQ(res_table.get<sol::table>("nested").get<double>(5), -0.5);
|
||||
EXPECT_EQ(res_table.get<osg::Vec2f>(1), osg::Vec2f(1, 2));
|
||||
EXPECT_EQ(res_table.get<osg::Vec2f>(2), osg::Vec2f(2, 1));
|
||||
for (auto t : {res_table, res_readonly_table})
|
||||
{
|
||||
EXPECT_EQ(t.get<int>("aa"), 1);
|
||||
EXPECT_EQ(t.get<bool>("ab"), true);
|
||||
EXPECT_EQ(t.get<sol::table>("nested").get<int>("aa"), 2);
|
||||
EXPECT_EQ(t.get<sol::table>("nested").get<std::string>("bb"), "something");
|
||||
EXPECT_FLOAT_EQ(t.get<sol::table>("nested").get<double>(5), -0.5);
|
||||
EXPECT_EQ(t.get<osg::Vec2f>(1), osg::Vec2f(1, 2));
|
||||
EXPECT_EQ(t.get<osg::Vec2f>(2), osg::Vec2f(2, 1));
|
||||
}
|
||||
|
||||
lua["t"] = res_table;
|
||||
lua["ro_t"] = res_readonly_table;
|
||||
EXPECT_NO_THROW(lua.safe_script("t.x = 5"));
|
||||
EXPECT_NO_THROW(lua.safe_script("t.nested.x = 5"));
|
||||
EXPECT_ERROR(lua.safe_script("ro_t.x = 5"), "userdata value");
|
||||
EXPECT_ERROR(lua.safe_script("ro_t.nested.x = 5"), "userdata value");
|
||||
}
|
||||
|
||||
struct TestStruct1 { double a, b; };
|
||||
@ -157,7 +168,7 @@ namespace
|
||||
return false;
|
||||
}
|
||||
|
||||
bool deserialize(std::string_view typeName, std::string_view binaryData, sol::state& lua) const override
|
||||
bool deserialize(std::string_view typeName, std::string_view binaryData, lua_State* lua) const override
|
||||
{
|
||||
if (typeName == "ts1")
|
||||
{
|
||||
|
@ -120,6 +120,9 @@ namespace
|
||||
EXPECT_FLOAT_EQ(get<float>(lua, "util.clamp(0.1, 0, 1.5)"), 0.1);
|
||||
EXPECT_FLOAT_EQ(get<float>(lua, "util.clamp(-0.1, 0, 1.5)"), 0);
|
||||
EXPECT_FLOAT_EQ(get<float>(lua, "util.clamp(2.1, 0, 1.5)"), 1.5);
|
||||
lua.safe_script("t = util.makeReadOnly({x = 1})");
|
||||
EXPECT_FLOAT_EQ(get<float>(lua, "t.x"), 1);
|
||||
EXPECT_ERROR(lua.safe_script("t.y = 2"), "userdata value");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -76,9 +76,8 @@ namespace LuaUtil
|
||||
|
||||
lua_State* lua = table.lua_state();
|
||||
table[sol::meta_function::index] = table;
|
||||
sol::stack::push(lua, std::move(table));
|
||||
lua_newuserdata(lua, 0);
|
||||
lua_pushvalue(lua, -2);
|
||||
sol::stack::push(lua, std::move(table));
|
||||
lua_setmetatable(lua, -2);
|
||||
return sol::stack::pop<sol::table>(lua);
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
#include <components/misc/endianness.hpp>
|
||||
|
||||
#include "luastate.hpp"
|
||||
|
||||
namespace LuaUtil
|
||||
{
|
||||
|
||||
@ -147,7 +149,8 @@ namespace LuaUtil
|
||||
throw std::runtime_error("Unknown Lua type.");
|
||||
}
|
||||
|
||||
static void deserializeImpl(sol::state& lua, std::string_view& binaryData, const UserdataSerializer* customSerializer)
|
||||
static void deserializeImpl(lua_State* lua, std::string_view& binaryData,
|
||||
const UserdataSerializer* customSerializer, bool readOnly)
|
||||
{
|
||||
if (binaryData.empty())
|
||||
throw std::runtime_error("Unexpected end of serialized data.");
|
||||
@ -176,22 +179,22 @@ namespace LuaUtil
|
||||
if (type & SHORT_STRING_FLAG)
|
||||
{
|
||||
size_t size = type & 0x1f;
|
||||
sol::stack::push<std::string_view>(lua.lua_state(), binaryData.substr(0, size));
|
||||
sol::stack::push<std::string_view>(lua, binaryData.substr(0, size));
|
||||
binaryData = binaryData.substr(size);
|
||||
return;
|
||||
}
|
||||
switch (static_cast<SerializedType>(type))
|
||||
{
|
||||
case SerializedType::NUMBER:
|
||||
sol::stack::push<double>(lua.lua_state(), getValue<double>(binaryData));
|
||||
sol::stack::push<double>(lua, getValue<double>(binaryData));
|
||||
return;
|
||||
case SerializedType::BOOLEAN:
|
||||
sol::stack::push<bool>(lua.lua_state(), getValue<char>(binaryData) != 0);
|
||||
sol::stack::push<bool>(lua, getValue<char>(binaryData) != 0);
|
||||
return;
|
||||
case SerializedType::LONG_STRING:
|
||||
{
|
||||
uint32_t size = getValue<uint32_t>(binaryData);
|
||||
sol::stack::push<std::string_view>(lua.lua_state(), binaryData.substr(0, size));
|
||||
sol::stack::push<std::string_view>(lua, binaryData.substr(0, size));
|
||||
binaryData = binaryData.substr(size);
|
||||
return;
|
||||
}
|
||||
@ -200,13 +203,15 @@ namespace LuaUtil
|
||||
lua_createtable(lua, 0, 0);
|
||||
while (!binaryData.empty() && binaryData[0] != char(SerializedType::TABLE_END))
|
||||
{
|
||||
deserializeImpl(lua, binaryData, customSerializer);
|
||||
deserializeImpl(lua, binaryData, customSerializer);
|
||||
deserializeImpl(lua, binaryData, customSerializer, readOnly);
|
||||
deserializeImpl(lua, binaryData, customSerializer, readOnly);
|
||||
lua_settable(lua, -3);
|
||||
}
|
||||
if (binaryData.empty())
|
||||
throw std::runtime_error("Unexpected end of serialized data.");
|
||||
binaryData = binaryData.substr(1);
|
||||
if (readOnly)
|
||||
sol::stack::push(lua, makeReadOnly(sol::stack::pop<sol::table>(lua)));
|
||||
return;
|
||||
}
|
||||
case SerializedType::TABLE_END:
|
||||
@ -215,7 +220,7 @@ namespace LuaUtil
|
||||
{
|
||||
float x = getValue<float>(binaryData);
|
||||
float y = getValue<float>(binaryData);
|
||||
sol::stack::push<osg::Vec2f>(lua.lua_state(), osg::Vec2f(x, y));
|
||||
sol::stack::push<osg::Vec2f>(lua, osg::Vec2f(x, y));
|
||||
return;
|
||||
}
|
||||
case SerializedType::VEC3:
|
||||
@ -223,7 +228,7 @@ namespace LuaUtil
|
||||
float x = getValue<float>(binaryData);
|
||||
float y = getValue<float>(binaryData);
|
||||
float z = getValue<float>(binaryData);
|
||||
sol::stack::push<osg::Vec3f>(lua.lua_state(), osg::Vec3f(x, y, z));
|
||||
sol::stack::push<osg::Vec3f>(lua, osg::Vec3f(x, y, z));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -240,7 +245,8 @@ namespace LuaUtil
|
||||
return res;
|
||||
}
|
||||
|
||||
sol::object deserialize(sol::state& lua, std::string_view binaryData, const UserdataSerializer* customSerializer)
|
||||
sol::object deserialize(lua_State* lua, std::string_view binaryData,
|
||||
const UserdataSerializer* customSerializer, bool readOnly)
|
||||
{
|
||||
if (binaryData.empty())
|
||||
return sol::nil;
|
||||
@ -248,10 +254,10 @@ namespace LuaUtil
|
||||
throw std::runtime_error("Incorrect version of Lua serialization format: " +
|
||||
std::to_string(static_cast<unsigned>(binaryData[0])));
|
||||
binaryData = binaryData.substr(1);
|
||||
deserializeImpl(lua, binaryData, customSerializer);
|
||||
deserializeImpl(lua, binaryData, customSerializer, readOnly);
|
||||
if (!binaryData.empty())
|
||||
throw std::runtime_error("Unexpected data after serialized object");
|
||||
return sol::stack::pop<sol::object>(lua.lua_state());
|
||||
return sol::stack::pop<sol::object>(lua);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
#ifndef COMPONENTS_LUA_SERIALIZATION_H
|
||||
#define COMPONENTS_LUA_SERIALIZATION_H
|
||||
|
||||
#include <limits> // missing from sol/sol.hpp
|
||||
#include <sol/sol.hpp>
|
||||
|
||||
namespace LuaUtil
|
||||
@ -21,14 +20,15 @@ namespace LuaUtil
|
||||
|
||||
// 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.
|
||||
virtual bool deserialize(std::string_view typeName, std::string_view binaryData, sol::state&) const = 0;
|
||||
virtual bool deserialize(std::string_view typeName, std::string_view binaryData, lua_State*) const = 0;
|
||||
|
||||
protected:
|
||||
static void append(BinaryData&, std::string_view typeName, const void* data, size_t dataSize);
|
||||
};
|
||||
|
||||
BinaryData serialize(const sol::object&, const UserdataSerializer* customSerializer = nullptr);
|
||||
sol::object deserialize(sol::state& lua, std::string_view binaryData, const UserdataSerializer* customSerializer = nullptr);
|
||||
sol::object deserialize(lua_State* lua, std::string_view binaryData,
|
||||
const UserdataSerializer* customSerializer = nullptr, bool readOnly = false);
|
||||
|
||||
}
|
||||
|
||||
|
@ -175,6 +175,7 @@ namespace LuaUtil
|
||||
util["clamp"] = [](float value, float from, float to) { return std::clamp(value, from, to); };
|
||||
// NOTE: `util["clamp"] = std::clamp<float>` causes error 'AddressSanitizer: stack-use-after-scope'
|
||||
util["normalizeAngle"] = &Misc::normalizeAngle;
|
||||
util["makeReadOnly"] = &makeReadOnly;
|
||||
|
||||
return util;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
local camera = require('openmw.camera')
|
||||
local core = require('openmw.core')
|
||||
local input = require('openmw.input')
|
||||
local settings = require('openmw.settings')
|
||||
local util = require('openmw.util')
|
||||
@ -225,6 +226,7 @@ return {
|
||||
onUpdate = onUpdate,
|
||||
onInputUpdate = onInputUpdate,
|
||||
onInputAction = function(action)
|
||||
if core.isWorldPaused() then return end
|
||||
if action == input.ACTION.ZoomIn then
|
||||
zoom(10)
|
||||
elseif action == input.ACTION.ZoomOut then
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Game time in seconds.
|
||||
-- The number of seconds in the game world, passed from starting a new game.
|
||||
-- The number of seconds passed in the game world since starting a new game.
|
||||
-- @function [parent=#core] getGameTimeInSeconds
|
||||
-- @return #number
|
||||
|
||||
@ -32,6 +32,11 @@
|
||||
-- @function [parent=#core] getGameTimeInHours
|
||||
-- @return #number
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Whether the world is paused (onUpdate doesn't work when the world is paused).
|
||||
-- @function [parent=#core] isWorldPaused
|
||||
-- @return #boolean
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- @type OBJECT_TYPE
|
||||
|
@ -19,6 +19,12 @@
|
||||
-- @param #number angle Angle in radians
|
||||
-- @return #number Angle in range `[-pi, pi]`
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Makes a table read only.
|
||||
-- @function [parent=#util] makeReadOnly
|
||||
-- @param #table table Any table.
|
||||
-- @return #table The same table wrapped with read only userdata.
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Immutable 2D vector
|
||||
|
Loading…
x
Reference in New Issue
Block a user