mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-27 00:56:39 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			338 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			338 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "inputactions.hpp"
 | |
| 
 | |
| #include <queue>
 | |
| #include <set>
 | |
| 
 | |
| #include <components/debug/debuglog.hpp>
 | |
| #include <components/misc/strings/format.hpp>
 | |
| 
 | |
| #include "luastate.hpp"
 | |
| 
 | |
| namespace LuaUtil
 | |
| {
 | |
|     namespace InputAction
 | |
|     {
 | |
|         namespace
 | |
|         {
 | |
|             std::string_view typeName(Type actionType)
 | |
|             {
 | |
|                 switch (actionType)
 | |
|                 {
 | |
|                     case Type::Boolean:
 | |
|                         return "Boolean";
 | |
|                     case Type::Number:
 | |
|                         return "Number";
 | |
|                     case Type::Range:
 | |
|                         return "Range";
 | |
|                     default:
 | |
|                         throw std::logic_error("Unknown input action type");
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         MultiTree::Node MultiTree::insert()
 | |
|         {
 | |
|             size_t nextId = size();
 | |
|             mChildren.push_back({});
 | |
|             mParents.push_back({});
 | |
|             return nextId;
 | |
|         }
 | |
| 
 | |
|         bool MultiTree::validateTree() const
 | |
|         {
 | |
|             std::vector<bool> complete(size(), false);
 | |
|             traverse([&complete](Node node) { complete[node] = true; });
 | |
|             return std::find(complete.begin(), complete.end(), false) == complete.end();
 | |
|         }
 | |
| 
 | |
|         template <typename Function>
 | |
|         void MultiTree::traverse(Function callback) const
 | |
|         {
 | |
|             std::queue<Node> nodeQueue;
 | |
|             std::vector<bool> complete(size(), false);
 | |
|             for (Node root = 0; root < size(); ++root)
 | |
|             {
 | |
|                 if (!complete[root])
 | |
|                     nodeQueue.push(root);
 | |
|                 while (!nodeQueue.empty())
 | |
|                 {
 | |
|                     Node node = nodeQueue.back();
 | |
|                     nodeQueue.pop();
 | |
| 
 | |
|                     bool isComplete = true;
 | |
|                     for (Node parent : mParents[node])
 | |
|                         isComplete = isComplete && complete[parent];
 | |
|                     complete[node] = isComplete;
 | |
|                     if (isComplete)
 | |
|                     {
 | |
|                         callback(node);
 | |
|                         for (Node child : mChildren[node])
 | |
|                             nodeQueue.push(child);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         bool MultiTree::multiEdge(Node target, const std::vector<Node>& source)
 | |
|         {
 | |
|             mParents[target].reserve(mParents[target].size() + source.size());
 | |
|             for (Node s : source)
 | |
|             {
 | |
|                 mParents[target].push_back(s);
 | |
|                 mChildren[s].push_back(target);
 | |
|             }
 | |
|             bool validTree = validateTree();
 | |
|             if (!validTree)
 | |
|             {
 | |
|                 for (Node s : source)
 | |
|                 {
 | |
|                     mParents[target].pop_back();
 | |
|                     mChildren[s].pop_back();
 | |
|                 }
 | |
|             }
 | |
|             return validTree;
 | |
|         }
 | |
| 
 | |
|         namespace
 | |
|         {
 | |
|             bool validateActionValue(sol::object value, Type type)
 | |
|             {
 | |
|                 switch (type)
 | |
|                 {
 | |
|                     case Type::Boolean:
 | |
|                         return value.get_type() == sol::type::boolean;
 | |
|                     case Type::Number:
 | |
|                         return value.get_type() == sol::type::number;
 | |
|                     case Type::Range:
 | |
|                         if (value.get_type() != sol::type::number)
 | |
|                             return false;
 | |
|                         double d = value.as<double>();
 | |
|                         return 0.0 <= d && d <= 1.0;
 | |
|                 }
 | |
|                 throw std::invalid_argument("Unknown action type");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void Registry::insert(const Info& info)
 | |
|         {
 | |
|             if (mIds.find(info.mKey) != mIds.end())
 | |
|                 throw std::domain_error(Misc::StringUtils::format("Action key \"%s\" is already in use", info.mKey));
 | |
|             if (info.mKey.empty())
 | |
|                 throw std::domain_error("Action key can't be an empty string");
 | |
|             if (info.mL10n.empty())
 | |
|                 throw std::domain_error("Localization context can't be empty");
 | |
|             if (!validateActionValue(info.mDefaultValue, info.mType))
 | |
|                 throw std::logic_error(Misc::StringUtils::format(
 | |
|                     "Invalid value: \"%s\" for action \"%s\"", LuaUtil::toString(info.mDefaultValue), info.mKey));
 | |
|             Id id = mBindingTree.insert();
 | |
|             mKeys.push_back(info.mKey);
 | |
|             mIds[std::string(info.mKey)] = id;
 | |
|             mInfo.push_back(info);
 | |
|             mHandlers.push_back({});
 | |
|             mBindings.push_back({});
 | |
|             mValues.push_back(info.mDefaultValue);
 | |
|         }
 | |
| 
 | |
|         std::optional<std::string> Registry::nextKey(std::string_view key) const
 | |
|         {
 | |
|             auto it = mIds.find(key);
 | |
|             if (it == mIds.end())
 | |
|                 return std::nullopt;
 | |
|             auto nextId = it->second + 1;
 | |
|             if (nextId >= mKeys.size())
 | |
|                 return std::nullopt;
 | |
|             return mKeys.at(nextId);
 | |
|         }
 | |
| 
 | |
|         std::optional<Info> Registry::operator[](std::string_view actionKey)
 | |
|         {
 | |
|             auto iter = mIds.find(actionKey);
 | |
|             if (iter == mIds.end())
 | |
|                 return std::nullopt;
 | |
|             return mInfo[iter->second];
 | |
|         }
 | |
| 
 | |
|         Registry::Id Registry::safeIdByKey(std::string_view key)
 | |
|         {
 | |
|             auto iter = mIds.find(key);
 | |
|             if (iter == mIds.end())
 | |
|                 throw std::logic_error(Misc::StringUtils::format("Unknown action key: \"%s\"", key));
 | |
|             return iter->second;
 | |
|         }
 | |
| 
 | |
|         bool Registry::bind(
 | |
|             std::string_view key, const LuaUtil::Callback& callback, const std::vector<std::string_view>& dependencies)
 | |
|         {
 | |
|             Id id = safeIdByKey(key);
 | |
|             std::vector<Id> dependencyIds;
 | |
|             dependencyIds.reserve(dependencies.size());
 | |
|             for (std::string_view s : dependencies)
 | |
|                 dependencyIds.push_back(safeIdByKey(s));
 | |
|             bool validEdge = mBindingTree.multiEdge(id, dependencyIds);
 | |
|             if (validEdge)
 | |
|                 mBindings[id].push_back(Binding{
 | |
|                     callback,
 | |
|                     std::move(dependencyIds),
 | |
|                 });
 | |
|             return validEdge;
 | |
|         }
 | |
| 
 | |
|         sol::object Registry::valueOfType(std::string_view key, Type type)
 | |
|         {
 | |
|             Id id = safeIdByKey(key);
 | |
|             Info info = mInfo[id];
 | |
|             if (info.mType != type)
 | |
|                 throw std::logic_error(
 | |
|                     Misc::StringUtils::format("Attempt to get value of type \"%s\" from action \"%s\" with type \"%s\"",
 | |
|                         typeName(type), key, typeName(info.mType)));
 | |
|             return mValues[id];
 | |
|         }
 | |
| 
 | |
|         void Registry::update(double dt)
 | |
|         {
 | |
|             std::vector<sol::object> dependencyValues;
 | |
|             mBindingTree.traverse([this, &dependencyValues, dt](Id node) {
 | |
|                 sol::main_object newValue = mValues[node];
 | |
|                 std::vector<Binding>& bindings = mBindings[node];
 | |
|                 bindings.erase(std::remove_if(bindings.begin(), bindings.end(),
 | |
|                                    [&](const Binding& binding) {
 | |
|                                        if (!binding.mCallback.isValid())
 | |
|                                            return true;
 | |
| 
 | |
|                                        dependencyValues.clear();
 | |
|                                        for (Id parent : binding.mDependencies)
 | |
|                                            dependencyValues.push_back(mValues[parent]);
 | |
|                                        try
 | |
|                                        {
 | |
|                                            newValue = sol::main_object(
 | |
|                                                binding.mCallback.call(dt, newValue, sol::as_args(dependencyValues)));
 | |
|                                        }
 | |
|                                        catch (std::exception& e)
 | |
|                                        {
 | |
|                                            if (!validateActionValue(newValue, mInfo[node].mType))
 | |
|                                                Log(Debug::Error) << Misc::StringUtils::format(
 | |
|                                                    "Error due to invalid value of action \"%s\"(\"%s\"): ", mKeys[node],
 | |
|                                                    LuaUtil::toString(newValue))
 | |
|                                                                  << e.what();
 | |
|                                            else
 | |
|                                                Log(Debug::Error) << "Error in callback: " << e.what();
 | |
|                                        }
 | |
|                                        return false;
 | |
|                                    }),
 | |
|                     bindings.end());
 | |
| 
 | |
|                 if (!validateActionValue(newValue, mInfo[node].mType))
 | |
|                     Log(Debug::Error) << Misc::StringUtils::format(
 | |
|                         "Invalid value of action \"%s\": %s", mKeys[node], LuaUtil::toString(newValue));
 | |
|                 if (mValues[node] != newValue)
 | |
|                 {
 | |
|                     mValues[node] = sol::object(newValue);
 | |
|                     std::vector<LuaUtil::Callback>& handlers = mHandlers[node];
 | |
|                     handlers.erase(std::remove_if(handlers.begin(), handlers.end(),
 | |
|                                        [&](const LuaUtil::Callback& handler) {
 | |
|                                            if (!handler.isValid())
 | |
|                                                return true;
 | |
|                                            handler.tryCall(newValue);
 | |
|                                            return false;
 | |
|                                        }),
 | |
|                         handlers.end());
 | |
|                 }
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         void Registry::clear(bool force)
 | |
|         {
 | |
|             std::vector<Info> infoToKeep;
 | |
|             if (!force)
 | |
|             {
 | |
|                 for (const Info& info : mInfo)
 | |
|                     if (info.mPersistent)
 | |
|                         infoToKeep.push_back(info);
 | |
|             }
 | |
|             mKeys.clear();
 | |
|             mIds.clear();
 | |
|             mInfo.clear();
 | |
|             mHandlers.clear();
 | |
|             mBindings.clear();
 | |
|             mValues.clear();
 | |
|             mBindingTree.clear();
 | |
|             if (!force)
 | |
|             {
 | |
|                 for (const Info& i : infoToKeep)
 | |
|                     insert(i);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     namespace InputTrigger
 | |
|     {
 | |
|         Registry::Id Registry::safeIdByKey(std::string_view key)
 | |
|         {
 | |
|             auto it = mIds.find(key);
 | |
|             if (it == mIds.end())
 | |
|                 throw std::domain_error(Misc::StringUtils::format("Unknown trigger key \"%s\"", key));
 | |
|             return it->second;
 | |
|         }
 | |
| 
 | |
|         void Registry::insert(const Info& info)
 | |
|         {
 | |
|             if (mIds.find(info.mKey) != mIds.end())
 | |
|                 throw std::domain_error(Misc::StringUtils::format("Trigger key \"%s\" is already in use", info.mKey));
 | |
|             if (info.mKey.empty())
 | |
|                 throw std::domain_error("Trigger key can't be an empty string");
 | |
|             if (info.mL10n.empty())
 | |
|                 throw std::domain_error("Localization context can't be empty");
 | |
|             Id id = mIds.size();
 | |
|             mIds[info.mKey] = id;
 | |
|             mInfo.push_back(info);
 | |
|             mHandlers.push_back({});
 | |
|         }
 | |
| 
 | |
|         std::optional<Info> Registry::operator[](std::string_view key)
 | |
|         {
 | |
|             auto iter = mIds.find(key);
 | |
|             if (iter == mIds.end())
 | |
|                 return std::nullopt;
 | |
|             return mInfo[iter->second];
 | |
|         }
 | |
| 
 | |
|         void Registry::registerHandler(std::string_view key, const LuaUtil::Callback& callback)
 | |
|         {
 | |
|             Id id = safeIdByKey(key);
 | |
|             mHandlers[id].push_back(callback);
 | |
|         }
 | |
| 
 | |
|         void Registry::activate(std::string_view key)
 | |
|         {
 | |
|             Id id = safeIdByKey(key);
 | |
|             std::vector<LuaUtil::Callback>& handlers = mHandlers[id];
 | |
|             handlers.erase(std::remove_if(handlers.begin(), handlers.end(),
 | |
|                                [&](const LuaUtil::Callback& handler) {
 | |
|                                    if (!handler.isValid())
 | |
|                                        return true;
 | |
|                                    handler.tryCall();
 | |
|                                    return false;
 | |
|                                }),
 | |
|                 handlers.end());
 | |
|         }
 | |
| 
 | |
|         void Registry::clear(bool force)
 | |
|         {
 | |
|             std::vector<Info> infoToKeep;
 | |
|             if (!force)
 | |
|             {
 | |
|                 for (const Info& info : mInfo)
 | |
|                     if (info.mPersistent)
 | |
|                         infoToKeep.push_back(info);
 | |
|             }
 | |
|             mInfo.clear();
 | |
|             mHandlers.clear();
 | |
|             mIds.clear();
 | |
|             if (!force)
 | |
|             {
 | |
|                 for (const Info& i : infoToKeep)
 | |
|                     insert(i);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |