|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
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
|
|
|
|
{
|
|
|
|
// 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<Type, ecs_entity_t> _byType = new();
|
|
|
|
|
|
|
|
public ecs_world_t* Handle { get; }
|
|
|
|
|
|
|
|
public UniverseModules Modules { get; } = new();
|
|
|
|
public UniverseSystems Systems { get; } = new();
|
|
|
|
public UniverseObservers Observers { get; } = new();
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
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 TryLookup<T>()
|
|
|
|
=> TryLookup(typeof(T));
|
|
|
|
public Entity TryLookup(Type type)
|
|
|
|
=> _byType.TryGetValue(type, out var e) ? new(this, e) : default;
|
|
|
|
public Entity TryLookup(ecs_entity_t value)
|
|
|
|
=> new(this, ecs_get_alive(this, value));
|
|
|
|
public Entity TryLookup(string path)
|
|
|
|
=> new(this, ecs_lookup_path_w_sep(this, default, path, ".", default, true));
|
|
|
|
|
|
|
|
public Entity Lookup<T>()
|
|
|
|
=> TryLookup<T>().ThrowIfNone();
|
|
|
|
public Entity Lookup(Type type)
|
|
|
|
=> TryLookup(type).ThrowIfNone();
|
|
|
|
public Entity Lookup(ecs_entity_t value)
|
|
|
|
=> TryLookup(value).ThrowIfNone();
|
|
|
|
public Entity Lookup(string path)
|
|
|
|
=> TryLookup(path).ThrowIfNone();
|
|
|
|
|
|
|
|
|
|
|
|
public void RegisterAll(Assembly? from = null)
|
|
|
|
{
|
|
|
|
from ??= Assembly.GetEntryAssembly()!;
|
|
|
|
foreach (var type in from.GetTypes()) {
|
|
|
|
var info = type.GetRegisterableInfo(out var isPartOfModule);
|
|
|
|
if (info == null || isPartOfModule) continue;
|
|
|
|
switch (info.Kind) {
|
|
|
|
case RegisterableKind.Entity: RegisterEntity(type); break;
|
|
|
|
case RegisterableKind.Tag: RegisterTag(type); break;
|
|
|
|
case RegisterableKind.Component: RegisterComponent(type); break;
|
|
|
|
case RegisterableKind.Relation: RegisterRelation(type); break;
|
|
|
|
case RegisterableKind.Module: RegisterModule(type); break;
|
|
|
|
default: throw new InvalidOperationException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public Entity RegisterRelation<T>()
|
|
|
|
=> RegisterRelation(typeof(T));
|
|
|
|
public Entity RegisterRelation(Type type)
|
|
|
|
=> throw new NotImplementedException();
|
|
|
|
|
|
|
|
public Entity RegisterComponent<T>()
|
|
|
|
=> 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<EntityAttribute>()) {
|
|
|
|
if (type.IsValueType) entity.Add(entity);
|
|
|
|
else entity.Set(type, Activator.CreateInstance(type)!);
|
|
|
|
}
|
|
|
|
|
|
|
|
return entity;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Entity RegisterTag<T>()
|
|
|
|
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<T>()
|
|
|
|
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 id = type.Get<EntityAttribute>()?.ID ?? 0;
|
|
|
|
var entity = Create(new ecs_entity_desc_t {
|
|
|
|
name = type.GetFriendlyName(),
|
|
|
|
id = new() { Data = id },
|
|
|
|
});
|
|
|
|
_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);
|
|
|
|
return new Entity(this, entity).ThrowIfNone();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static implicit operator ecs_world_t*(Universe w) => w.Handle;
|
|
|
|
}
|