@ -1,5 +1,6 @@
using System ;
using System ;
using System.Collections.Generic ;
using System.Collections.Generic ;
using System.Collections.Immutable ;
using System.Linq ;
using System.Linq ;
using System.Reflection ;
using System.Reflection ;
using System.Reflection.Emit ;
using System.Reflection.Emit ;
@ -9,6 +10,8 @@ using gaemstone.ECS;
namespace gaemstone.Utility.IL ;
namespace gaemstone.Utility.IL ;
// TODO: Implement "or" operator.
// TODO: Support tuple syntax to match relationship pairs.
public unsafe class IterActionGenerator
public unsafe class IterActionGenerator
{
{
private static readonly ConstructorInfo _ entityRefCtor = typeof ( EntityRef ) . GetConstructors ( ) . Single ( ) ;
private static readonly ConstructorInfo _ entityRefCtor = typeof ( EntityRef ) . GetConstructors ( ) . Single ( ) ;
@ -35,7 +38,7 @@ public unsafe class IterActionGenerator
public Universe Universe { get ; }
public Universe Universe { get ; }
public MethodInfo Method { get ; }
public MethodInfo Method { get ; }
public ParamInfo [ ] Parameters { get ; }
public IReadOnlyList < ParamInfo > Parameters { get ; }
public IReadOnlyList < Term > Terms { get ; }
public IReadOnlyList < Term > Terms { get ; }
public Action < object? , Iterator > GeneratedAction { get ; }
public Action < object? , Iterator > GeneratedAction { get ; }
@ -44,12 +47,12 @@ public unsafe class IterActionGenerator
public void RunWithTryCatch ( object? instance , Iterator iter )
public void RunWithTryCatch ( object? instance , Iterator iter )
{
{
try { GeneratedAction ( instance , iter ) ; } catch {
try { GeneratedAction ( instance , iter ) ; } catch {
Console . WriteLine ( "Exception occured while running:" ) ;
Console . Error . WriteLine ( "Exception occured while running:" ) ;
Console . WriteLine ( " " + Method ) ;
Console . Error . WriteLine ( " " + Method ) ;
Console . WriteLine ( ) ;
Console . Error . WriteLine ( ) ;
Console . WriteLine ( "Method's IL code:" ) ;
Console . Error . WriteLine ( "Method's IL code:" ) ;
Console . WriteLine ( ReadableString ) ;
Console . Error . WriteLine ( ReadableString ) ;
Console . WriteLine ( ) ;
Console . Error . WriteLine ( ) ;
throw ;
throw ;
}
}
}
}
@ -59,46 +62,24 @@ public unsafe class IterActionGenerator
Universe = universe ;
Universe = universe ;
Method = method ;
Method = method ;
Parameters = method . GetParameters ( ) . Select ( ParamInfo . Build ) . ToArray ( ) ;
var name = "<>Query_" + string . Join ( "_" , method . Name ) ;
// if (!Parameters.Any(c => c.IsRequired && (c.Kind != ParamKind.Unique)))
// throw new ArgumentException($"At least one parameter in {method} is required");
var name = "<>Query_" + string . Join ( "_" , Parameters . Select ( p = > p . UnderlyingType . Name ) ) ;
var genMethod = new DynamicMethod ( name , null , new [ ] { typeof ( object ) , typeof ( Iterator ) } ) ;
var genMethod = new DynamicMethod ( name , null , new [ ] { typeof ( object ) , typeof ( Iterator ) } ) ;
var IL = new ILGeneratorWrapper ( genMethod ) ;
var IL = new ILGeneratorWrapper ( genMethod ) ;
var instanceArg = IL . Argument < object? > ( 0 ) ;
var instanceArg = IL . Argument < object? > ( 0 ) ;
var iteratorArg = IL . Argument < Iterator > ( 1 ) ;
var iteratorArg = IL . Argument < Iterator > ( 1 ) ;
// If parameters only contains global unique paremeters (such as
var fieldIndex = 1 ;
// Universe or TimeSpan), or is empty, just run the system, since
var parameters = new List < ( ParamInfo Info , Term ? Term , ILocal ? FieldLocal , ILocal ? TempLocal ) > ( ) ;
// without terms it won't match any entities anyway.
foreach ( var info in method . GetParameters ( ) ) {
if ( Parameters . All ( p = > p . Kind = = ParamKind . GlobalUnique ) ) {
var p = ParamInfo . Build ( info ) ;
if ( ! Method . IsStatic ) IL . Load ( instanceArg ) ;
foreach ( var p in Parameters ) {
IL . Comment ( $"Global unique parameter {p.ParameterType.GetFriendlyName()}" ) ;
_ globalUniqueParameters [ p . ParameterType ] ( IL , iteratorArg ) ;
}
IL . Call ( Method ) ;
IL . Return ( ) ;
Terms = Array . Empty < Term > ( ) ;
GeneratedAction = genMethod . CreateDelegate < Action < object? , Iterator > > ( ) ;
ReadableString = IL . ToReadableString ( ) ;
return ;
}
var terms = new List < Term > ( ) ;
var fieldLocals = new ILocal [ Parameters . Length ] ;
var tempLocals = new ILocal [ Parameters . Length ] ;
for ( var i = 0 ; i < Parameters . Length ; i + + ) {
// If the parameter is unique, we don't create a term for it.
var p = Parameters [ i ] ;
if ( p . Kind < = ParamKind . Unique )
if ( p . Kind < = ParamKind . Unique ) continue ;
{ parameters . Add ( ( p , null , null , null ) ) ; continue ; }
// Add an entry to the terms to look for this type .
// Create a term to add to the query.
terms . Add ( new ( universe . LookupOrThrow ( p . UnderlyingType ) ) {
var term = new Term ( universe . LookupOrThrow ( p . UnderlyingType ) ) {
Source = ( p . Source ! = null ) ? ( TermID ) Universe . LookupOrThrow ( p . Source ) : null ,
Source = ( p . Source ! = null ) ? ( TermID ) Universe . LookupOrThrow ( p . Source ) : null ,
InOut = p . Kind switch {
InOut = p . Kind switch {
ParamKind . In = > TermInOutKind . In ,
ParamKind . In = > TermInOutKind . In ,
@ -108,116 +89,145 @@ public unsafe class IterActionGenerator
} ,
} ,
Oper = p . Kind switch {
Oper = p . Kind switch {
ParamKind . Not = > TermOperKind . Not ,
ParamKind . Not = > TermOperKind . Not ,
ParamKind . Or = > TermOperKind . Or ,
_ when ! p . IsRequired = > TermOperKind . Optional ,
_ when ! p . IsRequired = > TermOperKind . Optional ,
_ = > default ,
_ = > default ,
} ,
} ,
} ) ;
} ;
// Create a Span<T> local and initialize it to iterator.Field<T>(i).
var spanType = typeof ( Span < > ) . MakeGenericType ( p . FieldType ) ;
var spanType = typeof ( Span < > ) . MakeGenericType ( p . FieldType ) ;
fieldLocals [ i ] = IL . Local ( spanType , $"field_{i}" ) ;
var fieldLocal = IL . Local ( spanType , $"{info.Name}Field" ) ;
if ( p . Kind is ParamKind . Has or ParamKind . Not ) {
var tempLocal = ( ILocal ? ) null ;
switch ( p . Kind ) {
case ParamKind . Has or ParamKind . Not :
if ( ! p . ParameterType . IsValueType ) break ;
// If a "has" or "not" parameter is a struct, we require a temporary local that
// 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.
// we can later load onto the stack when loading the arguments for the action.
if ( p . ParameterType . IsValueType ) {
IL . Comment ( $"{info.Name}Temp = default({p.ParameterType});" ) ;
IL . Comment ( $"temp_{i} = default({p.ParameterType});" ) ;
tempLocal = IL . Local ( p . ParameterType ) ;
tempLocals [ i ] = IL . Local ( p . ParameterType ) ;
IL . LoadAddr ( tempLocal ) ;
IL . LoadAddr ( tempLocals [ i ] ) ;
IL . Init ( tempLocal . LocalType ) ;
IL . Init ( tempLocals [ i ] . LocalType ) ;
break ;
}
} else if ( p . IsRequired ) {
case ParamKind . Nullable :
IL . Comment ( $"field_{i} = iterator.Field<{p.FieldType.Name}>({terms.Count})" ) ;
IL . Comment ( $"{info.Name}Field = iterator.MaybeField<{p.FieldType.Name}>({fieldIndex})" ) ;
IL . Load ( iteratorArg ) ;
IL . LoadConst ( terms . Count ) ;
IL . Call ( _ iteratorFieldMethod . MakeGenericMethod ( p . FieldType ) ) ;
IL . Store ( fieldLocals [ i ] ) ;
} else {
IL . Comment ( $"field_{i} = iterator.MaybeField<{p.FieldType.Name}>({terms.Count})" ) ;
IL . Load ( iteratorArg ) ;
IL . Load ( iteratorArg ) ;
IL . LoadConst ( terms . Count ) ;
IL . LoadConst ( fieldIndex ) ;
IL . Call ( _ iteratorMaybeFieldMethod . MakeGenericMethod ( p . FieldType ) ) ;
IL . Call ( _ iteratorMaybeFieldMethod . MakeGenericMethod ( p . FieldType ) ) ;
IL . Store ( fieldLocals [ i ] ) ;
IL . Store ( fieldLocal ) ;
}
IL . Comment ( $"{info.Name}Temp = default({p.ParameterType});" ) ;
tempLocal = IL . Local ( p . ParameterType ) ;
IL . LoadAddr ( tempLocal ) ;
IL . Init ( tempLocal . LocalType ) ;
break ;
if ( p . Kind = = ParamKind . Nullable ) {
default :
IL . Comment ( $"temp_{i} = default({p.ParameterType});" ) ;
IL . Comment ( $"{info.Name}Field = iterator.Field<{p.FieldType.Name}>({fieldIndex})" ) ;
tempLocals [ i ] = IL . Local ( p . ParameterType ) ;
IL . Load ( iteratorArg ) ;
IL . LoadAddr ( tempLocals [ i ] ) ;
IL . LoadConst ( fieldIndex ) ;
IL . Init ( tempLocals [ i ] . LocalType ) ;
IL . Call ( _ iteratorFieldMethod . MakeGenericMethod ( p . FieldType ) ) ;
IL . Store ( fieldLocal ) ;
break ;
}
}
parameters . Add ( ( p , term , fieldLocal , tempLocal ) ) ;
fieldIndex + + ;
}
}
// If there's any reference type parameters, we need to define a GCHandle local.
// If there's any reference type parameters, we need to define a GCHandle local.
var hasReferenceType = P arameters
var hasReferenceType = p arameters
. Where ( p = > p . Kind > ParamKind . Unique )
. Where ( p = > p . Info . Kind > ParamKind . Unique )
. Any ( p = > ! p . UnderlyingType . IsValueType ) ;
. Any ( p = > ! p . Info . UnderlyingType . IsValueType ) ;
var handleLocal = hasReferenceType ? IL . Local < GCHandle > ( ) : null ;
var handleLocal = hasReferenceType ? IL . Local < GCHandle > ( ) : null ;
using ( IL . For ( ( ) = > IL . Load ( iteratorArg , _ iteratorCountProp ) , out var currentLocal ) ) {
IDisposable ? forLoopBlock = null ;
if ( ! Method . IsStatic ) IL . Load ( instanceArg ) ;
ILocal < int > ? forCurrentLocal = null ;
for ( var i = 0 ; i < Parameters . Length ; i + + ) {
// If all parameters are fixed, iterator count will be 0, but since
var p = Parameters [ i ] ;
// the query matched, we want to run the callback at least once.
if ( p . Kind = = ParamKind . GlobalUnique ) {
if ( parameters . Any ( p = > ! p . Info . IsFixed ) )
IL . Comment ( $"Global unique parameter {p.ParameterType.GetFriendlyName()}" ) ;
forLoopBlock = IL . For ( ( ) = > IL . Load ( iteratorArg , _ iteratorCountProp ) , out forCurrentLocal ) ;
_ globalUniqueParameters [ p . ParameterType ] ( IL , iteratorArg ) ;
} else if ( p . Kind = = ParamKind . Unique ) {
if ( ! Method . IsStatic )
IL . Comment ( $"Unique parameter {p.ParameterType.GetFriendlyName()}" ) ;
IL . Load ( instanceArg ) ;
_ uniqueParameters [ p . ParameterType ] ( IL , iteratorArg , currentLocal ) ;
} else if ( p . Kind is ParamKind . Has or ParamKind . Not ) {
foreach ( var ( info , term , fieldLocal , tempLocal ) in parameters ) {
if ( p . ParameterType . IsValueType )
switch ( info . Kind ) {
IL . LoadObj ( tempLocals [ i ] ! ) ;
case ParamKind . GlobalUnique :
IL . Comment ( $"Global unique parameter {info.ParameterType.GetFriendlyName()}" ) ;
_ globalUniqueParameters [ info . ParameterType ] ( IL , iteratorArg ) ;
break ;
case ParamKind . Unique :
IL . Comment ( $"Unique parameter {info.ParameterType.GetFriendlyName()}" ) ;
_ uniqueParameters [ info . ParameterType ] ( IL , iteratorArg , forCurrentLocal ! ) ;
break ;
case ParamKind . Has or ParamKind . Not :
if ( info . ParameterType . IsValueType )
IL . LoadObj ( tempLocal ! ) ;
else IL . LoadNull ( ) ;
else IL . LoadNull ( ) ;
} else {
break ;
var spanType = typeof ( Span < > ) . MakeGenericType ( p . FieldType ) ;
default :
var spanType = typeof ( Span < > ) . MakeGenericType ( info . FieldType ) ;
var spanItemMethod = spanType . GetProperty ( "Item" ) ! . GetMethod ! ;
var spanItemMethod = spanType . GetProperty ( "Item" ) ! . GetMethod ! ;
var spanLengthMethod = spanType . GetProperty ( "Length" ) ! . GetMethod ! ;
var spanLengthMethod = spanType . GetProperty ( "Length" ) ! . GetMethod ! ;
IL . Comment ( $"Parameter {p.ParameterType.GetFriendlyName()}" ) ;
IL . Comment ( $"Parameter {info.ParameterType.GetFriendlyName()}" ) ;
if ( p . IsByRef ) {
if ( info . IsByRef ) {
IL . LoadAddr ( fieldLocals [ i ] ! ) ;
IL . LoadAddr ( fieldLocal ! ) ;
IL . Load ( currentLocal ) ;
if ( info . IsFixed ) IL . LoadConst ( 0 ) ;
else IL . Load ( forCurrentLocal ! ) ;
IL . Call ( spanItemMethod ) ;
IL . Call ( spanItemMethod ) ;
} else if ( p . IsRequired ) {
} else if ( info . IsRequired ) {
IL . LoadAddr ( fieldLocals [ i ] ! ) ;
IL . LoadAddr ( fieldLocal ! ) ;
IL . Load ( currentLocal ) ;
if ( info . IsFixed ) IL . LoadConst ( 0 ) ;
else IL . Load ( forCurrentLocal ! ) ;
IL . Call ( spanItemMethod ) ;
IL . Call ( spanItemMethod ) ;
IL . LoadObj ( p . FieldType ) ;
IL . LoadObj ( info . FieldType ) ;
} else {
} else {
var elseLabel = IL . DefineLabel ( ) ;
var elseLabel = IL . DefineLabel ( ) ;
var doneLabel = IL . DefineLabel ( ) ;
var doneLabel = IL . DefineLabel ( ) ;
IL . LoadAddr ( fieldLocals [ i ] ! ) ;
IL . LoadAddr ( fieldLocal ! ) ;
IL . Call ( spanLengthMethod ) ;
IL . Call ( spanLengthMethod ) ;
IL . GotoIfFalse ( elseLabel ) ;
IL . GotoIfFalse ( elseLabel ) ;
IL . LoadAddr ( fieldLocals [ i ] ! ) ;
IL . LoadAddr ( fieldLocal ! ) ;
IL . Load ( currentLocal ) ;
if ( info . IsFixed ) IL . LoadConst ( 0 ) ;
else IL . Load ( forCurrentLocal ! ) ;
IL . Call ( spanItemMethod ) ;
IL . Call ( spanItemMethod ) ;
IL . LoadObj ( p . FieldType ) ;
IL . LoadObj ( info . FieldType ) ;
if ( p . Kind = = ParamKind . Nullable )
if ( info . Kind = = ParamKind . Nullable )
IL . New ( p . ParameterType ) ;
IL . New ( info . ParameterType ) ;
IL . Goto ( doneLabel ) ;
IL . Goto ( doneLabel ) ;
IL . MarkLabel ( elseLabel ) ;
IL . MarkLabel ( elseLabel ) ;
if ( p . Kind = = ParamKind . Nullable )
if ( info . Kind = = ParamKind . Nullable )
IL . LoadObj ( tempLocals [ i ] ! ) ;
IL . LoadObj ( tempLocal ! ) ;
else IL . LoadNull ( ) ;
else IL . LoadNull ( ) ;
IL . MarkLabel ( doneLabel ) ;
IL . MarkLabel ( doneLabel ) ;
}
}
if ( ! p . UnderlyingType . IsValueType ) {
if ( ! info . UnderlyingType . IsValueType ) {
IL . Comment ( $"Convert nint to {p .UnderlyingType.GetFriendlyName()}" ) ;
IL . Comment ( $"Convert nint to {info .UnderlyingType.GetFriendlyName()}" ) ;
IL . Call ( _ handleFromIntPtrMethod ) ;
IL . Call ( _ handleFromIntPtrMethod ) ;
IL . Store ( handleLocal ! ) ;
IL . Store ( handleLocal ! ) ;
IL . LoadAddr ( handleLocal ! ) ;
IL . LoadAddr ( handleLocal ! ) ;
IL . Call ( _ handleTargetProp . GetMethod ! ) ;
IL . Call ( _ handleTargetProp . GetMethod ! ) ;
IL . Cast ( p . UnderlyingType ) ;
IL . Cast ( info . UnderlyingType ) ;
}
}
break ;
}
}
}
}
IL . Call ( Method ) ;
IL . Call ( Method ) ;
}
forLoopBlock ? . Dispose ( ) ;
IL . Return ( ) ;
IL . Return ( ) ;
Terms = terms . AsReadOnly ( ) ;
Parameters = parameters . Select ( p = > p . Info ) . ToImmutableList ( ) ;
Terms = parameters . Where ( p = > p . Term ! = null ) . Select ( p = > p . Term ! ) . ToImmutableList ( ) ;
GeneratedAction = genMethod . CreateDelegate < Action < object? , Iterator > > ( ) ;
GeneratedAction = genMethod . CreateDelegate < Action < object? , Iterator > > ( ) ;
ReadableString = IL . ToReadableString ( ) ;
ReadableString = IL . ToReadableString ( ) ;
}
}
@ -228,7 +238,6 @@ public unsafe class IterActionGenerator
public class ParamInfo
public class ParamInfo
{
{
public ParameterInfo Info { get ; }
public ParameterInfo Info { get ; }
public int Index { get ; }
public ParamKind Kind { get ; }
public ParamKind Kind { get ; }
public Type ParameterType { get ; }
public Type ParameterType { get ; }
@ -239,36 +248,33 @@ public unsafe class IterActionGenerator
public bool IsRequired = > ( Kind < ParamKind . Nullable ) ;
public bool IsRequired = > ( Kind < ParamKind . Nullable ) ;
public bool IsByRef = > ( Kind > = ParamKind . In ) & & ( Kind < = ParamKind . Ref ) ;
public bool IsByRef = > ( Kind > = ParamKind . In ) & & ( Kind < = ParamKind . Ref ) ;
public bool IsFixed = > ( Kind = = ParamKind . GlobalUnique ) | | ( Source ! = null ) ;
private ParamInfo (
private ParamInfo ( ParameterInfo info , ParamKind kind ,
ParameterInfo info , int index , ParamKind kind ,
Type paramType , Type underlyingType )
Type paramType , Type underlyingType )
{
{
Info = info ;
Info = info ;
Index = index ;
Kind = kind ;
Kind = kind ;
ParameterType = paramType ;
ParameterType = paramType ;
UnderlyingType = underlyingType ;
UnderlyingType = underlyingType ;
// Reference types have a backing type of nint - they're pointers.
// Reference types have a backing type of nint - they're pointers.
FieldType = underlyingType . IsValueType ? underlyingType : typeof ( nint ) ;
FieldType = underlyingType . IsValueType ? underlyingType : typeof ( nint ) ;
// FIXME: Reimplement singletons somehow.
if ( UnderlyingType . Has < SingletonAttribute > ( ) ) Source = underlyingType ;
// if (UnderlyingType.Has<EntityAttribute>()) Source = underlyingType;
if ( Info . Get < SourceAttribute > ( ) is SourceAttribute attr ) Source = attr . Type ;
if ( Info . Get < SourceAttribute > ( ) is SourceAttribute attr ) Source = attr . Type ;
// TODO: Needs support for the new attributes.
// TODO: Needs support for the new attributes.
}
}
public static ParamInfo Build ( ParameterInfo info , int index )
public static ParamInfo Build ( ParameterInfo info )
{
{
if ( info . IsOptional ) throw new ArgumentException ( "Optional parameters are not supported\nParameter: " + 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 . 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 . IsPointer ) throw new ArgumentException ( "Pointers are not supported\nParameter: " + info ) ;
if ( _ globalUniqueParameters . ContainsKey ( info . ParameterType ) )
if ( _ globalUniqueParameters . ContainsKey ( info . ParameterType ) )
return new ( info , index , ParamKind . GlobalUnique , info . ParameterType , info . ParameterType ) ;
return new ( info , ParamKind . GlobalUnique , info . ParameterType , info . ParameterType ) ;
if ( _ uniqueParameters . ContainsKey ( info . ParameterType ) )
if ( _ uniqueParameters . ContainsKey ( info . ParameterType ) )
return new ( info , index , ParamKind . Unique , info . ParameterType , info . ParameterType ) ;
return new ( info , ParamKind . Unique , info . ParameterType , info . ParameterType ) ;
var isByRef = info . ParameterType . IsByRef ;
var isByRef = info . ParameterType . IsByRef ;
var isNullable = info . IsNullable ( ) ;
var isNullable = info . IsNullable ( ) ;
@ -276,13 +282,13 @@ public unsafe class IterActionGenerator
if ( info . Has < NotAttribute > ( ) ) {
if ( info . Has < NotAttribute > ( ) ) {
if ( isByRef | | isNullable ) throw new ArgumentException (
if ( isByRef | | isNullable ) throw new ArgumentException (
"Parameter with NotAttribute must not be ByRef or nullable\nParameter: " + info ) ;
"Parameter with NotAttribute must not be ByRef or nullable\nParameter: " + info ) ;
return new ( info , index , ParamKind . Not , info . ParameterType , info . ParameterType ) ;
return new ( info , ParamKind . Not , info . ParameterType , info . ParameterType ) ;
}
}
if ( info . Has < HasAttribute > ( ) | | info . ParameterType . Has < TagAttribute > ( ) ) {
if ( info . Has < HasAttribute > ( ) | | info . ParameterType . Has < TagAttribute > ( ) ) {
if ( isByRef | | isNullable ) throw new ArgumentException (
if ( isByRef | | isNullable ) throw new ArgumentException (
"Parameter with HasAttribute / TagAttribute must not be ByRef or nullable\nParameter: " + info ) ;
"Parameter with HasAttribute / TagAttribute must not be ByRef or nullable\nParameter: " + info ) ;
return new ( info , index , ParamKind . Has , info . ParameterType , info . ParameterType ) ;
return new ( info , ParamKind . Has , info . ParameterType , info . ParameterType ) ;
}
}
var kind = ParamKind . Normal ;
var kind = ParamKind . Normal ;
@ -308,15 +314,23 @@ public unsafe class IterActionGenerator
if ( underlyingType . IsPrimitive ) throw new ArgumentException (
if ( underlyingType . IsPrimitive ) throw new ArgumentException (
"Primitives are not supported\nParameter: " + info ) ;
"Primitives are not supported\nParameter: " + info ) ;
return new ( info , index , kind , info . ParameterType , underlyingType ) ;
return new ( info , kind , info . ParameterType , underlyingType ) ;
}
}
}
}
public enum ParamKind
public enum ParamKind
{
{
/// <summary> Parameter is not part of terms, handled uniquely, such as Universe. </summary>
/// <summary>
/// Not part of the resulting query's terms.
/// Same value across a single invocation of a callback.
/// For example <see cref="ECS.Universe"/> or <see cref="TimeSpan"/>.
/// </summary>
GlobalUnique ,
GlobalUnique ,
/// <summary> Parameter is unique per matched entity, such as EntityRef. </summary>
/// <summary>
/// Not part of the resulting query's terms.
/// Unique value for each iterated entity.
/// For example <see cref="EntityRef"/>.
/// </summary>
Unique ,
Unique ,
/// <summary> Passed by value. </summary>
/// <summary> Passed by value. </summary>
Normal ,
Normal ,
@ -332,9 +346,15 @@ public unsafe class IterActionGenerator
/// Automatically applied for types with <see cref="TagAttribute"/>.
/// Automatically applied for types with <see cref="TagAttribute"/>.
/// </summary>
/// </summary>
Has ,
Has ,
/// <summary> Struct passed as <c>Nullable<T></c >. </summary>
/// <summary> Struct or class passed as <see cref="T?"/ >. </summary>
Nullable ,
Nullable ,
/// <summary>
/// <summary>
/// Matches any terms in a chain of "or" terms.
/// Applied with <see cref="OrAttribute"/>.
/// Implies <see cref="Nullable"/>.
/// </summary>
Or ,
/// <summary>
/// Only checks for absence.
/// Only checks for absence.
/// Applied with <see cref="NotAttribute"/>.
/// Applied with <see cref="NotAttribute"/>.
/// </summary>
/// </summary>