mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-11-04 02:56:39 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			296 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			296 lines
		
	
	
	
		
			12 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());
 | 
						|
                }
 | 
						|
            });
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    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());
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |