1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-21 05:39:42 +00:00

Remove LuaManager::Action

This commit is contained in:
Petr Mikheev 2023-04-22 14:24:48 +02:00
parent 4562b8c06b
commit 003f611bdb
6 changed files with 132 additions and 272 deletions

View file

@ -206,12 +206,12 @@ namespace MWLua
windowManager->printToConsole(msg, "#" + color.toHex());
mInGameConsoleMessages.clear();
for (std::unique_ptr<Action>& action : mActionQueue)
action->safeApply();
for (DelayedAction& action : mActionQueue)
action.apply();
mActionQueue.clear();
if (mTeleportPlayerAction)
mTeleportPlayerAction->safeApply();
mTeleportPlayerAction->apply();
mTeleportPlayerAction.reset();
}
@ -465,22 +465,24 @@ namespace MWLua
"No Lua handlers for console\n", MWBase::WindowManager::sConsoleColor_Error);
}
LuaManager::Action::Action(LuaUtil::LuaState* state)
LuaManager::DelayedAction::DelayedAction(LuaUtil::LuaState* state, std::function<void()> fn, std::string_view name)
: mFn(std::move(fn))
, mName(name)
{
static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua");
if (luaDebug)
mCallerTraceback = state->debugTraceback();
}
void LuaManager::Action::safeApply() const
void LuaManager::DelayedAction::apply() const
{
try
{
apply();
mFn();
}
catch (const std::exception& e)
{
Log(Debug::Error) << "Error in " << this->toString() << ": " << e.what();
Log(Debug::Error) << "Error in DelayedAction " << mName << ": " << e.what();
if (mCallerTraceback.empty())
Log(Debug::Error) << "Set 'lua_debug=true' in settings.cfg to enable action tracebacks";
@ -489,35 +491,14 @@ namespace MWLua
}
}
namespace
{
class FunctionAction final : public LuaManager::Action
{
public:
FunctionAction(LuaUtil::LuaState* state, std::function<void()> fn, std::string_view name)
: Action(state)
, mFn(std::move(fn))
, mName(name)
{
}
void apply() const override { mFn(); }
std::string toString() const override { return "FunctionAction " + mName; }
private:
std::function<void()> mFn;
std::string mName;
};
}
void LuaManager::addAction(std::function<void()> action, std::string_view name)
{
mActionQueue.push_back(std::make_unique<FunctionAction>(&mLua, std::move(action), name));
mActionQueue.emplace_back(&mLua, std::move(action), name);
}
void LuaManager::addTeleportPlayerAction(std::function<void()> action)
{
mTeleportPlayerAction = std::make_unique<FunctionAction>(&mLua, std::move(action), "TeleportPlayer");
mTeleportPlayerAction = DelayedAction(&mLua, std::move(action), "TeleportPlayer");
}
void LuaManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const

View file

@ -86,24 +86,8 @@ namespace MWLua
}
// Some changes to the game world can not be done from the scripting thread (because it runs in parallel with
// OSG Cull), so we need to queue it and apply from the main thread. All such changes should be implemented as
// classes inherited from MWLua::Action.
class Action
{
public:
Action(LuaUtil::LuaState* state);
virtual ~Action() {}
void safeApply() const;
virtual void apply() const = 0;
virtual std::string toString() const = 0;
private:
std::string mCallerTraceback;
};
// OSG Cull), so we need to queue it and apply from the main thread.
void addAction(std::function<void()> action, std::string_view name = "");
void addAction(std::unique_ptr<Action>&& action) { mActionQueue.push_back(std::move(action)); }
void addTeleportPlayerAction(std::function<void()> action);
// Saving
@ -183,8 +167,19 @@ namespace MWLua
std::vector<CallbackWithData> mQueuedCallbacks;
// Queued actions that should be done in main thread. Processed by applyQueuedChanges().
std::vector<std::unique_ptr<Action>> mActionQueue;
std::unique_ptr<Action> mTeleportPlayerAction;
class DelayedAction
{
public:
DelayedAction(LuaUtil::LuaState* state, std::function<void()> fn, std::string_view name);
void apply() const;
private:
std::string mCallerTraceback;
std::function<void()> mFn;
std::string mName;
};
std::vector<DelayedAction> mActionQueue;
std::optional<DelayedAction> mTeleportPlayerAction;
std::vector<std::string> mUIMessages;
std::vector<std::pair<std::string, Misc::Color>> mInGameConsoleMessages;

View file

@ -5,39 +5,6 @@
#include "luamanagerimp.hpp"
namespace
{
template <class T>
class SetUniformShaderAction final : public MWLua::LuaManager::Action
{
public:
SetUniformShaderAction(
LuaUtil::LuaState* state, std::shared_ptr<fx::Technique> shader, const std::string& name, const T& value)
: MWLua::LuaManager::Action(state)
, mShader(std::move(shader))
, mName(name)
, mValue(value)
{
}
void apply() const override
{
MWBase::Environment::get().getWorld()->getPostProcessor()->setUniform(mShader, mName, mValue);
}
std::string toString() const override
{
return std::string("SetUniformShaderAction shader=") + (mShader ? mShader->getName() : "nil")
+ std::string("uniform=") + (mShader ? mName : "nil");
}
private:
std::shared_ptr<fx::Technique> mShader;
std::string mName;
T mValue;
};
}
namespace MWLua
{
struct Shader;
@ -84,7 +51,10 @@ namespace MWLua
{
return [context](const Shader& shader, const std::string& name, const T& value) {
context.mLuaManager->addAction(
std::make_unique<SetUniformShaderAction<T>>(context.mLua, shader.mShader, name, value));
[&] {
MWBase::Environment::get().getWorld()->getPostProcessor()->setUniform(shader.mShader, name, value);
},
"SetUniformShaderAction");
};
}
@ -114,7 +84,10 @@ namespace MWLua
}
context.mLuaManager->addAction(
std::make_unique<SetUniformShaderAction<std::vector<T>>>(context.mLua, shader.mShader, name, values));
[&] {
MWBase::Environment::get().getWorld()->getPostProcessor()->setUniform(shader.mShader, name, values);
},
"SetUniformShaderAction");
};
}

View file

@ -55,29 +55,17 @@ namespace
namespace MWLua
{
namespace
static void addStatUpdateAction(MWLua::LuaManager* manager, const SelfObject& obj)
{
class StatUpdateAction final : public LuaManager::Action
{
ObjectId mId;
public:
StatUpdateAction(LuaUtil::LuaState* state, ObjectId id)
: Action(state)
, mId(id)
{
}
void apply() const override
{
LObject obj(mId);
if (!obj.mStatsCache.empty())
return; // was already added before
manager->addAction(
[obj = Object(obj)] {
LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts();
if (scripts)
scripts->applyStatsCache();
}
std::string toString() const override { return "StatUpdateAction"; }
};
},
"StatUpdateAction");
}
class LevelStat
@ -99,8 +87,7 @@ namespace MWLua
void setCurrent(const Context& context, const sol::object& value) const
{
SelfObject* obj = mObject.asSelfObject();
if (obj->mStatsCache.empty())
context.mLuaManager->addAction(std::make_unique<StatUpdateAction>(context.mLua, obj->id()));
addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &LevelStat::setValue, 0, "current" }] = value;
}
@ -158,8 +145,7 @@ namespace MWLua
void cache(const Context& context, std::string_view prop, const sol::object& value) const
{
SelfObject* obj = mObject.asSelfObject();
if (obj->mStatsCache.empty())
context.mLuaManager->addAction(std::make_unique<StatUpdateAction>(context.mLua, obj->id()));
addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &DynamicStat::setValue, mIndex, prop }] = value;
}
@ -217,8 +203,7 @@ namespace MWLua
void cache(const Context& context, std::string_view prop, const sol::object& value) const
{
SelfObject* obj = mObject.asSelfObject();
if (obj->mStatsCache.empty())
context.mLuaManager->addAction(std::make_unique<StatUpdateAction>(context.mLua, obj->id()));
addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &AttributeStat::setValue, mIndex, prop }] = value;
}
@ -302,8 +287,7 @@ namespace MWLua
void cache(const Context& context, std::string_view prop, const sol::object& value) const
{
SelfObject* obj = mObject.asSelfObject();
if (obj->mStatsCache.empty())
context.mLuaManager->addAction(std::make_unique<StatUpdateAction>(context.mLua, obj->id()));
addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &SkillStat::setValue, mIndex, prop }] = value;
}

View file

@ -18,107 +18,84 @@
namespace MWLua
{
namespace
using EquipmentItem = std::variant<std::string, ObjectId>;
using Equipment = std::map<int, EquipmentItem>;
static void setEquipment(const MWWorld::Ptr& actor, const Equipment& equipment)
{
class SetEquipmentAction final : public LuaManager::Action
{
public:
using Item = std::variant<std::string, ObjectId>; // recordId or ObjectId
using Equipment = std::map<int, Item>; // slot to item
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
std::array<bool, MWWorld::InventoryStore::Slots> usedSlots;
std::fill(usedSlots.begin(), usedSlots.end(), false);
SetEquipmentAction(LuaUtil::LuaState* state, ObjectId actor, Equipment equipment)
: Action(state)
, mActor(actor)
, mEquipment(std::move(equipment))
static constexpr int anySlot = -1;
auto tryEquipToSlot = [&store, &usedSlots](int slot, const EquipmentItem& item) -> bool {
auto old_it = slot != anySlot ? store.getSlot(slot) : store.end();
MWWorld::Ptr itemPtr;
if (std::holds_alternative<ObjectId>(item))
{
}
void apply() const override
{
MWWorld::Ptr actor = MWBase::Environment::get().getWorldModel()->getPtr(mActor);
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
std::array<bool, MWWorld::InventoryStore::Slots> usedSlots;
std::fill(usedSlots.begin(), usedSlots.end(), false);
static constexpr int anySlot = -1;
auto tryEquipToSlot = [&store, &usedSlots](int slot, const Item& item) -> bool {
auto old_it = slot != anySlot ? store.getSlot(slot) : store.end();
MWWorld::Ptr itemPtr;
if (std::holds_alternative<ObjectId>(item))
{
itemPtr = MWBase::Environment::get().getWorldModel()->getPtr(std::get<ObjectId>(item));
if (old_it != store.end() && *old_it == itemPtr)
return true; // already equipped
if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0
|| itemPtr.getContainerStore() != static_cast<const MWWorld::ContainerStore*>(&store))
{
Log(Debug::Warning)
<< "Object" << std::get<ObjectId>(item).toString() << " is not in inventory";
return false;
}
}
else
{
const ESM::RefId& recordId = ESM::RefId::stringRefId(std::get<std::string>(item));
if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId)
return true; // already equipped
itemPtr = store.search(recordId);
if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0)
{
Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory";
return false;
}
}
auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr);
bool requestedSlotIsAllowed
= std::find(allowedSlots.begin(), allowedSlots.end(), slot) != allowedSlots.end();
if (!requestedSlotIsAllowed)
{
auto firstAllowed = std::find_if(
allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; });
if (firstAllowed == allowedSlots.end())
{
Log(Debug::Warning) << "No suitable slot for " << itemPtr.toString();
return false;
}
slot = *firstAllowed;
}
// TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search.
MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr);
if (it == store.end()) // should never happen
throw std::logic_error("Item not found in container");
store.equip(slot, it);
return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was
// changed
};
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
itemPtr = MWBase::Environment::get().getWorldModel()->getPtr(std::get<ObjectId>(item));
if (old_it != store.end() && *old_it == itemPtr)
return true; // already equipped
if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0
|| itemPtr.getContainerStore() != static_cast<const MWWorld::ContainerStore*>(&store))
{
auto old_it = store.getSlot(slot);
auto new_it = mEquipment.find(slot);
if (new_it == mEquipment.end())
{
if (old_it != store.end())
store.unequipSlot(slot);
continue;
}
if (tryEquipToSlot(slot, new_it->second))
usedSlots[slot] = true;
Log(Debug::Warning) << "Object" << std::get<ObjectId>(item).toString() << " is not in inventory";
return false;
}
}
else
{
const ESM::RefId& recordId = ESM::RefId::stringRefId(std::get<std::string>(item));
if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId)
return true; // already equipped
itemPtr = store.search(recordId);
if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0)
{
Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory";
return false;
}
for (const auto& [slot, item] : mEquipment)
if (slot >= MWWorld::InventoryStore::Slots)
tryEquipToSlot(anySlot, item);
}
std::string toString() const override { return "SetEquipmentAction"; }
auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr);
bool requestedSlotIsAllowed
= std::find(allowedSlots.begin(), allowedSlots.end(), slot) != allowedSlots.end();
if (!requestedSlotIsAllowed)
{
auto firstAllowed
= std::find_if(allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; });
if (firstAllowed == allowedSlots.end())
{
Log(Debug::Warning) << "No suitable slot for " << itemPtr.toString();
return false;
}
slot = *firstAllowed;
}
private:
ObjectId mActor;
Equipment mEquipment;
// TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search.
MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr);
if (it == store.end()) // should never happen
throw std::logic_error("Item not found in container");
store.equip(slot, it);
return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed
};
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
{
auto old_it = store.getSlot(slot);
auto new_it = equipment.find(slot);
if (new_it == equipment.end())
{
if (old_it != store.end())
store.unequipSlot(slot);
continue;
}
if (tryEquipToSlot(slot, new_it->second))
usedSlots[slot] = true;
}
for (const auto& [slot, item] : equipment)
if (slot >= MWWorld::InventoryStore::Slots)
tryEquipToSlot(anySlot, item);
}
void addActorBindings(sol::table actor, const Context& context)
@ -263,13 +240,14 @@ namespace MWLua
return store.isEquipped(item.ptr());
};
actor["setEquipment"] = [context](const SelfObject& obj, const sol::table& equipment) {
if (!obj.ptr().getClass().hasInventoryStore(obj.ptr()))
const MWWorld::Ptr& ptr = obj.ptr();
if (!ptr.getClass().hasInventoryStore(ptr))
{
if (!equipment.empty())
throw std::runtime_error(obj.toString() + " has no equipment slots");
return;
}
SetEquipmentAction::Equipment eqp;
Equipment eqp;
for (auto& [key, value] : equipment)
{
int slot = key.as<int>();
@ -279,7 +257,7 @@ namespace MWLua
eqp[slot] = value.as<std::string>();
}
context.mLuaManager->addAction(
std::make_unique<SetEquipmentAction>(context.mLua, obj.id(), std::move(eqp)));
[obj = Object(ptr), eqp = std::move(eqp)] { setEquipment(obj.ptr(), eqp); }, "SetEquipmentAction");
};
actor["getPathfindingAgentBounds"] = [](sol::this_state lua, const LObject& o) {
const DetourNavigator::AgentBounds agentBounds

View file

@ -20,71 +20,20 @@ namespace MWLua
{
namespace
{
class UiAction final : public LuaManager::Action
template <typename Fn>
void wrapAction(const std::shared_ptr<LuaUi::Element>& element, Fn&& fn)
{
public:
enum Type
{
CREATE = 0,
UPDATE,
DESTROY,
};
UiAction(Type type, std::shared_ptr<LuaUi::Element> element, LuaUtil::LuaState* state)
: Action(state)
, mType{ type }
, mElement{ std::move(element) }
try
{
fn();
}
void apply() const override
catch (...)
{
try
{
switch (mType)
{
case CREATE:
mElement->create();
break;
case UPDATE:
mElement->update();
break;
case DESTROY:
mElement->destroy();
break;
}
}
catch (std::exception&)
{
// prevent any actions on a potentially corrupted widget
mElement->mRoot = nullptr;
throw;
}
// prevent any actions on a potentially corrupted widget
element->mRoot = nullptr;
throw;
}
std::string toString() const override
{
std::string result;
switch (mType)
{
case CREATE:
result += "Create";
break;
case UPDATE:
result += "Update";
break;
case DESTROY:
result += "Destroy";
break;
}
result += " UI";
return result;
}
private:
Type mType;
std::shared_ptr<LuaUi::Element> mElement;
};
}
// Lua arrays index from 1
inline size_t fromLuaIndex(size_t i)
@ -102,17 +51,17 @@ namespace MWLua
auto element = context.mLua->sol().new_usertype<LuaUi::Element>("Element");
element["layout"] = sol::property([](LuaUi::Element& element) { return element.mLayout; },
[](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; });
element["update"] = [context](const std::shared_ptr<LuaUi::Element>& element) {
element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) {
if (element->mDestroy || element->mUpdate)
return;
element->mUpdate = true;
context.mLuaManager->addAction(std::make_unique<UiAction>(UiAction::UPDATE, element, context.mLua));
luaManager->addAction([element] { wrapAction(element, [&] { element->update(); }); }, "Update UI");
};
element["destroy"] = [context](const std::shared_ptr<LuaUi::Element>& element) {
element["destroy"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) {
if (element->mDestroy)
return;
element->mDestroy = true;
context.mLuaManager->addAction(std::make_unique<UiAction>(UiAction::DESTROY, element, context.mLua));
luaManager->addAction([element] { wrapAction(element, [&] { element->destroy(); }); }, "Destroy UI");
};
sol::table api = context.mLua->newTable();
@ -144,9 +93,9 @@ namespace MWLua
}
};
api["content"] = LuaUi::loadContentConstructor(context.mLua);
api["create"] = [context](const sol::table& layout) {
api["create"] = [luaManager = context.mLuaManager](const sol::table& layout) {
auto element = LuaUi::Element::make(layout);
context.mLuaManager->addAction(std::make_unique<UiAction>(UiAction::CREATE, element, context.mLua));
luaManager->addAction([element] { wrapAction(element, [&] { element->create(); }); }, "Create UI");
return element;
};
api["updateAll"] = [context]() {