mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-16 17:59:56 +00:00
[Lua] Remove queries
This commit is contained in:
parent
9af49cfa68
commit
fa115418eb
23 changed files with 2 additions and 995 deletions
|
@ -57,7 +57,7 @@ add_openmw_dir (mwscript
|
|||
)
|
||||
|
||||
add_openmw_dir (mwlua
|
||||
luamanagerimp actions object worldview userdataserializer eventqueue query
|
||||
luamanagerimp actions object worldview userdataserializer eventqueue
|
||||
luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings
|
||||
camerabindings uibindings inputbindings nearbybindings
|
||||
types/types types/door types/actor types/container
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include <components/lua/luastate.hpp>
|
||||
#include <components/lua/i18n.hpp>
|
||||
#include <components/queries/luabindings.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/statemanager.hpp"
|
||||
|
@ -90,59 +89,10 @@ namespace MWLua
|
|||
return sol::nullopt;
|
||||
};
|
||||
api["activeActors"] = GObjectList{worldView->getActorsInScene()};
|
||||
api["selectObjects"] = [context](const Queries::Query& query)
|
||||
{
|
||||
ObjectIdList list;
|
||||
WorldView* worldView = context.mWorldView;
|
||||
if (query.mQueryType == "activators")
|
||||
list = worldView->getActivatorsInScene();
|
||||
else if (query.mQueryType == "actors")
|
||||
list = worldView->getActorsInScene();
|
||||
else if (query.mQueryType == "containers")
|
||||
list = worldView->getContainersInScene();
|
||||
else if (query.mQueryType == "doors")
|
||||
list = worldView->getDoorsInScene();
|
||||
else if (query.mQueryType == "items")
|
||||
list = worldView->getItemsInScene();
|
||||
return GObjectList{selectObjectsFromList(query, list, context)};
|
||||
// TODO: Use sqlite to search objects that are not in the scene
|
||||
// return GObjectList{worldView->selectObjects(query, false)};
|
||||
};
|
||||
// TODO: add world.placeNewObject(recordId, cell, pos, [rot])
|
||||
return LuaUtil::makeReadOnly(api);
|
||||
}
|
||||
|
||||
sol::table initQueryPackage(const Context& context)
|
||||
{
|
||||
Queries::registerQueryBindings(context.mLua->sol());
|
||||
sol::table query(context.mLua->sol(), sol::create);
|
||||
for (std::string_view t : ObjectQueryTypes::types)
|
||||
query[t] = Queries::Query(std::string(t));
|
||||
for (const QueryFieldGroup& group : getBasicQueryFieldGroups())
|
||||
query[group.mName] = initFieldGroup(context, group);
|
||||
return query; // makeReadOnly is applied by LuaState::addCommonPackage
|
||||
}
|
||||
|
||||
sol::table initFieldGroup(const Context& context, const QueryFieldGroup& group)
|
||||
{
|
||||
sol::table res(context.mLua->sol(), sol::create);
|
||||
for (const Queries::Field* field : group.mFields)
|
||||
{
|
||||
sol::table subgroup = res;
|
||||
if (field->path().empty())
|
||||
throw std::logic_error("Empty path in Queries::Field");
|
||||
for (size_t i = 0; i < field->path().size() - 1; ++i)
|
||||
{
|
||||
const std::string& name = field->path()[i];
|
||||
if (subgroup[name] == sol::nil)
|
||||
subgroup[name] = LuaUtil::makeReadOnly(context.mLua->newTable());
|
||||
subgroup = LuaUtil::getMutableFromReadOnly(subgroup[name]);
|
||||
}
|
||||
subgroup[field->path().back()] = field;
|
||||
}
|
||||
return LuaUtil::makeReadOnly(res);
|
||||
}
|
||||
|
||||
sol::table initGlobalStoragePackage(const Context& context, LuaUtil::LuaStorage* globalStorage)
|
||||
{
|
||||
sol::table res(context.mLua->sol(), sol::create);
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include "context.hpp"
|
||||
#include "eventqueue.hpp"
|
||||
#include "object.hpp"
|
||||
#include "query.hpp"
|
||||
#include "worldview.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
|
@ -22,9 +21,6 @@ namespace MWLua
|
|||
|
||||
sol::table initCorePackage(const Context&);
|
||||
sol::table initWorldPackage(const Context&);
|
||||
sol::table initQueryPackage(const Context&);
|
||||
|
||||
sol::table initFieldGroup(const Context&, const QueryFieldGroup&);
|
||||
|
||||
sol::table initGlobalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage);
|
||||
sol::table initLocalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage);
|
||||
|
|
|
@ -82,7 +82,6 @@ namespace MWLua
|
|||
mLua.addCommonPackage("openmw.async", getAsyncPackageInitializer(context));
|
||||
mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol()));
|
||||
mLua.addCommonPackage("openmw.core", initCorePackage(context));
|
||||
mLua.addCommonPackage("openmw.query", initQueryPackage(context));
|
||||
mLua.addCommonPackage("openmw.types", initTypesPackage(context));
|
||||
mGlobalScripts.addPackage("openmw.world", initWorldPackage(context));
|
||||
mGlobalScripts.addPackage("openmw.settings", initGlobalSettingsPackage(context));
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "luabindings.hpp"
|
||||
|
||||
#include <components/lua/luastate.hpp>
|
||||
#include <components/queries/luabindings.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
@ -98,24 +97,6 @@ namespace MWLua
|
|||
api["containers"] = LObjectList{worldView->getContainersInScene()};
|
||||
api["doors"] = LObjectList{worldView->getDoorsInScene()};
|
||||
api["items"] = LObjectList{worldView->getItemsInScene()};
|
||||
api["selectObjects"] = [context](const Queries::Query& query)
|
||||
{
|
||||
ObjectIdList list;
|
||||
WorldView* worldView = context.mWorldView;
|
||||
if (query.mQueryType == "activators")
|
||||
list = worldView->getActivatorsInScene();
|
||||
else if (query.mQueryType == "actors")
|
||||
list = worldView->getActorsInScene();
|
||||
else if (query.mQueryType == "containers")
|
||||
list = worldView->getContainersInScene();
|
||||
else if (query.mQueryType == "doors")
|
||||
list = worldView->getDoorsInScene();
|
||||
else if (query.mQueryType == "items")
|
||||
list = worldView->getItemsInScene();
|
||||
return LObjectList{selectObjectsFromList(query, list, context)};
|
||||
// TODO: Maybe use sqlite
|
||||
// return LObjectList{worldView->selectObjects(query, true)};
|
||||
};
|
||||
return LuaUtil::makeReadOnly(api);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
#include "luabindings.hpp"
|
||||
|
||||
#include <components/lua/luastate.hpp>
|
||||
#include <components/queries/query.hpp>
|
||||
|
||||
#include "../mwclass/door.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
||||
|
@ -77,10 +75,6 @@ namespace MWLua
|
|||
});
|
||||
listT[sol::meta_function::ipairs] = [iter](const LObjectList& list) { return std::make_tuple(iter, list, 0); };
|
||||
}
|
||||
listT["select"] = [context](const ListT& list, const Queries::Query& query)
|
||||
{
|
||||
return ListT{selectObjectsFromList(query, list.mIds, context)};
|
||||
};
|
||||
}
|
||||
|
||||
template <class ObjectT>
|
||||
|
|
|
@ -1,191 +0,0 @@
|
|||
#include "query.hpp"
|
||||
|
||||
#include <sol/sol.hpp>
|
||||
|
||||
#include <components/lua/luastate.hpp>
|
||||
|
||||
#include "../mwclass/container.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
#include "worldview.hpp"
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
|
||||
static std::vector<QueryFieldGroup> initBasicFieldGroups()
|
||||
{
|
||||
auto createGroup = [](std::string name, const auto& arr) -> QueryFieldGroup
|
||||
{
|
||||
std::vector<const Queries::Field*> fieldPtrs;
|
||||
fieldPtrs.reserve(arr.size());
|
||||
for (const Queries::Field& field : arr)
|
||||
fieldPtrs.push_back(&field);
|
||||
return {std::move(name), std::move(fieldPtrs)};
|
||||
};
|
||||
static std::array objectFields = {
|
||||
Queries::Field({"type"}, typeid(std::string)),
|
||||
Queries::Field({"recordId"}, typeid(std::string)),
|
||||
Queries::Field({"cell", "name"}, typeid(std::string)),
|
||||
Queries::Field({"cell", "region"}, typeid(std::string)),
|
||||
Queries::Field({"cell", "isExterior"}, typeid(bool)),
|
||||
Queries::Field({"count"}, typeid(int32_t)),
|
||||
};
|
||||
static std::array doorFields = {
|
||||
Queries::Field({"isTeleport"}, typeid(bool)),
|
||||
Queries::Field({"destCell", "name"}, typeid(std::string)),
|
||||
Queries::Field({"destCell", "region"}, typeid(std::string)),
|
||||
Queries::Field({"destCell", "isExterior"}, typeid(bool)),
|
||||
};
|
||||
return std::vector<QueryFieldGroup>{
|
||||
createGroup("OBJECT", objectFields),
|
||||
createGroup("DOOR", doorFields),
|
||||
};
|
||||
}
|
||||
|
||||
const std::vector<QueryFieldGroup>& getBasicQueryFieldGroups()
|
||||
{
|
||||
static std::vector<QueryFieldGroup> fieldGroups = initBasicFieldGroups();
|
||||
return fieldGroups;
|
||||
}
|
||||
|
||||
bool checkQueryConditions(const Queries::Query& query, const ObjectId& id, const Context& context)
|
||||
{
|
||||
auto compareFn = [](auto&& a, auto&& b, Queries::Condition::Type t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case Queries::Condition::EQUAL: return a == b;
|
||||
case Queries::Condition::NOT_EQUAL: return a != b;
|
||||
case Queries::Condition::GREATER: return a > b;
|
||||
case Queries::Condition::GREATER_OR_EQUAL: return a >= b;
|
||||
case Queries::Condition::LESSER: return a < b;
|
||||
case Queries::Condition::LESSER_OR_EQUAL: return a <= b;
|
||||
default:
|
||||
throw std::runtime_error("Unsupported condition type");
|
||||
}
|
||||
};
|
||||
sol::object obj;
|
||||
MWWorld::Ptr ptr;
|
||||
if (context.mIsGlobal)
|
||||
{
|
||||
GObject g(id, context.mWorldView->getObjectRegistry());
|
||||
if (!g.isValid())
|
||||
return false;
|
||||
ptr = g.ptr();
|
||||
obj = sol::make_object(context.mLua->sol(), g);
|
||||
}
|
||||
else
|
||||
{
|
||||
LObject l(id, context.mWorldView->getObjectRegistry());
|
||||
if (!l.isValid())
|
||||
return false;
|
||||
ptr = l.ptr();
|
||||
obj = sol::make_object(context.mLua->sol(), l);
|
||||
}
|
||||
if (ptr.getRefData().getCount() == 0)
|
||||
return false;
|
||||
|
||||
// It is important to exclude all markers before checking what class it is.
|
||||
// For example "prisonmarker" has class "Door" despite that it is only an invisible marker.
|
||||
if (isMarker(ptr))
|
||||
return false;
|
||||
|
||||
const MWWorld::Class& cls = ptr.getClass();
|
||||
if (cls.isActivator() != (query.mQueryType == ObjectQueryTypes::ACTIVATORS))
|
||||
return false;
|
||||
if (cls.isActor() != (query.mQueryType == ObjectQueryTypes::ACTORS))
|
||||
return false;
|
||||
if (cls.isDoor() != (query.mQueryType == ObjectQueryTypes::DOORS))
|
||||
return false;
|
||||
if ((typeid(cls) == typeid(MWClass::Container)) != (query.mQueryType == ObjectQueryTypes::CONTAINERS))
|
||||
return false;
|
||||
|
||||
std::vector<char> condStack;
|
||||
for (const Queries::Operation& op : query.mFilter.mOperations)
|
||||
{
|
||||
switch(op.mType)
|
||||
{
|
||||
case Queries::Operation::PUSH:
|
||||
{
|
||||
const Queries::Condition& cond = query.mFilter.mConditions[op.mConditionIndex];
|
||||
sol::object fieldObj = obj;
|
||||
for (const std::string& field : cond.mField->path())
|
||||
fieldObj = LuaUtil::getFieldOrNil(fieldObj, field);
|
||||
bool c;
|
||||
if (fieldObj == sol::nil)
|
||||
c = false;
|
||||
else if (cond.mField->type() == typeid(std::string))
|
||||
c = compareFn(fieldObj.as<std::string_view>(), std::get<std::string>(cond.mValue), cond.mType);
|
||||
else if (cond.mField->type() == typeid(float))
|
||||
c = compareFn(fieldObj.as<float>(), std::get<float>(cond.mValue), cond.mType);
|
||||
else if (cond.mField->type() == typeid(double))
|
||||
c = compareFn(fieldObj.as<double>(), std::get<double>(cond.mValue), cond.mType);
|
||||
else if (cond.mField->type() == typeid(bool))
|
||||
c = compareFn(fieldObj.as<bool>(), std::get<bool>(cond.mValue), cond.mType);
|
||||
else if (cond.mField->type() == typeid(int32_t))
|
||||
c = compareFn(fieldObj.as<int32_t>(), std::get<int32_t>(cond.mValue), cond.mType);
|
||||
else if (cond.mField->type() == typeid(int64_t))
|
||||
c = compareFn(fieldObj.as<int64_t>(), std::get<int64_t>(cond.mValue), cond.mType);
|
||||
else
|
||||
throw std::runtime_error("Unknown field type");
|
||||
condStack.push_back(c);
|
||||
break;
|
||||
}
|
||||
case Queries::Operation::NOT:
|
||||
condStack.back() = !condStack.back();
|
||||
break;
|
||||
case Queries::Operation::AND:
|
||||
{
|
||||
bool v = condStack.back();
|
||||
condStack.pop_back();
|
||||
condStack.back() = condStack.back() && v;
|
||||
break;
|
||||
}
|
||||
case Queries::Operation::OR:
|
||||
{
|
||||
bool v = condStack.back();
|
||||
condStack.pop_back();
|
||||
condStack.back() = condStack.back() || v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return condStack.empty() || condStack.back() != 0;
|
||||
}
|
||||
|
||||
ObjectIdList selectObjectsFromList(const Queries::Query& query, const ObjectIdList& list, const Context& context)
|
||||
{
|
||||
if (!query.mOrderBy.empty() || !query.mGroupBy.empty() || query.mOffset > 0)
|
||||
throw std::runtime_error("OrderBy, GroupBy, and Offset are not supported");
|
||||
|
||||
ObjectIdList res = std::make_shared<std::vector<ObjectId>>();
|
||||
for (const ObjectId& id : *list)
|
||||
{
|
||||
if (static_cast<int64_t>(res->size()) == query.mLimit)
|
||||
break;
|
||||
if (checkQueryConditions(query, id, context))
|
||||
res->push_back(id);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
ObjectIdList selectObjectsFromCellStore(const Queries::Query& query, MWWorld::CellStore* store, const Context& context)
|
||||
{
|
||||
if (!query.mOrderBy.empty() || !query.mGroupBy.empty() || query.mOffset > 0)
|
||||
throw std::runtime_error("OrderBy, GroupBy, and Offset are not supported");
|
||||
|
||||
ObjectIdList res = std::make_shared<std::vector<ObjectId>>();
|
||||
auto visitor = [&](const MWWorld::Ptr& ptr)
|
||||
{
|
||||
if (static_cast<int64_t>(res->size()) == query.mLimit)
|
||||
return false;
|
||||
context.mWorldView->getObjectRegistry()->registerPtr(ptr);
|
||||
if (checkQueryConditions(query, getId(ptr), context))
|
||||
res->push_back(getId(ptr));
|
||||
return static_cast<int64_t>(res->size()) != query.mLimit;
|
||||
};
|
||||
store->forEach(std::move(visitor)); // TODO: maybe use store->forEachType<TYPE> depending on query.mType
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
#ifndef MWLUA_QUERY_H
|
||||
#define MWLUA_QUERY_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <components/queries/query.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
#include "object.hpp"
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
|
||||
struct ObjectQueryTypes
|
||||
{
|
||||
static constexpr std::string_view ACTIVATORS = "activators";
|
||||
static constexpr std::string_view ACTORS = "actors";
|
||||
static constexpr std::string_view CONTAINERS = "containers";
|
||||
static constexpr std::string_view DOORS = "doors";
|
||||
static constexpr std::string_view ITEMS = "items";
|
||||
|
||||
static constexpr std::string_view types[] = {ACTIVATORS, ACTORS, CONTAINERS, DOORS, ITEMS};
|
||||
};
|
||||
|
||||
struct QueryFieldGroup
|
||||
{
|
||||
std::string mName;
|
||||
std::vector<const Queries::Field*> mFields;
|
||||
};
|
||||
const std::vector<QueryFieldGroup>& getBasicQueryFieldGroups();
|
||||
|
||||
// TODO: Implement custom fields. QueryFieldGroup registerCustomFields(...);
|
||||
|
||||
ObjectIdList selectObjectsFromList(const Queries::Query& query, const ObjectIdList& list, const Context&);
|
||||
ObjectIdList selectObjectsFromCellStore(const Queries::Query& query, MWWorld::CellStore* store, const Context&);
|
||||
|
||||
}
|
||||
|
||||
#endif // MWLUA_QUERY_H
|
|
@ -21,7 +21,6 @@ if (GTEST_FOUND AND GMOCK_FOUND)
|
|||
lua/test_scriptscontainer.cpp
|
||||
lua/test_utilpackage.cpp
|
||||
lua/test_serialization.cpp
|
||||
lua/test_querypackage.cpp
|
||||
lua/test_configuration.cpp
|
||||
lua/test_i18n.cpp
|
||||
lua/test_storage.cpp
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
#include "gmock/gmock.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <components/queries/luabindings.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace testing;
|
||||
|
||||
TEST(LuaQueryPackageTest, basic)
|
||||
{
|
||||
sol::state lua;
|
||||
lua.open_libraries(sol::lib::base, sol::lib::string);
|
||||
Queries::registerQueryBindings(lua);
|
||||
lua["query"] = Queries::Query("test");
|
||||
lua["fieldX"] = Queries::Field({ "x" }, typeid(std::string));
|
||||
lua["fieldY"] = Queries::Field({ "y" }, typeid(int));
|
||||
lua.safe_script("t = query:where(fieldX:eq('abc') + fieldX:like('%abcd%'))");
|
||||
lua.safe_script("t = t:where(fieldY:gt(5))");
|
||||
lua.safe_script("t = t:orderBy(fieldX)");
|
||||
lua.safe_script("t = t:orderByDesc(fieldY)");
|
||||
lua.safe_script("t = t:groupBy(fieldY)");
|
||||
lua.safe_script("t = t:limit(10):offset(5)");
|
||||
EXPECT_EQ(
|
||||
lua.safe_script("return tostring(t)").get<std::string>(),
|
||||
"SELECT test WHERE ((x == \"abc\") OR (x LIKE \"%abcd%\")) AND (y > 5) ORDER BY x, y DESC GROUP BY y LIMIT 10 OFFSET 5");
|
||||
}
|
||||
}
|
||||
|
|
@ -253,10 +253,6 @@ add_component_dir (fallback
|
|||
fallback validate
|
||||
)
|
||||
|
||||
add_component_dir (queries
|
||||
query luabindings
|
||||
)
|
||||
|
||||
add_component_dir (lua_ui
|
||||
registerscriptsettings scriptsettings
|
||||
properties widget element util layers content alignment resources
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
#include "luabindings.hpp"
|
||||
|
||||
namespace sol
|
||||
{
|
||||
template <>
|
||||
struct is_automagical<Queries::Field> : std::false_type {};
|
||||
|
||||
template <>
|
||||
struct is_automagical<Queries::Filter> : std::false_type {};
|
||||
|
||||
template <>
|
||||
struct is_automagical<Queries::Query> : std::false_type {};
|
||||
}
|
||||
|
||||
namespace Queries
|
||||
{
|
||||
template <Condition::Type type>
|
||||
struct CondBuilder
|
||||
{
|
||||
Filter operator()(const Field& field, const sol::object& o)
|
||||
{
|
||||
FieldValue value;
|
||||
if (field.type() == typeid(bool) && o.is<bool>())
|
||||
value = o.as<bool>();
|
||||
else if (field.type() == typeid(int32_t) && o.is<int32_t>())
|
||||
value = o.as<int32_t>();
|
||||
else if (field.type() == typeid(int64_t) && o.is<int64_t>())
|
||||
value = o.as<int64_t>();
|
||||
else if (field.type() == typeid(float) && o.is<float>())
|
||||
value = o.as<float>();
|
||||
else if (field.type() == typeid(double) && o.is<double>())
|
||||
value = o.as<double>();
|
||||
else if (field.type() == typeid(std::string) && o.is<std::string>())
|
||||
value = o.as<std::string>();
|
||||
else
|
||||
throw std::logic_error("Invalid value for field " + field.toString());
|
||||
Filter filter;
|
||||
filter.add({&field, type, value});
|
||||
return filter;
|
||||
}
|
||||
};
|
||||
|
||||
void registerQueryBindings(sol::state& lua)
|
||||
{
|
||||
sol::usertype<Field> field = lua.new_usertype<Field>("QueryField");
|
||||
sol::usertype<Filter> filter = lua.new_usertype<Filter>("QueryFilter");
|
||||
sol::usertype<Query> query = lua.new_usertype<Query>("Query");
|
||||
|
||||
field[sol::meta_function::to_string] = [](const Field& f) { return f.toString(); };
|
||||
field["eq"] = CondBuilder<Condition::EQUAL>();
|
||||
field["neq"] = CondBuilder<Condition::NOT_EQUAL>();
|
||||
field["lt"] = CondBuilder<Condition::LESSER>();
|
||||
field["lte"] = CondBuilder<Condition::LESSER_OR_EQUAL>();
|
||||
field["gt"] = CondBuilder<Condition::GREATER>();
|
||||
field["gte"] = CondBuilder<Condition::GREATER_OR_EQUAL>();
|
||||
field["like"] = CondBuilder<Condition::LIKE>();
|
||||
|
||||
filter[sol::meta_function::to_string] = [](const Filter& filter) { return filter.toString(); };
|
||||
filter[sol::meta_function::multiplication] = [](const Filter& a, const Filter& b)
|
||||
{
|
||||
Filter res = a;
|
||||
res.add(b, Operation::AND);
|
||||
return res;
|
||||
};
|
||||
filter[sol::meta_function::addition] = [](const Filter& a, const Filter& b)
|
||||
{
|
||||
Filter res = a;
|
||||
res.add(b, Operation::OR);
|
||||
return res;
|
||||
};
|
||||
filter[sol::meta_function::unary_minus] = [](const Filter& a)
|
||||
{
|
||||
Filter res = a;
|
||||
if (!a.mConditions.empty())
|
||||
res.mOperations.push_back({Operation::NOT, 0});
|
||||
return res;
|
||||
};
|
||||
|
||||
query[sol::meta_function::to_string] = [](const Query& q) { return q.toString(); };
|
||||
query["where"] = [](const Query& q, const Filter& filter)
|
||||
{
|
||||
Query res = q;
|
||||
res.mFilter.add(filter, Operation::AND);
|
||||
return res;
|
||||
};
|
||||
query["orderBy"] = [](const Query& q, const Field& field)
|
||||
{
|
||||
Query res = q;
|
||||
res.mOrderBy.push_back({&field, false});
|
||||
return res;
|
||||
};
|
||||
query["orderByDesc"] = [](const Query& q, const Field& field)
|
||||
{
|
||||
Query res = q;
|
||||
res.mOrderBy.push_back({&field, true});
|
||||
return res;
|
||||
};
|
||||
query["groupBy"] = [](const Query& q, const Field& field)
|
||||
{
|
||||
Query res = q;
|
||||
res.mGroupBy.push_back(&field);
|
||||
return res;
|
||||
};
|
||||
query["offset"] = [](const Query& q, int64_t offset)
|
||||
{
|
||||
Query res = q;
|
||||
res.mOffset = offset;
|
||||
return res;
|
||||
};
|
||||
query["limit"] = [](const Query& q, int64_t limit)
|
||||
{
|
||||
Query res = q;
|
||||
res.mLimit = limit;
|
||||
return res;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
#include <limits> // missing from sol/sol.hpp
|
||||
#include <sol/sol.hpp>
|
||||
|
||||
#include "query.hpp"
|
||||
|
||||
namespace Queries
|
||||
{
|
||||
void registerQueryBindings(sol::state& lua);
|
||||
}
|
|
@ -1,185 +0,0 @@
|
|||
#include "query.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace Queries
|
||||
{
|
||||
Field::Field(std::vector<std::string> path, std::type_index type)
|
||||
: mPath(std::move(path))
|
||||
, mType(type) {}
|
||||
|
||||
std::string Field::toString() const
|
||||
{
|
||||
std::string result;
|
||||
for (const std::string& segment : mPath)
|
||||
{
|
||||
if (!result.empty())
|
||||
result += ".";
|
||||
result += segment;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string toString(const FieldValue& value)
|
||||
{
|
||||
return std::visit([](auto&& arg) -> std::string
|
||||
{
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, std::string>)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << std::quoted(arg);
|
||||
return oss.str();
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, bool>)
|
||||
return arg ? "true" : "false";
|
||||
else
|
||||
return std::to_string(arg);
|
||||
}, value);
|
||||
}
|
||||
|
||||
std::string Condition::toString() const
|
||||
{
|
||||
std::string res;
|
||||
res += mField->toString();
|
||||
switch (mType)
|
||||
{
|
||||
case Condition::EQUAL: res += " == "; break;
|
||||
case Condition::NOT_EQUAL: res += " != "; break;
|
||||
case Condition::LESSER: res += " < "; break;
|
||||
case Condition::LESSER_OR_EQUAL: res += " <= "; break;
|
||||
case Condition::GREATER: res += " > "; break;
|
||||
case Condition::GREATER_OR_EQUAL: res += " >= "; break;
|
||||
case Condition::LIKE: res += " LIKE "; break;
|
||||
}
|
||||
res += Queries::toString(mValue);
|
||||
return res;
|
||||
}
|
||||
|
||||
void Filter::add(const Condition& c, Operation::Type op)
|
||||
{
|
||||
mOperations.push_back({Operation::PUSH, mConditions.size()});
|
||||
mConditions.push_back(c);
|
||||
if (mConditions.size() > 1)
|
||||
mOperations.push_back({op, 0});
|
||||
}
|
||||
|
||||
void Filter::add(const Filter& f, Operation::Type op)
|
||||
{
|
||||
size_t conditionOffset = mConditions.size();
|
||||
size_t operationsBefore = mOperations.size();
|
||||
mConditions.insert(mConditions.end(), f.mConditions.begin(), f.mConditions.end());
|
||||
mOperations.insert(mOperations.end(), f.mOperations.begin(), f.mOperations.end());
|
||||
for (size_t i = operationsBefore; i < mOperations.size(); ++i)
|
||||
mOperations[i].mConditionIndex += conditionOffset;
|
||||
if (conditionOffset > 0 && !f.mConditions.empty())
|
||||
mOperations.push_back({op, 0});
|
||||
}
|
||||
|
||||
std::string Filter::toString() const
|
||||
{
|
||||
if(mOperations.empty())
|
||||
return "";
|
||||
std::vector<std::string> stack;
|
||||
auto pop = [&stack](){ auto v = stack.back(); stack.pop_back(); return v; };
|
||||
auto push = [&stack](const std::string& s) { stack.push_back(s); };
|
||||
for (const Operation& op : mOperations)
|
||||
{
|
||||
if(op.mType == Operation::PUSH)
|
||||
push(mConditions[op.mConditionIndex].toString());
|
||||
else if(op.mType == Operation::AND)
|
||||
{
|
||||
auto rhs = pop();
|
||||
auto lhs = pop();
|
||||
std::string res;
|
||||
res += "(";
|
||||
res += lhs;
|
||||
res += ") AND (";
|
||||
res += rhs;
|
||||
res += ")";
|
||||
push(res);
|
||||
}
|
||||
else if (op.mType == Operation::OR)
|
||||
{
|
||||
auto rhs = pop();
|
||||
auto lhs = pop();
|
||||
std::string res;
|
||||
res += "(";
|
||||
res += lhs;
|
||||
res += ") OR (";
|
||||
res += rhs;
|
||||
res += ")";
|
||||
push(res);
|
||||
}
|
||||
else if (op.mType == Operation::NOT)
|
||||
{
|
||||
std::string res;
|
||||
res += "NOT (";
|
||||
res += pop();
|
||||
res += ")";
|
||||
push(res);
|
||||
}
|
||||
else
|
||||
throw std::logic_error("Unknown operation type!");
|
||||
}
|
||||
return pop();
|
||||
}
|
||||
|
||||
std::string Query::toString() const
|
||||
{
|
||||
std::string res;
|
||||
res += "SELECT ";
|
||||
res += mQueryType;
|
||||
|
||||
std::string filter = mFilter.toString();
|
||||
if(!filter.empty())
|
||||
{
|
||||
res += " WHERE ";
|
||||
res += filter;
|
||||
}
|
||||
|
||||
std::string order;
|
||||
for(const OrderBy& ord : mOrderBy)
|
||||
{
|
||||
if(!order.empty())
|
||||
order += ", ";
|
||||
order += ord.mField->toString();
|
||||
if(ord.mDescending)
|
||||
order += " DESC";
|
||||
}
|
||||
if (!order.empty())
|
||||
{
|
||||
res += " ORDER BY ";
|
||||
res += order;
|
||||
}
|
||||
|
||||
std::string group;
|
||||
for (const Field* f: mGroupBy)
|
||||
{
|
||||
if (!group.empty())
|
||||
group += " ,";
|
||||
group += f->toString();
|
||||
}
|
||||
if (!group.empty())
|
||||
{
|
||||
res += " GROUP BY ";
|
||||
res += group;
|
||||
}
|
||||
|
||||
if (mLimit != sNoLimit)
|
||||
{
|
||||
res += " LIMIT ";
|
||||
res += std::to_string(mLimit);
|
||||
}
|
||||
|
||||
if (mOffset != 0)
|
||||
{
|
||||
res += " OFFSET ";
|
||||
res += std::to_string(mOffset);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
#ifndef COMPONENTS_QUERIES_QUERY
|
||||
#define COMPONENTS_QUERIES_QUERY
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <typeindex>
|
||||
#include <variant>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Queries
|
||||
{
|
||||
class Field
|
||||
{
|
||||
public:
|
||||
Field(std::vector<std::string> path, std::type_index type);
|
||||
|
||||
const std::vector<std::string>& path() const { return mPath; }
|
||||
const std::type_index type() const { return mType; }
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
private:
|
||||
std::vector<std::string> mPath;
|
||||
std::type_index mType;
|
||||
};
|
||||
|
||||
struct OrderBy
|
||||
{
|
||||
const Field* mField;
|
||||
bool mDescending;
|
||||
};
|
||||
|
||||
using FieldValue = std::variant<bool, int32_t, int64_t, float, double, std::string>;
|
||||
std::string toString(const FieldValue& value);
|
||||
|
||||
struct Condition
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
EQUAL = 0,
|
||||
NOT_EQUAL = 1,
|
||||
GREATER = 2,
|
||||
GREATER_OR_EQUAL = 3,
|
||||
LESSER = 4,
|
||||
LESSER_OR_EQUAL = 5,
|
||||
LIKE = 6,
|
||||
};
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
const Field* mField;
|
||||
Type mType;
|
||||
FieldValue mValue;
|
||||
};
|
||||
|
||||
struct Operation
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
PUSH = 0, // push condition on stack
|
||||
NOT = 1, // invert top condition on stack
|
||||
AND = 2, // logic AND for two top conditions
|
||||
OR = 3, // logic OR for two top conditions
|
||||
};
|
||||
|
||||
Type mType;
|
||||
size_t mConditionIndex; // used only if mType == PUSH
|
||||
};
|
||||
|
||||
struct Filter
|
||||
{
|
||||
std::string toString() const;
|
||||
|
||||
// combines with given condition or filter using operation `AND` or `OR`.
|
||||
void add(const Condition& c, Operation::Type op = Operation::AND);
|
||||
void add(const Filter& f, Operation::Type op = Operation::AND);
|
||||
|
||||
std::vector<Condition> mConditions;
|
||||
std::vector<Operation> mOperations; // operations on conditions in reverse polish notation
|
||||
};
|
||||
|
||||
struct Query
|
||||
{
|
||||
static constexpr int64_t sNoLimit = -1;
|
||||
|
||||
Query(std::string type) : mQueryType(std::move(type)) {}
|
||||
std::string toString() const;
|
||||
|
||||
std::string mQueryType;
|
||||
Filter mFilter;
|
||||
std::vector<OrderBy> mOrderBy;
|
||||
std::vector<const Field*> mGroupBy;
|
||||
int64_t mOffset = 0;
|
||||
int64_t mLimit = sNoLimit;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !COMPONENTS_QUERIES_QUERY
|
||||
|
|
@ -14,7 +14,6 @@ Lua API reference
|
|||
openmw_core
|
||||
openmw_types
|
||||
openmw_async
|
||||
openmw_query
|
||||
openmw_world
|
||||
openmw_self
|
||||
openmw_nearby
|
||||
|
@ -61,8 +60,6 @@ Player scripts are local scripts that are attached to a player.
|
|||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers (implemented) and coroutine utils (not implemented) |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.query <Package openmw.query>` | everywhere | | Tools for constructing queries: base queries and fields. |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.world <Package openmw.world>` | by global scripts | | Read-write access to the game world. |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.self <Package openmw.self>` | by local scripts | | Full access to the object the script is attached to. |
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
Package openmw.query
|
||||
====================
|
||||
|
||||
.. raw:: html
|
||||
:file: generated_html/openmw_query.html
|
|
@ -357,8 +357,6 @@ Player scripts are local scripts that are attached to a player.
|
|||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers (implemented) and coroutine utils (not implemented) |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.query <Package openmw.query>` | everywhere | | Tools for constructing queries: base queries and fields. |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.world <Package openmw.world>` | by global scripts | | Read-write access to the game world. |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.self <Package openmw.self>` | by local scripts | | Full access to the object the script is attached to. |
|
||||
|
@ -632,98 +630,6 @@ Also in `openmw_aux`_ is the helper function ``runRepeatedly``, it is implemente
|
|||
})
|
||||
|
||||
|
||||
Queries
|
||||
=======
|
||||
|
||||
`openmw.query` contains base queries of each type (e.g. `query.doors`, `query.containers`...), which return all of the objects of given type in no particular order. You can then modify that query to filter the results, sort them, group them, etc. Queries are immutable, so any operations on them return a new copy, leaving the original unchanged.
|
||||
|
||||
`openmw.world.selectObjects` and `openmw.nearby.selectObjects` both accept a query and return objects that match it. However, `nearby.selectObjects` is only available in local scripts, and returns only objects from currently active cells, while `world.selectObjects` is only available in global scripts, and returns objects regardless of them being in active cells.
|
||||
**TODO:** describe how to filter out inactive objects from world queries
|
||||
|
||||
An example of an object query:
|
||||
|
||||
.. code-block:: Lua
|
||||
|
||||
local query = require('openmw.query')
|
||||
local nearby = require('openmw.nearby')
|
||||
local ui = require('openmw.ui')
|
||||
|
||||
local function selectDoors(namePattern)
|
||||
local query = query.doors:where(query.DOOR.destCell.name:like(namePattern))
|
||||
return nearby.selectObjects(query)
|
||||
end
|
||||
|
||||
local function showGuildDoors()
|
||||
ui.showMessage('Here are all the entrances to guilds!')
|
||||
for _, door in selectDoors("%Guild%"):ipairs() do
|
||||
local pos = door.position
|
||||
local message = string.format("%.0f;%.0f;%.0f", pos.x, pos.y, pos.z)
|
||||
ui.showMessage(message)
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
engineHandlers = {
|
||||
onKeyPress = function(key)
|
||||
if key.symbol == 'e' then
|
||||
showGuildDoors()
|
||||
end
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
.. warning::
|
||||
The example above uses operation `like` that is not implemented yet.
|
||||
|
||||
**TODO:** add non-object queries, explain how relations work, and define what a field is
|
||||
|
||||
Queries are constructed through the following method calls: (if you've used SQL before, you will find them familiar)
|
||||
|
||||
- `:where(filter)` - filters the results to match the combination of conditions passed as the argument
|
||||
- `:orderBy(field)` and `:orderByDesc(field)` sort the result by the `field` argument. Sorts in descending order in case of `:orderByDesc`. Multiple calls can be chained, with the first call having priority. (i. e. if the first field is equal, objects are sorted by the second one...) **(not implemented yet)**
|
||||
- `:groupBy(field)` returns only one result for each value of the `field` argument. The choice of the result is arbitrary. Useful for counting only unique objects, or checking if certain objects exist. **(not implemented yet)**
|
||||
- `:limit(number)` will only return `number` of results (or fewer)
|
||||
- `:offset(number)` skips the first `number` results. Particularly useful in combination with `:limit` **(not implemented yet)**
|
||||
|
||||
Filters consist of conditions, which are combined with "and" (operator `*`), "or" (operator `+`), "not" (operator `-`) and braces `()`.
|
||||
|
||||
To make a condition, take a field from the `openmw.query` package and call any of the following methods:
|
||||
|
||||
- `:eq` equal to
|
||||
- `:neq` not equal to
|
||||
- `:gt` greater than
|
||||
- `:gte` greater or equal to
|
||||
- `:lt` less than
|
||||
- `:lte` less or equal to
|
||||
- `:like` matches a pattern. Only applicable to text (strings) **(not implemented yet)**
|
||||
|
||||
**TODO:** describe the pattern format
|
||||
|
||||
All the condition methods are type sensitive, and will throw an error if you pass a value of the wrong type into them.
|
||||
|
||||
A few examples of filters:
|
||||
|
||||
.. warning::
|
||||
`openmw.query.ACTOR` is not implemented yet
|
||||
|
||||
.. code-block:: Lua
|
||||
|
||||
local query = require('openmw.query')
|
||||
local ACTOR = query.ACTOR
|
||||
|
||||
local strong_guys_from_capital = (ACTOR.stats.level:gt(10) + ACTOR.stats.strength:gt(70))
|
||||
* ACTOR.cell.name:eq("Default city")
|
||||
|
||||
-- could also write like this:
|
||||
local strong_guys = ACTOR.stats.level:gt(10) + ACTOR.stats.strength:gt(70)
|
||||
local guys_from_capital = ACTOR.cell.name:eq("Default city")
|
||||
local strong_guys_from_capital_2 = strong_guys * guys_from_capital
|
||||
|
||||
local DOOR = query.DOOR
|
||||
|
||||
local interestingDoors = -DOOR.name:eq("") * DOOR.isTeleport:eq(true) * Door.destCell.isExterior:eq(false)
|
||||
|
||||
|
||||
Using IDE for Lua scripting
|
||||
===========================
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ set(LUA_API_FILES
|
|||
openmw/async.lua
|
||||
openmw/core.lua
|
||||
openmw/nearby.lua
|
||||
openmw/query.lua
|
||||
openmw/self.lua
|
||||
openmw/ui.lua
|
||||
openmw/util.lua
|
||||
|
|
|
@ -159,13 +159,6 @@
|
|||
-- @type ObjectList
|
||||
-- @list <#GameObject>
|
||||
|
||||
---
|
||||
-- Filter list with a Query.
|
||||
-- @function [parent=#ObjectList] select
|
||||
-- @param self
|
||||
-- @param openmw.query#Query query
|
||||
-- @return #ObjectList
|
||||
|
||||
|
||||
---
|
||||
-- A cell of the game world.
|
||||
|
|
|
@ -26,12 +26,6 @@
|
|||
-- Everything that can be picked up in the nearby.
|
||||
-- @field [parent=#nearby] openmw.core#ObjectList items
|
||||
|
||||
---
|
||||
-- Evaluates a Query.
|
||||
-- @function [parent=#nearby] selectObjects
|
||||
-- @param openmw.query#Query query
|
||||
-- @return openmw.core#ObjectList
|
||||
|
||||
---
|
||||
-- @type COLLISION_TYPE
|
||||
-- @field [parent=#COLLISION_TYPE] #number World
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
---
|
||||
-- `openmw.query` constructs queries that can be used in `world.selectObjects` and `nearby.selectObjects`.
|
||||
-- @module query
|
||||
-- @usage local query = require('openmw.query')
|
||||
|
||||
|
||||
|
||||
---
|
||||
-- Query. A Query itself can no return objects. It only holds search conditions.
|
||||
-- @type Query
|
||||
|
||||
---
|
||||
-- Add condition.
|
||||
-- @function [parent=#Query] where
|
||||
-- @param self
|
||||
-- @param condition
|
||||
-- @return #Query
|
||||
|
||||
---
|
||||
-- Limit result size.
|
||||
-- @function [parent=#Query] limit
|
||||
-- @param self
|
||||
-- @param #number maxCount
|
||||
-- @return #Query
|
||||
|
||||
|
||||
---
|
||||
-- A field that can be used in a condition
|
||||
-- @type Field
|
||||
|
||||
---
|
||||
-- Equal
|
||||
-- @function [parent=#Field] eq
|
||||
-- @param self
|
||||
-- @param value
|
||||
|
||||
---
|
||||
-- Not equal
|
||||
-- @function [parent=#Field] neq
|
||||
-- @param self
|
||||
-- @param value
|
||||
|
||||
---
|
||||
-- Lesser
|
||||
-- @function [parent=#Field] lt
|
||||
-- @param self
|
||||
-- @param value
|
||||
|
||||
---
|
||||
-- Lesser or equal
|
||||
-- @function [parent=#Field] lte
|
||||
-- @param self
|
||||
-- @param value
|
||||
|
||||
---
|
||||
-- Greater
|
||||
-- @function [parent=#Field] gt
|
||||
-- @param self
|
||||
-- @param value
|
||||
|
||||
---
|
||||
-- Greater or equal
|
||||
-- @function [parent=#Field] gte
|
||||
-- @param self
|
||||
-- @param value
|
||||
|
||||
|
||||
---
|
||||
-- @type OBJECT
|
||||
-- @field [parent=#OBJECT] #Field type
|
||||
-- @field [parent=#OBJECT] #Field recordId
|
||||
-- @field [parent=#OBJECT] #Field count
|
||||
-- @field [parent=#OBJECT] #CellFields cell
|
||||
|
||||
---
|
||||
-- Fields that can be used with any object.
|
||||
-- @field [parent=#query] #OBJECT OBJECT
|
||||
|
||||
---
|
||||
-- @type DOOR
|
||||
-- @field [parent=#DOOR] #Field isTeleport
|
||||
-- @field [parent=#DOOR] #CellFields destCell
|
||||
|
||||
---
|
||||
-- Fields that can be used only when search for doors.
|
||||
-- @field [parent=#query] #DOOR DOOR
|
||||
|
||||
---
|
||||
-- @type CellFields
|
||||
-- @field [parent=#CellFields] #Field name
|
||||
-- @field [parent=#CellFields] #Field region
|
||||
-- @field [parent=#CellFields] #Field isExterior
|
||||
|
||||
|
||||
---
|
||||
-- Base Query to select activators.
|
||||
-- @field [parent=#query] #Query activators
|
||||
|
||||
---
|
||||
-- Base Query to select actors.
|
||||
-- @field [parent=#query] #Query actors
|
||||
|
||||
---
|
||||
-- Base Query to select containers.
|
||||
-- @field [parent=#query] #Query containers
|
||||
|
||||
---
|
||||
-- Base Query to select doors.
|
||||
-- @field [parent=#query] #Query doors
|
||||
|
||||
---
|
||||
-- Base Query to select items.
|
||||
-- @field [parent=#query] #Query items
|
||||
|
||||
return nil
|
||||
|
|
@ -10,12 +10,6 @@
|
|||
-- List of currently active actors.
|
||||
-- @field [parent=#world] openmw.core#ObjectList activeActors
|
||||
|
||||
---
|
||||
-- Evaluates a Query.
|
||||
-- @function [parent=#world] selectObjects
|
||||
-- @param openmw.query#Query query
|
||||
-- @return openmw.core#ObjectList
|
||||
|
||||
---
|
||||
-- Loads a named cell
|
||||
-- @function [parent=#world] getCellByName
|
||||
|
|
Loading…
Reference in a new issue