mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Introduce BQLv2, part 1 (#2170)
This commit is contained in:
162
Robust.Server/Bql/BqlQueryManager.Parsers.cs
Normal file
162
Robust.Server/Bql/BqlQueryManager.Parsers.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Pidgin;
|
||||
using Robust.Shared.GameObjects;
|
||||
using static Pidgin.Parser;
|
||||
using static Pidgin.Parser<char>;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public partial class BqlQueryManager
|
||||
{
|
||||
private readonly Dictionary<Type, Parser<char, BqlQuerySelectorParsed>> _parsers = new();
|
||||
private Parser<char, BqlQuerySelectorParsed> _allQuerySelectors = default!;
|
||||
private Parser<char, (IEnumerable<BqlQuerySelectorParsed>, string)> SimpleQuery => Parser.Map((en, _, rest) => (en, rest), SkipWhitespaces.Then(_allQuerySelectors).Many(), String("do").Then(SkipWhitespaces), Any.ManyString());
|
||||
|
||||
private static Parser<char, string> Word => OneOf(LetterOrDigit, Char('_')).ManyString();
|
||||
|
||||
private static Parser<char, object> Objectify<T>(Parser<char, T> inp)
|
||||
{
|
||||
return Parser.Map(x => (object) x!, inp);
|
||||
}
|
||||
|
||||
private struct SubstitutionData
|
||||
{
|
||||
public string Name;
|
||||
|
||||
public SubstitutionData(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
private static Parser<char, SubstitutionData> Substitution =>
|
||||
Try(Char('$').Then(OneOf(Uppercase, Char('_')).ManyString()))
|
||||
.MapWithInput((x, _) => new SubstitutionData(x.ToString()));
|
||||
|
||||
private static Parser<char, int> Integer =>
|
||||
Try(Int(10));
|
||||
|
||||
private static Parser<char, object> SubstitutableInteger =>
|
||||
Objectify(Integer).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, double> Float =>
|
||||
Try(Real);
|
||||
|
||||
private static Parser<char, object> SubstitutableFloat =>
|
||||
Objectify(Float).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, double> Percentage =>
|
||||
Try(Real).Before(Char('%'));
|
||||
|
||||
private static Parser<char, object> SubstitutablePercentage =>
|
||||
Objectify(Percentage).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, EntityUid> EntityId =>
|
||||
Try(Parser.Map(x => new EntityUid(x), Int(10)));
|
||||
|
||||
private static Parser<char, object> SubstitutableEntityId =>
|
||||
Objectify(EntityId).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private Parser<char, Type> Component =>
|
||||
Try(Parser.Map(t => _componentFactory.GetRegistration(t).Type, Word));
|
||||
|
||||
private Parser<char, object> SubstitutableComponent =>
|
||||
Objectify(Component).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, string> QuotedString =>
|
||||
OneOf(Try(Char('"').Then(OneOf(new []
|
||||
{
|
||||
AnyCharExcept("\"")
|
||||
}).ManyString().Before(Char('"')))), Try(Word));
|
||||
|
||||
private static Parser<char, object> SubstitutableString =>
|
||||
Objectify(QuotedString).Or(Objectify(Try(Substitution)));
|
||||
|
||||
// thing to make sure it all compiles.
|
||||
[UsedImplicitly]
|
||||
private Parser<char, object> TypeSystemCheck =>
|
||||
OneOf(new[]
|
||||
{
|
||||
Objectify(Integer),
|
||||
Objectify(Percentage),
|
||||
Objectify(EntityId),
|
||||
Objectify(Component),
|
||||
Objectify(Float),
|
||||
Objectify(QuotedString)
|
||||
});
|
||||
|
||||
private Parser<char, BqlQuerySelectorParsed> BuildBqlQueryParser(BqlQuerySelector inst)
|
||||
{
|
||||
if (inst.Arguments.Length == 0)
|
||||
{
|
||||
return Parser.Map(_ => new BqlQuerySelectorParsed(new List<object>(), inst.Token, false), SkipWhitespaces);
|
||||
}
|
||||
|
||||
List<Parser<char, object>> argsParsers = new();
|
||||
|
||||
foreach (var (arg, _) in inst.Arguments.Select((x, i) => (x, i)))
|
||||
{
|
||||
List<Parser<char, object>> choices = new();
|
||||
if ((arg & QuerySelectorArgument.String) == QuerySelectorArgument.String)
|
||||
{
|
||||
choices.Add(Try(SubstitutableString.Before(SkipWhitespaces).Labelled("string argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Component) == QuerySelectorArgument.Component)
|
||||
{
|
||||
choices.Add(Try(SubstitutableComponent.Before(SkipWhitespaces).Labelled("component argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.EntityId) == QuerySelectorArgument.EntityId)
|
||||
{
|
||||
choices.Add(Try(SubstitutableEntityId.Before(SkipWhitespaces).Labelled("entity ID argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Integer) == QuerySelectorArgument.Integer)
|
||||
{
|
||||
choices.Add(Try(SubstitutableInteger.Before(SkipWhitespaces).Labelled("integer argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Percentage) == QuerySelectorArgument.Percentage)
|
||||
{
|
||||
choices.Add(Try(SubstitutablePercentage.Before(SkipWhitespaces).Labelled("percentage argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Float) == QuerySelectorArgument.Float)
|
||||
{
|
||||
choices.Add(Try(SubstitutableFloat.Before(SkipWhitespaces).Labelled("float argument")));
|
||||
}
|
||||
|
||||
argsParsers.Add(OneOf(choices));
|
||||
}
|
||||
|
||||
Parser<char, List<object>> finalParser = argsParsers[0].Map(x => new List<object> { x });
|
||||
|
||||
for (var i = 1; i < argsParsers.Count; i++)
|
||||
{
|
||||
finalParser = finalParser.Then(argsParsers[i], (list, o) =>
|
||||
{
|
||||
list.Add(o);
|
||||
return list;
|
||||
}).Labelled("arguments");
|
||||
}
|
||||
|
||||
return Parser.Map(args => new BqlQuerySelectorParsed(args, inst.Token, false), finalParser);
|
||||
}
|
||||
|
||||
private void DoParserSetup()
|
||||
{
|
||||
foreach (var inst in _instances)
|
||||
{
|
||||
_parsers.Add(inst.GetType(), BuildBqlQueryParser(inst));
|
||||
}
|
||||
|
||||
_allQuerySelectors = Parser.Map((a,b) => (a,b), Try(String("not").Before(Char(' '))).Optional(), OneOf(_instances.Select(x =>
|
||||
Try(String(x.Token).Before(Char(' '))))).Then(tok =>
|
||||
_parsers[_queriesByToken[tok].GetType()])
|
||||
).Map(pair =>
|
||||
{
|
||||
pair.b.Inverted = pair.a.HasValue;
|
||||
return pair.b;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Robust.Server/Bql/BqlQueryManager.SimpleExecutor.cs
Normal file
40
Robust.Server/Bql/BqlQueryManager.SimpleExecutor.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Pidgin;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public partial class BqlQueryManager
|
||||
{
|
||||
public (IEnumerable<EntityUid>, string) SimpleParseAndExecute(string query)
|
||||
{
|
||||
var parsed = SimpleQuery.Parse(query);
|
||||
if (parsed.Success)
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var selectors = parsed.Value.Item1.ToArray();
|
||||
if (selectors.Length == 0)
|
||||
{
|
||||
return (entityManager.GetEntityUids(), parsed.Value.Item2);
|
||||
}
|
||||
|
||||
var entities = _queriesByToken[selectors[0].Token]
|
||||
.DoInitialSelection(selectors[0].Arguments, selectors[0].Inverted, entityManager);
|
||||
|
||||
foreach (var sel in selectors[1..])
|
||||
{
|
||||
entities = _queriesByToken[sel.Token].DoSelection(entities, sel.Arguments, sel.Inverted, entityManager);
|
||||
}
|
||||
|
||||
return (entities, parsed.Value.Item2);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(parsed.Error!.RenderErrorMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Robust.Server/Bql/BqlQueryManager.cs
Normal file
49
Robust.Server/Bql/BqlQueryManager.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public partial class BqlQueryManager : IBqlQueryManager
|
||||
{
|
||||
private readonly IReflectionManager _reflectionManager;
|
||||
private readonly IComponentFactory _componentFactory;
|
||||
|
||||
private readonly List<BqlQuerySelector> _instances = new();
|
||||
private readonly Dictionary<string, BqlQuerySelector> _queriesByToken = new();
|
||||
|
||||
public BqlQueryManager()
|
||||
{
|
||||
_reflectionManager = IoCManager.Resolve<IReflectionManager>();
|
||||
_componentFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically registers all query selectors with the parser/executor.
|
||||
/// </summary>
|
||||
public void DoAutoRegistrations()
|
||||
{
|
||||
foreach (var type in _reflectionManager.FindTypesWithAttribute<RegisterBqlQuerySelectorAttribute>())
|
||||
{
|
||||
RegisterClass(type);
|
||||
}
|
||||
|
||||
DoParserSetup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internally registers the given <see cref="BqlQuerySelector"/>.
|
||||
/// </summary>
|
||||
/// <param name="bqlQuerySelector">The selector to register</param>
|
||||
private void RegisterClass(Type bqlQuerySelector)
|
||||
{
|
||||
DebugTools.Assert(bqlQuerySelector.BaseType == typeof(BqlQuerySelector));
|
||||
var inst = (BqlQuerySelector)Activator.CreateInstance(bqlQuerySelector)!;
|
||||
_instances.Add(inst);
|
||||
_queriesByToken.Add(inst.Token, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
329
Robust.Server/Bql/BqlQuerySelector.Builtin.cs
Normal file
329
Robust.Server/Bql/BqlQuerySelector.Builtin.cs
Normal file
@@ -0,0 +1,329 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
[RegisterBqlQuerySelector]
|
||||
public class WithQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "with";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Component };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var comp = (Type) arguments[0];
|
||||
return input.Where(x => entityManager.HasComponent(x, comp) ^ isInverted);
|
||||
}
|
||||
|
||||
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
if (isInverted)
|
||||
{
|
||||
return base.DoInitialSelection(arguments, isInverted, entityManager);
|
||||
}
|
||||
|
||||
return entityManager.GetAllComponents((Type) arguments[0])
|
||||
.Select(x => x.OwnerUid);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public class NamedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "named";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var r = new Regex("^" + (string) arguments[0] + "$");
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (entityManager.TryGetComponent<MetaDataComponent>(e, out var metaDataComponent))
|
||||
return r.IsMatch(metaDataComponent.EntityName) ^ isInverted;
|
||||
return isInverted;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public class ParentedToQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "parentedto";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.EntityId };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var uid = (EntityUid) arguments[0];
|
||||
return input.Where(e => (entityManager.TryGetComponent<ITransformComponent>(e, out var transform) &&
|
||||
transform.Parent?.OwnerUid == uid) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public class RecursiveParentedToQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "rparentedto";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.EntityId };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var uid = (EntityUid) arguments[0];
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (!entityManager.TryGetComponent<ITransformComponent>(e, out var transform))
|
||||
return isInverted;
|
||||
var cur = transform;
|
||||
while (cur.ParentUid != EntityUid.Invalid)
|
||||
{
|
||||
if ((cur.ParentUid == uid) ^ isInverted)
|
||||
return true;
|
||||
if (cur.Parent is null)
|
||||
return false;
|
||||
cur = cur.Parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public class ChildrenQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "children";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return input.SelectMany(e =>
|
||||
{
|
||||
return entityManager.TryGetComponent<ITransformComponent>(e, out var transform)
|
||||
? transform.Children.Select(y => y.OwnerUid)
|
||||
: new List<EntityUid>();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public class RecursiveChildrenQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "rchildren";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments,
|
||||
bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
IEnumerable<EntityUid> toSearch = input;
|
||||
List<IEnumerable<EntityUid>> children = new List<IEnumerable<EntityUid>>();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var doing = toSearch.Where(entityManager.HasComponent<ITransformComponent>).Select(entityManager.GetComponent<ITransformComponent>).ToArray();
|
||||
var search = doing.SelectMany(x => x.Children.Select(y => y.Owner));
|
||||
if (!search.Any())
|
||||
break;
|
||||
toSearch = doing.SelectMany(x => x.Children.Select(y => y.OwnerUid)).Where(x => x != EntityUid.Invalid);
|
||||
children.Add(doing.SelectMany(x => x.Children.Select(y => y.OwnerUid)).Where(x => x != EntityUid.Invalid));
|
||||
}
|
||||
|
||||
return children.SelectMany(x => x);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public class ParentQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "parent";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return input.Where(entityManager.HasComponent<ITransformComponent>)
|
||||
.Select(e => entityManager.GetComponent<ITransformComponent>(e).OwnerUid)
|
||||
.Distinct();
|
||||
}
|
||||
|
||||
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return DoSelection(entityManager.EntityQuery<ITransformComponent>().Select(x => x.OwnerUid), arguments,
|
||||
isInverted, entityManager);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public class AboveQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "above";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
|
||||
var tileTy = tileDefinitionManager[(string) arguments[0]];
|
||||
var entity = IoCManager.Resolve<IEntityManager>();
|
||||
var map = IoCManager.Resolve<IMapManager>();
|
||||
if (tileTy.TileId == 0)
|
||||
{
|
||||
return input.Where(e => entityManager.TryGetComponent<ITransformComponent>(e, out var transform) && (transform.Coordinates.GetGridId(entity) == GridId.Invalid) ^ isInverted);
|
||||
}
|
||||
else
|
||||
{
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (!entityManager.TryGetComponent<ITransformComponent>(e, out var transform)) return isInverted;
|
||||
|
||||
var gridId = transform.Coordinates.GetGridId(entity);
|
||||
if (gridId == GridId.Invalid)
|
||||
return isInverted;
|
||||
|
||||
var grid = map.GetGrid(gridId);
|
||||
return (grid.GetTileRef(transform.Coordinates).Tile.TypeId == tileTy.TileId) ^ isInverted;
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
// ReSharper disable once InconsistentNaming the name is correct shut up
|
||||
public class OnGridQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "ongrid";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.Integer };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var grid = new GridId((int) arguments[0]);
|
||||
return input.Where(e => (entityManager.TryGetComponent<ITransformComponent>(e, out var transform) && transform.GridID == grid) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
// ReSharper disable once InconsistentNaming the name is correct shut up
|
||||
public class OnMapQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "onmap";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.Integer };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var map = new MapId((int) arguments[0]);
|
||||
return input.Where(e => (entityManager.TryGetComponent<ITransformComponent>(e, out var transform) && transform.MapID == map) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public class PrototypedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "prototyped";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var name = (string) arguments[0];
|
||||
return input.Where(e => (entityManager.TryGetComponent<MetaDataComponent>(e, out var metaData) && metaData.EntityPrototype?.ID == name) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public class RecursivePrototypedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "rprototyped";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var name = (string) arguments[0];
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (!entityManager.TryGetComponent<MetaDataComponent>(e, out var metaData))
|
||||
return isInverted;
|
||||
if ((metaData.EntityPrototype?.ID == name) ^ isInverted)
|
||||
return true;
|
||||
|
||||
return (metaData.EntityPrototype?.Parent == name) ^ isInverted; // Damn, can't actually do recursive check here.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public class SelectQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "select";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Integer | QuerySelectorArgument.Percentage };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
if (arguments[0] is int)
|
||||
{
|
||||
var inp = input.OrderBy(_ => Guid.NewGuid()).ToArray();
|
||||
var taken = (int) arguments[0];
|
||||
|
||||
if (isInverted)
|
||||
taken = Math.Max(0, inp.Length - taken);
|
||||
|
||||
return inp.Take(taken);
|
||||
}
|
||||
|
||||
var enumerable = input.OrderBy(_ => Guid.NewGuid()).ToArray();
|
||||
var amount = isInverted
|
||||
? (int) Math.Floor(enumerable.Length * Math.Clamp(1 - (double) arguments[0], 0, 1))
|
||||
: (int) Math.Floor(enumerable.Length * Math.Clamp((double) arguments[0], 0, 1));
|
||||
return enumerable.Take(amount);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public class NearQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "near";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Float };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var radius = (float)(double)arguments[0];
|
||||
var entityLookup = IoCManager.Resolve<IEntityLookup>();
|
||||
|
||||
//BUG: GetEntitiesInRange effectively uses manhattan distance. This is not intended, near is supposed to be circular.
|
||||
return input.Where(entityManager.HasComponent<ITransformComponent>)
|
||||
.SelectMany(e =>
|
||||
entityLookup.GetEntitiesInRange(entityManager.GetComponent<ITransformComponent>(e).Coordinates,
|
||||
radius))
|
||||
.Select(x => x.Uid) // Sloth's fault.
|
||||
.Distinct();
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
// ReSharper disable once InconsistentNaming the name is correct shut up
|
||||
public class anchoredQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "anchored";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return input.Where(e => (entityManager.TryGetComponent<ITransformComponent>(e, out var transform) && transform.Anchored) ^ isInverted);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Robust.Server/Bql/BqlQuerySelector.cs
Normal file
64
Robust.Server/Bql/BqlQuerySelector.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
[Flags]
|
||||
[PublicAPI]
|
||||
public enum QuerySelectorArgument
|
||||
{
|
||||
Integer = 0b00000001,
|
||||
Float = 0b00000010,
|
||||
String = 0b00000100,
|
||||
Percentage = 0b00001000,
|
||||
Component = 0b00010000,
|
||||
//SubQuery = 0b00100000,
|
||||
EntityId = 0b01000000,
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public abstract class BqlQuerySelector
|
||||
{
|
||||
/// <summary>
|
||||
/// The token name for the given QuerySelector, for example `when`.
|
||||
/// </summary>
|
||||
public virtual string Token => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Arguments for the given QuerySelector, presented as "what arguments are permitted in what spot".
|
||||
/// </summary>
|
||||
public virtual QuerySelectorArgument[] Arguments => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Performs a transform over it's input entity list, whether that be filtering (selecting) or expanding the
|
||||
/// input on some criteria like what entities are nearby.
|
||||
/// </summary>
|
||||
/// <param name="input">Input entity list.</param>
|
||||
/// <param name="arguments">Parsed selector arguments.</param>
|
||||
/// <param name="isInverted">Whether the query is inverted.</param>
|
||||
/// <param name="entityManager">The entity manager.</param>
|
||||
/// <returns>New list of entities</returns>
|
||||
/// <exception cref="NotImplementedException">someone is a moron if this happens.</exception>
|
||||
public abstract IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input,
|
||||
IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager);
|
||||
|
||||
/// <summary>
|
||||
/// Performs selection as the first selector in the query. Allows for optimizing when you can be more efficient
|
||||
/// than just querying every entity.
|
||||
/// </summary>
|
||||
/// <param name="arguments"></param>
|
||||
/// <param name="isInverted"></param>
|
||||
/// <param name="entityManager"></param>
|
||||
/// <returns></returns>
|
||||
public virtual IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return DoSelection(entityManager.GetEntityUids(), arguments, isInverted, entityManager);
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
protected BqlQuerySelector() {}
|
||||
}
|
||||
}
|
||||
19
Robust.Server/Bql/BqlQuerySelectorParsed.cs
Normal file
19
Robust.Server/Bql/BqlQuerySelectorParsed.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public struct BqlQuerySelectorParsed
|
||||
{
|
||||
public List<object> Arguments;
|
||||
public string Token;
|
||||
public bool Inverted;
|
||||
|
||||
public BqlQuerySelectorParsed(List<object> arguments, string token, bool inverted)
|
||||
{
|
||||
Arguments = arguments;
|
||||
Token = token;
|
||||
Inverted = inverted;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Robust.Server/Bql/ForAllCommand.cs
Normal file
73
Robust.Server/Bql/ForAllCommand.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public class ForAllCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "forall";
|
||||
public string Description => "Runs a command over all entities with a given component";
|
||||
public string Help => "Usage: forall <bql query> do <command...>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
var queryManager = IoCManager.Resolve<IBqlQueryManager>();
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var (entities, rest) = queryManager.SimpleParseAndExecute(argStr[6..]);
|
||||
|
||||
foreach (var ent in entities.ToList())
|
||||
{
|
||||
var cmds = SubstituteEntityDetails(shell, entityManager.GetEntity(ent), rest).Split(";");
|
||||
foreach (var cmd in cmds)
|
||||
{
|
||||
shell.ExecuteCommand(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This will be refactored out soon.
|
||||
private static string SubstituteEntityDetails(IConsoleShell shell, IEntity ent, string ruleString)
|
||||
{
|
||||
// gross, is there a better way to do this?
|
||||
ruleString = ruleString.Replace("$ID", ent.Uid.ToString());
|
||||
ruleString = ruleString.Replace("$WX",
|
||||
ent.Transform.WorldPosition.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$WY",
|
||||
ent.Transform.WorldPosition.Y.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$LX",
|
||||
ent.Transform.LocalPosition.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$LY",
|
||||
ent.Transform.LocalPosition.Y.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$NAME", ent.Name);
|
||||
|
||||
if (shell.Player is IPlayerSession player)
|
||||
{
|
||||
if (player.AttachedEntity != null)
|
||||
{
|
||||
var p = player.AttachedEntity;
|
||||
ruleString = ruleString.Replace("$PID", ent.Uid.ToString());
|
||||
ruleString = ruleString.Replace("$PWX",
|
||||
p.Transform.WorldPosition.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$PWY",
|
||||
p.Transform.WorldPosition.Y.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$PLX",
|
||||
p.Transform.LocalPosition.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$PLY",
|
||||
p.Transform.LocalPosition.Y.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
return ruleString;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Robust.Server/Bql/IBqlQueryManager.cs
Normal file
11
Robust.Server/Bql/IBqlQueryManager.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public interface IBqlQueryManager
|
||||
{
|
||||
public (IEnumerable<EntityUid>, string) SimpleParseAndExecute(string query);
|
||||
void DoAutoRegistrations();
|
||||
}
|
||||
}
|
||||
14
Robust.Server/Bql/RegisterBqlQuerySelectorAttribute.cs
Normal file
14
Robust.Server/Bql/RegisterBqlQuerySelectorAttribute.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
[BaseTypeRequired(typeof(BqlQuerySelector))]
|
||||
[MeansImplicitUse]
|
||||
[PublicAPI]
|
||||
public class RegisterBqlQuerySelectorAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Robust.Server.Bql;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.DataMetrics;
|
||||
using Robust.Server.Debugging;
|
||||
@@ -73,6 +74,7 @@ namespace Robust.Server
|
||||
IoCManager.Register<IMetricsManager, MetricsManager>();
|
||||
IoCManager.Register<IAuthManager, AuthManager>();
|
||||
IoCManager.Register<IPhysicsManager, PhysicsManager>();
|
||||
IoCManager.Register<IBqlQueryManager, BqlQueryManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,6 +224,8 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> GetEntities() => Entities.Values;
|
||||
|
||||
public IEnumerable<EntityUid> GetEntityUids() => Entities.Keys;
|
||||
|
||||
/// <summary>
|
||||
/// Shuts-down and removes given Entity. This is also broadcast to all clients.
|
||||
/// </summary>
|
||||
|
||||
@@ -86,6 +86,12 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns></returns>
|
||||
IEnumerable<IEntity> GetEntities();
|
||||
|
||||
/// <summary>
|
||||
/// Returns all entities by uid
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerable<EntityUid> GetEntityUids();
|
||||
|
||||
public void QueueDeleteEntity(IEntity entity);
|
||||
|
||||
public void QueueDeleteEntity(EntityUid uid);
|
||||
|
||||
Reference in New Issue
Block a user