parent
36102b3536
commit
12f3ff451c
13 changed files with 300 additions and 113 deletions
@ -0,0 +1,14 @@ |
||||
using System; |
||||
using gaemstone.Bloxel; |
||||
using gaemstone.Client; |
||||
using gaemstone.ECS; |
||||
|
||||
namespace Immersion; |
||||
|
||||
[Module] |
||||
public class ObserverModule |
||||
{ |
||||
[Observer(ObserverEvent.OnSet)] |
||||
public static void DoObserver(in Chunk chunk, in Mesh _) |
||||
=> Console.WriteLine($"Chunk at {chunk.Position} now has a Mesh!"); |
||||
} |
@ -1,18 +1,32 @@ |
||||
using System; |
||||
using static flecs_hub.flecs; |
||||
|
||||
namespace gaemstone.ECS; |
||||
|
||||
[AttributeUsage(AttributeTargets.Method)] |
||||
public class ObserverAttribute : Attribute |
||||
public enum ObserverEvent |
||||
{ |
||||
public Event Event { get; } |
||||
public ObserverAttribute(Event @event) |
||||
=> Event = @event; |
||||
OnAdd = ECS_HI_COMPONENT_ID + 33, |
||||
OnRemove = ECS_HI_COMPONENT_ID + 34, |
||||
OnSet = ECS_HI_COMPONENT_ID + 35, |
||||
UnSet = ECS_HI_COMPONENT_ID + 36, |
||||
OnDelete = ECS_HI_COMPONENT_ID + 37, |
||||
OnCreateTable = ECS_HI_COMPONENT_ID + 38, |
||||
OnDeleteTable = ECS_HI_COMPONENT_ID + 39, |
||||
OnTableEmpty = ECS_HI_COMPONENT_ID + 40, |
||||
OnTableFill = ECS_HI_COMPONENT_ID + 41, |
||||
OnCreateTrigger = ECS_HI_COMPONENT_ID + 42, |
||||
OnDeleteTrigger = ECS_HI_COMPONENT_ID + 43, |
||||
OnDeleteObservable = ECS_HI_COMPONENT_ID + 44, |
||||
OnComponentHooks = ECS_HI_COMPONENT_ID + 45, |
||||
OnDeleteTarget = ECS_HI_COMPONENT_ID + 46, |
||||
} |
||||
|
||||
public enum Event |
||||
[AttributeUsage(AttributeTargets.Method)] |
||||
public class ObserverAttribute : Attribute |
||||
{ |
||||
OnAdd, |
||||
OnSet, |
||||
OnRemove, |
||||
public ObserverEvent Event { get; } |
||||
public string? Expression { get; } |
||||
|
||||
public ObserverAttribute(ObserverEvent @event) |
||||
=> Event = @event; |
||||
} |
||||
|
@ -0,0 +1,65 @@ |
||||
using System; |
||||
using System.Linq; |
||||
using System.Reflection; |
||||
using gaemstone.Utility; |
||||
|
||||
namespace gaemstone.ECS; |
||||
|
||||
public enum RegisterableKind |
||||
{ |
||||
Entity, |
||||
Tag, |
||||
Component, |
||||
Relation, |
||||
System, |
||||
Observer, |
||||
Module, |
||||
} |
||||
|
||||
public class RegisterableInfo |
||||
{ |
||||
public Type Type { get; } |
||||
public RegisterableKind Kind { get; } |
||||
public bool? PartOfModule { get; } |
||||
internal Type[] AllowedWith { get; } |
||||
|
||||
internal RegisterableInfo(Type type, RegisterableKind kind, bool? partOfModule, Type[]? allowedWith = null) |
||||
{ Type = type; Kind = kind; PartOfModule = partOfModule; AllowedWith = allowedWith ?? Array.Empty<Type>(); } |
||||
} |
||||
|
||||
public static class RegisterableExtensions |
||||
{ |
||||
|
||||
// These are ordered by priority. For example a type marked with [Component, Relation] |
||||
// will result in RegisterableKind.Relation due to being first in the list. |
||||
private static readonly RegisterableInfo[] _knownAttributes = new RegisterableInfo[] { |
||||
new(typeof(RelationAttribute) , RegisterableKind.Relation , null, new[] { typeof(ComponentAttribute), typeof(TagAttribute) }), |
||||
new(typeof(ComponentAttribute) , RegisterableKind.Component , null, new[] { typeof(EntityAttribute) }), |
||||
new(typeof(TagAttribute) , RegisterableKind.Tag , null), |
||||
new(typeof(EntityAttribute) , RegisterableKind.Entity , null), |
||||
|
||||
new(typeof(ModuleAttribute) , RegisterableKind.Module , false), |
||||
new(typeof(SystemAttribute) , RegisterableKind.System , true), |
||||
new(typeof(ObserverAttribute) , RegisterableKind.Observer , true), |
||||
}; |
||||
|
||||
public static RegisterableInfo? GetRegisterableInfo(this MemberInfo member, out bool isPartOfModule) |
||||
{ |
||||
isPartOfModule = member.DeclaringType?.Has<ModuleAttribute>() == true; |
||||
var matched = _knownAttributes.Where(a => member.GetCustomAttribute(a.Type) != null).ToList(); |
||||
if (matched.Count == 0) return null; |
||||
|
||||
var attr = matched[0]; |
||||
|
||||
var disallowed = matched.Except(new[] { attr }).Select(a => a.Type).Except(attr.AllowedWith); |
||||
if (disallowed.Any()) throw new InvalidOperationException( |
||||
$"{member} marked with {attr.Type} may not be used together with " + string.Join(", ", disallowed)); |
||||
|
||||
if (attr.PartOfModule == true && !isPartOfModule) throw new InvalidOperationException( |
||||
$"{member} marked with {attr.Type} must be part of a module"); |
||||
if (attr.PartOfModule == false && isPartOfModule) throw new InvalidOperationException( |
||||
$"{member} marked with {attr.Type} must not be part of a module"); |
||||
|
||||
return attr; |
||||
} |
||||
} |
@ -0,0 +1,93 @@ |
||||
using System; |
||||
using System.Collections; |
||||
using System.Collections.Generic; |
||||
using System.Reflection; |
||||
using gaemstone.Utility; |
||||
using gaemstone.Utility.IL; |
||||
using static flecs_hub.flecs; |
||||
|
||||
namespace gaemstone.ECS; |
||||
|
||||
public unsafe partial class Universe |
||||
{ |
||||
public ObserverInfo RegisterObserver(Action<Iterator> callback, string expression, |
||||
ObserverEvent @event, string? name = null) |
||||
=> RegisterObserver(name ?? callback.Method.Name, expression, @event, new() { expr = expression }, callback); |
||||
public ObserverInfo RegisterObserver(Action<Iterator> callback, ecs_filter_desc_t filter, |
||||
ObserverEvent @event, string? name = null) |
||||
=> RegisterObserver(name ?? callback.Method.Name, null, @event, filter, callback); |
||||
|
||||
public ObserverInfo RegisterObserver(string name, string? expression, |
||||
ObserverEvent @event, ecs_filter_desc_t filter, Action<Iterator> callback) |
||||
{ |
||||
var observerDesc = default(ecs_observer_desc_t); |
||||
observerDesc.filter = filter; |
||||
observerDesc.events[0] = (ecs_entity_t)(ecs_id_t)(uint)@event; |
||||
observerDesc.binding_ctx = (void*)UniverseSystems.CreateSystemCallbackContext(this, callback); |
||||
observerDesc.callback.Data.Pointer = &UniverseSystems.SystemCallback; |
||||
observerDesc.entity = Create(name); |
||||
|
||||
var entity = new Entity(this, ecs_observer_init(Handle, &observerDesc)); |
||||
var observer = new ObserverInfo(this, entity, name, expression, @event, filter, callback); |
||||
Observers._observers.Add(observer); |
||||
return observer; |
||||
} |
||||
|
||||
public ObserverInfo RegisterObserver(object? instance, MethodInfo method) |
||||
{ |
||||
var attr = method.Get<ObserverAttribute>() ?? throw new ArgumentException( |
||||
"Observer must specify ObserverAttribute", nameof(method)); |
||||
|
||||
var param = method.GetParameters(); |
||||
if (param.Length == 1 && param[0].ParameterType == typeof(Iterator)) { |
||||
if (attr.Expression == null) throw new Exception( |
||||
"Observer must specify expression in ObserverAttribute"); |
||||
var action = (Action<Iterator>)Delegate.CreateDelegate(typeof(Action<Iterator>), instance, method); |
||||
return RegisterObserver(method.Name, attr.Expression, attr.Event, |
||||
new() { expr = attr.Expression }, action); |
||||
} else { |
||||
var gen = QueryActionGenerator.GetOrBuild(this, method); |
||||
var filter = (attr.Expression == null) ? gen.Filter : new() { expr = attr.Expression }; |
||||
return RegisterObserver(method.Name, attr.Expression, attr.Event, |
||||
filter, iter => gen.RunWithTryCatch(instance, iter)); |
||||
} |
||||
} |
||||
|
||||
public class UniverseObservers |
||||
: IReadOnlyCollection<ObserverInfo> |
||||
{ |
||||
internal readonly List<ObserverInfo> _observers = new(); |
||||
|
||||
// IReadOnlyCollection implementation |
||||
public int Count => _observers.Count; |
||||
public IEnumerator<ObserverInfo> GetEnumerator() => _observers.GetEnumerator(); |
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
||||
} |
||||
|
||||
public class ObserverInfo |
||||
{ |
||||
public Universe Universe { get; } |
||||
public Entity Entity { get; } |
||||
|
||||
public string Name { get; } |
||||
public string? Expression { get; } |
||||
|
||||
public ObserverEvent Event { get; } |
||||
public ecs_filter_desc_t Filter { get; } |
||||
public Action<Iterator> Callback { get; } |
||||
|
||||
internal ObserverInfo(Universe universe, Entity entity, string name, string? expression, |
||||
ObserverEvent @event, ecs_filter_desc_t filter, Action<Iterator> callback) |
||||
{ |
||||
Universe = universe; |
||||
Entity = entity; |
||||
|
||||
Name = name; |
||||
Expression = expression; |
||||
|
||||
Event = @event; |
||||
Filter = filter; |
||||
Callback = callback; |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue