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

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