You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
124 lines
4.3 KiB
124 lines
4.3 KiB
using System; |
|
using System.Linq; |
|
using System.Reflection; |
|
using System.Runtime.InteropServices; |
|
using gaemstone.Flecs; |
|
using gaemstone.Utility; |
|
using gaemstone.Utility.IL; |
|
using static flecs_hub.flecs; |
|
using static gaemstone.Flecs.Core; |
|
|
|
namespace gaemstone.ECS; |
|
|
|
[AttributeUsage(AttributeTargets.Method)] |
|
public class SystemAttribute : Attribute |
|
{ |
|
public Type Phase { get; } |
|
public SystemAttribute() : this(typeof(SystemPhase.OnUpdate)) { } |
|
internal SystemAttribute(Type phase) => Phase = phase; // Use generic type instead. |
|
} |
|
public class SystemAttribute<TPhase> : SystemAttribute |
|
{ public SystemAttribute() : base(typeof(TPhase)) { } } |
|
|
|
[AttributeUsage(AttributeTargets.Method)] |
|
public class ExpressionAttribute : Attribute |
|
{ |
|
public string Value { get; } |
|
public ExpressionAttribute(string value) => Value = value; |
|
} |
|
|
|
public static class SystemExtensions |
|
{ |
|
private static unsafe EntityRef RegisterSystem(this Universe universe, |
|
QueryDesc query, Entity phase, CallbackContext callback) |
|
{ |
|
using var alloc = TempAllocator.Use(); |
|
var desc = new ecs_system_desc_t { |
|
query = query.ToFlecs(alloc), |
|
entity = universe.New((query.Name != null) ? new(query.Name) : null) |
|
.Add<DependsOn>(phase).Add(phase).Build(), |
|
binding_ctx = (void*)CallbackContextHelper.Create(callback), |
|
run = new() { Data = new() { Pointer = &Run } }, |
|
}; |
|
return new(universe, new(ecs_system_init(universe, &desc))); |
|
} |
|
|
|
public static EntityRef RegisterSystem(this Universe universe, Delegate action) |
|
{ |
|
var attr = action.Method.Get<SystemAttribute>(); |
|
var expr = action.Method.Get<ExpressionAttribute>()?.Value; |
|
|
|
QueryDesc query; |
|
if (action is Action<Iterator> callback) { |
|
query = new(expr ?? throw new ArgumentException( |
|
"System must specify ExpressionAttribute", nameof(action))); |
|
} else { |
|
var gen = IterActionGenerator.GetOrBuild(universe, action.Method); |
|
query = (expr != null) ? new(expr) : new(gen.Terms.ToArray()); |
|
callback = iter => gen.RunWithTryCatch(action.Target, iter); |
|
} |
|
|
|
query.Name = action.Method.Name; |
|
var phase = universe.LookupOrThrow(attr?.Phase ?? typeof(SystemPhase.OnUpdate)); |
|
return universe.RegisterSystem(query, phase, new(universe, action.Method, callback)); |
|
} |
|
|
|
public static EntityRef RegisterSystem(this Universe universe, |
|
object? instance, MethodInfo method) |
|
{ |
|
var attr = method.Get<SystemAttribute>(); |
|
var expr = method.Get<ExpressionAttribute>()?.Value; |
|
|
|
QueryDesc query; |
|
Action<Iterator> callback; |
|
var param = method.GetParameters(); |
|
if (param.Length == 1 && param[0].ParameterType == typeof(Iterator)) { |
|
query = new(expr ?? throw new ArgumentException( |
|
"System must specify ExpressionAttribute", nameof(method))); |
|
callback = (Action<Iterator>)Delegate.CreateDelegate(typeof(Action<Iterator>), instance, method); |
|
} else { |
|
var gen = IterActionGenerator.GetOrBuild(universe, method); |
|
query = (expr != null) ? new(expr) : new(gen.Terms.ToArray()); |
|
callback = iter => gen.RunWithTryCatch(instance, iter); |
|
} |
|
|
|
query.Name = method.Name; |
|
var phase = universe.LookupOrThrow(attr?.Phase ?? typeof(SystemPhase.OnUpdate)); |
|
return universe.RegisterSystem(query, phase, new(universe, method, callback)); |
|
} |
|
|
|
private class CallbackContext |
|
{ |
|
public Universe Universe { get; } |
|
public MethodInfo Method { get; } |
|
public Action<Iterator> Callback { get; } |
|
|
|
public CallbackContext(Universe universe, MethodInfo method, Action<Iterator> callback) |
|
{ Universe = universe; Method = method; Callback = callback; } |
|
|
|
public void Prepare(Iterator iter) |
|
{ |
|
// If the method is marked with [Source], set the $This variable. |
|
if (Method.Get<SourceAttribute>()?.Type is Type sourceType) |
|
iter.SetThis(Universe.LookupOrThrow(sourceType)); |
|
} |
|
} |
|
|
|
[UnmanagedCallersOnly] |
|
private static unsafe void Run(ecs_iter_t* flecsIter) |
|
{ |
|
var callback = CallbackContextHelper.Get<CallbackContext>((nint)flecsIter->binding_ctx); |
|
|
|
// This is what flecs does, so I guess we'll do it too! |
|
var type = (&flecsIter->next == (delegate*<ecs_iter_t*, Runtime.CBool>)&ecs_query_next) |
|
? IteratorType.Query : (IteratorType?)null; |
|
using var iter = new Iterator(callback.Universe, type, *flecsIter); |
|
|
|
callback.Prepare(iter); |
|
|
|
if (flecsIter->field_count == 0) |
|
callback.Callback(iter); |
|
else while (iter.Next()) |
|
callback.Callback(iter); |
|
} |
|
}
|
|
|