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 System; |
||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
namespace gaemstone.ECS; |
namespace gaemstone.ECS; |
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Method)] |
public enum ObserverEvent |
||||||
public class ObserverAttribute : Attribute |
|
||||||
{ |
{ |
||||||
public Event Event { get; } |
OnAdd = ECS_HI_COMPONENT_ID + 33, |
||||||
public ObserverAttribute(Event @event) |
OnRemove = ECS_HI_COMPONENT_ID + 34, |
||||||
=> Event = @event; |
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, |
public ObserverEvent Event { get; } |
||||||
OnSet, |
public string? Expression { get; } |
||||||
OnRemove, |
|
||||||
|
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