|
|
|
@ -5,12 +5,11 @@ using System.Linq; |
|
|
|
|
using System.Reflection; |
|
|
|
|
using System.Reflection.Emit; |
|
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
|
using System.Runtime.InteropServices; |
|
|
|
|
using gaemstone.ECS; |
|
|
|
|
using GCHandle = System.Runtime.InteropServices.GCHandle; |
|
|
|
|
|
|
|
|
|
namespace gaemstone.Utility.IL; |
|
|
|
|
|
|
|
|
|
// TODO: Implement "or" operator. |
|
|
|
|
// TODO: Support tuple syntax to match relationship pairs. |
|
|
|
|
public unsafe class IterActionGenerator |
|
|
|
|
{ |
|
|
|
@ -63,7 +62,7 @@ public unsafe class IterActionGenerator |
|
|
|
|
var instanceArg = IL.Argument<object?>(0); |
|
|
|
|
var iteratorArg = IL.Argument<Iterator>(1); |
|
|
|
|
|
|
|
|
|
var fieldIndex = 1; |
|
|
|
|
var fieldIndex = 0; |
|
|
|
|
var paramData = new List<(ParamInfo Info, Term? Term, ILocal? FieldLocal, ILocal? TempLocal)>(); |
|
|
|
|
foreach (var p in Parameters) { |
|
|
|
|
// If the parameter is unique, we don't create a term for it. |
|
|
|
@ -87,23 +86,29 @@ public unsafe class IterActionGenerator |
|
|
|
|
}, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// If this and the previous parameter are marked with [Or], do not advance the field index. |
|
|
|
|
if ((fieldIndex == 0) || (p.Kind != ParamKind.Or) || (paramData[^1].Info.Kind != ParamKind.Or)) |
|
|
|
|
fieldIndex++; |
|
|
|
|
|
|
|
|
|
var spanType = typeof(Span<>).MakeGenericType(p.FieldType); |
|
|
|
|
var fieldLocal = IL.Local(spanType, $"{p.Info.Name}Field"); |
|
|
|
|
var fieldLocal = (ILocal?)null; |
|
|
|
|
var tempLocal = (ILocal?)null; |
|
|
|
|
|
|
|
|
|
switch (p.Kind) { |
|
|
|
|
case ParamKind.Has or ParamKind.Not: |
|
|
|
|
// FIXME: Currently would not work with [Or]'d components. |
|
|
|
|
case ParamKind.Has or ParamKind.Not or ParamKind.Or: |
|
|
|
|
if (!p.ParameterType.IsValueType) break; |
|
|
|
|
// If a "has" or "not" parameter is a struct, we require a temporary local that |
|
|
|
|
// we can later load onto the stack when loading the arguments for the action. |
|
|
|
|
// If parameter is a struct, we require a temporary local that we can |
|
|
|
|
// later load onto the stack when loading the arguments for the action. |
|
|
|
|
IL.Comment($"{p.Info.Name}Temp = default({p.ParameterType});"); |
|
|
|
|
tempLocal = IL.Local(p.ParameterType); |
|
|
|
|
IL.LoadAddr(tempLocal); |
|
|
|
|
IL.Init(tempLocal.LocalType); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case ParamKind.Nullable: |
|
|
|
|
case ParamKind.Nullable or ParamKind.Or: |
|
|
|
|
IL.Comment($"{p.Info.Name}Field = iterator.MaybeField<{p.FieldType.Name}>({fieldIndex})"); |
|
|
|
|
fieldLocal = IL.Local(spanType, $"{p.Info.Name}Field"); |
|
|
|
|
IL.Load(iteratorArg); |
|
|
|
|
IL.LoadConst(fieldIndex); |
|
|
|
|
IL.Call(_iteratorMaybeFieldMethod.MakeGenericMethod(p.FieldType)); |
|
|
|
@ -117,6 +122,7 @@ public unsafe class IterActionGenerator |
|
|
|
|
|
|
|
|
|
default: |
|
|
|
|
IL.Comment($"{p.Info.Name}Field = iterator.Field<{p.FieldType.Name}>({fieldIndex})"); |
|
|
|
|
fieldLocal = IL.Local(spanType, $"{p.Info.Name}Field"); |
|
|
|
|
IL.Load(iteratorArg); |
|
|
|
|
IL.LoadConst(fieldIndex); |
|
|
|
|
IL.Call(_iteratorFieldMethod.MakeGenericMethod(p.FieldType)); |
|
|
|
@ -125,7 +131,6 @@ public unsafe class IterActionGenerator |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
paramData.Add((p, term, fieldLocal, tempLocal)); |
|
|
|
|
fieldIndex++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// If there's any reference type parameters, we need to define a GCHandle local. |
|
|
|
@ -134,12 +139,12 @@ public unsafe class IterActionGenerator |
|
|
|
|
.Any(p => !p.Info.UnderlyingType.IsValueType); |
|
|
|
|
var handleLocal = hasReferenceType ? IL.Local<GCHandle>() : null; |
|
|
|
|
|
|
|
|
|
var countLocal = IL.Local<int>("iter_count"); |
|
|
|
|
var indexLocal = IL.Local<int>("iter_index"); |
|
|
|
|
var countLocal = IL.Local<int>("iter_count"); |
|
|
|
|
|
|
|
|
|
IL.Set(indexLocal, 0); |
|
|
|
|
IL.Load(iteratorArg, _iteratorCountProp); |
|
|
|
|
IL.Store(countLocal); |
|
|
|
|
IL.Set(indexLocal, 0); |
|
|
|
|
|
|
|
|
|
// If all parameters are fixed, iterator count will be 0, but since |
|
|
|
|
// the query matched, we want to run the callback at least once. |
|
|
|
@ -158,6 +163,7 @@ public unsafe class IterActionGenerator |
|
|
|
|
IL.Load(instanceArg); |
|
|
|
|
|
|
|
|
|
foreach (var (info, term, fieldLocal, tempLocal) in paramData) { |
|
|
|
|
var isValueType = info.UnderlyingType.IsValueType; |
|
|
|
|
switch (info.Kind) { |
|
|
|
|
|
|
|
|
|
case ParamKind.GlobalUnique: |
|
|
|
@ -170,9 +176,9 @@ public unsafe class IterActionGenerator |
|
|
|
|
_uniqueParameters[info.ParameterType](IL, iteratorArg, indexLocal!); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case ParamKind.Has or ParamKind.Not: |
|
|
|
|
if (info.ParameterType.IsValueType) |
|
|
|
|
IL.LoadObj(tempLocal!); |
|
|
|
|
// FIXME: Currently would not work with [Or]'d components. |
|
|
|
|
case ParamKind.Has or ParamKind.Not or ParamKind.Or: |
|
|
|
|
if (isValueType) IL.LoadObj(tempLocal!); |
|
|
|
|
else IL.LoadNull(); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
@ -214,7 +220,7 @@ public unsafe class IterActionGenerator |
|
|
|
|
IL.MarkLabel(doneLabel); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!info.UnderlyingType.IsValueType) { |
|
|
|
|
if (!isValueType) { |
|
|
|
|
IL.Comment($"Convert nint to {info.UnderlyingType.GetFriendlyName()}"); |
|
|
|
|
IL.Call(_handleFromIntPtrMethod); |
|
|
|
|
IL.Store(handleLocal!); |
|
|
|
@ -252,7 +258,7 @@ public unsafe class IterActionGenerator |
|
|
|
|
public Type? Source { get; } |
|
|
|
|
|
|
|
|
|
public bool IsRequired => (Kind < ParamKind.Nullable); |
|
|
|
|
public bool IsByRef => (Kind >= ParamKind.In) && (Kind <= ParamKind.Ref); |
|
|
|
|
public bool IsByRef => (Kind is ParamKind.In or ParamKind.Ref); |
|
|
|
|
public bool IsFixed => (Kind == ParamKind.GlobalUnique) || (Source != null); |
|
|
|
|
|
|
|
|
|
private ParamInfo(ParameterInfo info, ParamKind kind, |
|
|
|
@ -265,59 +271,77 @@ public unsafe class IterActionGenerator |
|
|
|
|
// Reference types have a backing type of nint - they're pointers. |
|
|
|
|
FieldType = underlyingType.IsValueType ? underlyingType : typeof(nint); |
|
|
|
|
|
|
|
|
|
if (UnderlyingType.Has<SingletonAttribute>()) Source = underlyingType; |
|
|
|
|
if (Info.Get<SourceAttribute>() is SourceAttribute attr) Source = attr.Type; |
|
|
|
|
// TODO: Needs support for the new attributes. |
|
|
|
|
Source = Info.Get<SourceAttribute>()?.Type |
|
|
|
|
?? (UnderlyingType.Has<SingletonAttribute>() ? UnderlyingType : null); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public static ParamInfo Build(ParameterInfo info) |
|
|
|
|
{ |
|
|
|
|
if (info.IsOptional) throw new ArgumentException("Optional parameters are not supported\nParameter: " + info); |
|
|
|
|
if (info.ParameterType.IsArray) throw new ArgumentException("Arrays are not supported\nParameter: " + info); |
|
|
|
|
if (info.ParameterType.IsPointer) throw new ArgumentException("Pointers are not supported\nParameter: " + info); |
|
|
|
|
|
|
|
|
|
if (_globalUniqueParameters.ContainsKey(info.ParameterType)) |
|
|
|
|
return new(info, ParamKind.GlobalUnique, info.ParameterType, info.ParameterType); |
|
|
|
|
if (_uniqueParameters.ContainsKey(info.ParameterType)) |
|
|
|
|
return new(info, ParamKind.Unique, info.ParameterType, info.ParameterType); |
|
|
|
|
if (info.IsOptional) throw new ArgumentException($"Optional parameters are not supported\nParameter: {info}"); |
|
|
|
|
if (info.ParameterType.IsArray) throw new ArgumentException($"Arrays are not supported\nParameter: {info}"); |
|
|
|
|
if (info.ParameterType.IsPointer) throw new ArgumentException($"Pointers are not supported\nParameter: {info}"); |
|
|
|
|
if (info.ParameterType.IsPrimitive) throw new ArgumentException($"Primitives are not supported\nParameter: {info}"); |
|
|
|
|
|
|
|
|
|
// Find out initial parameter kind from provided attribute. |
|
|
|
|
var fromAttributes = new List<ParamKind>(); |
|
|
|
|
if (info.Has< InAttribute>()) fromAttributes.Add(ParamKind.In); |
|
|
|
|
if (info.Has<OutAttribute>()) fromAttributes.Add(ParamKind.Out); |
|
|
|
|
if (info.Has<HasAttribute>()) fromAttributes.Add(ParamKind.Has); |
|
|
|
|
if (info.Has< OrAttribute>()) fromAttributes.Add(ParamKind.Or); |
|
|
|
|
if (info.Has<NotAttribute>()) fromAttributes.Add(ParamKind.Not); |
|
|
|
|
// Throw an error if multiple incompatible attributes were found. |
|
|
|
|
if (fromAttributes.Count > 1) throw new ArgumentException( |
|
|
|
|
"Parameter must not be marked with multiple attributes: " |
|
|
|
|
+ string.Join(", ", fromAttributes.Select(a => $"[{a}]")) |
|
|
|
|
+ $"\nParameter: {info}"); |
|
|
|
|
var kind = fromAttributes.FirstOrNull() ?? ParamKind.Normal; |
|
|
|
|
|
|
|
|
|
// Handle unique parameters such as Universe, EntityRef, ... |
|
|
|
|
var isGlobalUnique = _globalUniqueParameters.ContainsKey(info.ParameterType); |
|
|
|
|
var isUnique = _uniqueParameters.ContainsKey(info.ParameterType); |
|
|
|
|
if (isGlobalUnique || isUnique) { |
|
|
|
|
if (kind != ParamKind.Normal) throw new ArgumentException( |
|
|
|
|
$"Unique parameter {info.ParameterType.Name} does not support [{kind}]\nParameter: {info}"); |
|
|
|
|
kind = isGlobalUnique ? ParamKind.GlobalUnique : ParamKind.Unique; |
|
|
|
|
return new(info, kind, info.ParameterType, info.ParameterType); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var isByRef = info.ParameterType.IsByRef; |
|
|
|
|
var isNullable = info.IsNullable(); |
|
|
|
|
var isByRef = info.ParameterType.IsByRef; |
|
|
|
|
|
|
|
|
|
if (info.Has<NotAttribute>()) { |
|
|
|
|
if (isByRef || isNullable) throw new ArgumentException( |
|
|
|
|
"Parameter with NotAttribute must not be ByRef or nullable\nParameter: " + info); |
|
|
|
|
return new(info, ParamKind.Not, info.ParameterType, info.ParameterType); |
|
|
|
|
if (info.ParameterType.Has<TagAttribute>() && (kind is not (ParamKind.Has or ParamKind.Not or ParamKind.Or))) { |
|
|
|
|
if (kind is not ParamKind.Normal) throw new ArgumentException($"Parameter does not support [{kind}]\nParameter: {info}"); |
|
|
|
|
kind = ParamKind.Has; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (info.Has<HasAttribute>() || info.ParameterType.Has<TagAttribute>()) { |
|
|
|
|
if (isByRef || isNullable) throw new ArgumentException( |
|
|
|
|
"Parameter with HasAttribute / TagAttribute must not be ByRef or nullable\nParameter: " + info); |
|
|
|
|
return new(info, ParamKind.Has, info.ParameterType, info.ParameterType); |
|
|
|
|
if (kind is ParamKind.Not or ParamKind.Has) { |
|
|
|
|
if (isNullable) throw new ArgumentException($"Parameter does not support Nullable\nParameter: {info}"); |
|
|
|
|
if (isByRef) throw new ArgumentException($"Parameter does not support ByRef\nParameter: {info}"); |
|
|
|
|
return new(info, kind, info.ParameterType, info.ParameterType); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var kind = ParamKind.Normal; |
|
|
|
|
var underlyingType = info.ParameterType; |
|
|
|
|
|
|
|
|
|
if (info.IsNullable()) { |
|
|
|
|
if (isNullable) { |
|
|
|
|
if (isByRef) throw new ArgumentException($"Parameter does not support ByRef\nParameter: {info}"); |
|
|
|
|
if (info.ParameterType.IsValueType) |
|
|
|
|
underlyingType = Nullable.GetUnderlyingType(info.ParameterType)!; |
|
|
|
|
kind = ParamKind.Nullable; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (info.ParameterType.IsByRef) { |
|
|
|
|
if (kind == ParamKind.Nullable) throw new ArgumentException( |
|
|
|
|
"ByRef and Nullable are not supported together\nParameter: " + info); |
|
|
|
|
if (kind != ParamKind.Normal) throw new ArgumentException( |
|
|
|
|
$"Parameter does not support [{kind}]\nParameter: {info}"); |
|
|
|
|
underlyingType = info.ParameterType.GetElementType()!; |
|
|
|
|
if (!underlyingType.IsValueType) throw new ArgumentException( |
|
|
|
|
"Reference types can't also be ByRef\nParameter: " + info); |
|
|
|
|
$"Reference types can't also be ByRef\nParameter: {info}"); |
|
|
|
|
kind = info.IsIn ? ParamKind.In |
|
|
|
|
: info.IsOut ? ParamKind.Out |
|
|
|
|
: ParamKind.Ref; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (underlyingType.IsPrimitive) throw new ArgumentException( |
|
|
|
|
"Primitives are not supported\nParameter: " + info); |
|
|
|
|
$"Primitives are not supported\nParameter: {info}"); |
|
|
|
|
|
|
|
|
|
return new(info, kind, info.ParameterType, underlyingType); |
|
|
|
|
} |
|
|
|
@ -331,38 +355,61 @@ public unsafe class IterActionGenerator |
|
|
|
|
/// For example <see cref="ECS.Universe"/> or <see cref="TimeSpan"/>. |
|
|
|
|
/// </summary> |
|
|
|
|
GlobalUnique, |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Not part of the resulting query's terms. |
|
|
|
|
/// Unique value for each iterated entity. |
|
|
|
|
/// For example <see cref="EntityRef"/>. |
|
|
|
|
/// </summary> |
|
|
|
|
Unique, |
|
|
|
|
|
|
|
|
|
/// <summary> Passed by value. </summary> |
|
|
|
|
Normal, |
|
|
|
|
/// <summary> Struct passed with the "in" modifier. </summary> |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Struct passed with the "in" modifier, allowing direct pointer access. |
|
|
|
|
/// Manually applied with <see cref="InAttribute"/>. |
|
|
|
|
/// Marks a component as being read from. |
|
|
|
|
/// </summary> |
|
|
|
|
In, |
|
|
|
|
/// <summary> Struct passed with the "out" modifier. </summary> |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Struct passed with the "out" modifier, allowing direct pointer access. |
|
|
|
|
/// Manually applied with <see cref="HasAttribute"/>. |
|
|
|
|
/// Marks a component as being written to. |
|
|
|
|
/// </summary> |
|
|
|
|
Out, |
|
|
|
|
/// <summary> Struct passed with the "ref" modifier. </summary> |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Struct passed with the "ref" modifier, allowing direct pointer access. |
|
|
|
|
/// Marks a component as being read from and written to. |
|
|
|
|
/// </summary> |
|
|
|
|
Ref, |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Only checks for presence. |
|
|
|
|
/// Manually applied with <see cref="HasAttribute"/>. |
|
|
|
|
/// Automatically applied for types with <see cref="TagAttribute"/>. |
|
|
|
|
/// Marks a component as not being accessed. |
|
|
|
|
/// </summary> |
|
|
|
|
Has, |
|
|
|
|
/// <summary> Struct or class passed as <see cref="T?"/>. </summary> |
|
|
|
|
Nullable, |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Matches any terms in a chain of "or" terms. |
|
|
|
|
/// Applied with <see cref="OrAttribute"/>. |
|
|
|
|
/// Implies <see cref="Nullable"/>. |
|
|
|
|
/// Struct or class passed as <see cref="T?"/>. |
|
|
|
|
/// </summary> |
|
|
|
|
Or, |
|
|
|
|
Nullable, |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Only checks for absence. |
|
|
|
|
/// Applied with <see cref="NotAttribute"/>. |
|
|
|
|
/// </summary> |
|
|
|
|
Not, |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Matches any terms in a chain of "or" terms. |
|
|
|
|
/// Applied with <see cref="OrAttribute"/>. |
|
|
|
|
/// Implies <see cref="Nullable"/>. |
|
|
|
|
/// </summary> |
|
|
|
|
Or, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|