1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-16 15:29:55 +00:00

[Lua] Remove queries

This commit is contained in:
Petr Mikheev 2022-03-05 20:11:17 +01:00
parent 9af49cfa68
commit fa115418eb
23 changed files with 2 additions and 995 deletions

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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));

View file

@ -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);
}
}

View file

@ -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>

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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");
}
}

View file

@ -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

View file

@ -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;
};
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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. |

View file

@ -1,5 +0,0 @@
Package openmw.query
====================
.. raw:: html
:file: generated_html/openmw_query.html

View file

@ -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
===========================

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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