using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using gaemstone.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; [Entity] public struct Game { } [Component] public unsafe partial class Universe { // Roles public static ecs_id_t ECS_PAIR { get; } = pinvoke_ECS_PAIR(); public static ecs_id_t ECS_OVERRIDE { get; } = pinvoke_ECS_OVERRIDE(); // Relationships public static ecs_entity_t EcsIsA { get; } = pinvoke_EcsIsA(); public static ecs_entity_t EcsDependsOn { get; } = pinvoke_EcsDependsOn(); public static ecs_entity_t EcsChildOf { get; } = pinvoke_EcsChildOf(); public static ecs_entity_t EcsSlotOf { get; } = pinvoke_EcsSlotOf(); // Entity Tags public static ecs_entity_t EcsPrefab { get; } = pinvoke_EcsPrefab(); private readonly Dictionary _byType = new(); public ecs_world_t* Handle { get; } public UniverseSystems Systems { get; } public UniverseModules Modules { get; } public Universe(string[]? args = null) { [UnmanagedCallersOnly] static void Abort() => throw new FlecsAbortException(); ecs_os_set_api_defaults(); var api = ecs_os_get_api(); api.abort_ = new FnPtr_Void { Pointer = &Abort }; ecs_os_set_api(&api); if (args?.Length > 0) { var argv = Runtime.CStrings.CStringArray(args); Handle = ecs_init_w_args(args.Length, argv); Runtime.CStrings.FreeCStrings(argv, args.Length); } else { Handle = ecs_init(); } Systems = new(this); Modules = new(this); RegisterAll(typeof(Universe).Assembly); } public bool Progress(TimeSpan delta) { if (Modules._deferred.Count > 0) throw new Exception( "Modules with unmet dependencies: \n" + string.Join(" \n", Modules._deferred.Values.Select( m => m.Type + " is missing " + string.Join(", ", m.UnmetDependencies)))); return ecs_progress(this, (float)delta.TotalSeconds); } public Entity Lookup() => Lookup(typeof(T)); public Entity Lookup(Type type) => _byType.TryGetValue(type, out var e) ? new(this, e) : default; public Entity Lookup(string path) => new(this, !path.Contains('.') ? ecs_lookup(this, path) : ecs_lookup_path_w_sep(this, default, path, ".", default, true)); public Entity Lookup(ecs_entity_t value) => new(this, ecs_get_alive(this, value)); public void RegisterAll(Assembly? from = null) { from ??= Assembly.GetEntryAssembly()!; foreach (var type in from.GetTypes()) { var isPartOfModule = type.DeclaringType?.Has() == true; if (type.Has()) { if (!isPartOfModule) RegisterRelation(type); } else if (type.Has()) { if (!isPartOfModule) RegisterComponent(type); } else if (type.Has()) { if (!isPartOfModule) RegisterTag(type); } else if (type.Has()) { if (!isPartOfModule) RegisterEntity(type); } else if (type.Has()) RegisterModule(type); } } public Entity RegisterRelation() => RegisterRelation(typeof(T)); public Entity RegisterRelation(Type type) => throw new NotImplementedException(); public Entity RegisterComponent() => RegisterComponent(typeof(T)); public Entity RegisterComponent(Type type) { var typeInfo = default(ecs_type_info_t); if (type.IsValueType) { var wrapper = TypeWrapper.For(type); if (!wrapper.IsUnmanaged) throw new Exception( "Component struct must satisfy the unmanaged constraint. " + "(Must not contain any reference types or structs that contain references.)"); var structLayout = type.StructLayoutAttribute; if (structLayout == null || structLayout.Value == LayoutKind.Auto) throw new Exception( "Component struct must have a StructLayout attribute with LayoutKind sequential or explicit. " + "This is to ensure that the struct fields are not reorganized by the C# compiler."); typeInfo.size = wrapper.Size; typeInfo.alignment = structLayout.Pack; } else { typeInfo.size = sizeof(nint); typeInfo.alignment = sizeof(nint); } var name = type.GetFriendlyName(); var entityDesc = new ecs_entity_desc_t { name = name, symbol = name }; var componentDesc = new ecs_component_desc_t { entity = Create(entityDesc), type = typeInfo }; var id = ecs_component_init(Handle, &componentDesc); _byType[type] = id; // TODO: SetHooks(hooks, id); var entity = new Entity(this, id); if (type.Has()) { if (type.IsValueType) entity.Add(entity); else entity.Set(type, Activator.CreateInstance(type)!); } return entity; } public Entity RegisterTag() where T : unmanaged => RegisterTag(typeof(T)); public Entity RegisterTag(Type type) { if (!type.IsValueType || type.IsPrimitive || type.GetFields().Length > 0) throw new Exception("Tag must be an empty, used-defined struct."); var entity = Create(type.GetFriendlyName()); _byType.Add(type, entity); return entity; } public Entity RegisterEntity() where T : unmanaged => RegisterEntity(typeof(T)); public Entity RegisterEntity(Type type) { if (!type.IsValueType || type.IsPrimitive || type.GetFields().Length > 0) throw new Exception("Entity must be an empty, used-defined struct."); var entity = Create(type.GetFriendlyName()); _byType.Add(type, entity); return entity; } public Entity Create() => Create(new ecs_entity_desc_t()); public Entity Create(string name) => Create(new ecs_entity_desc_t { name = name }); public Entity Create(ecs_entity_desc_t desc) { var entity = ecs_entity_init(Handle, &desc); Debug.Assert(entity.Data != 0, "ECS_INVALID_PARAMETER"); return new(this, entity); } public static implicit operator ecs_world_t*(Universe w) => w.Handle; }