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.
185 lines
5.7 KiB
185 lines
5.7 KiB
2 years ago
|
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<Type, ecs_entity_t> _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<T>()
|
||
|
=> 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<ModuleAttribute>() == true;
|
||
|
if (type.Has<RelationAttribute>()) {
|
||
|
if (!isPartOfModule) RegisterRelation(type);
|
||
|
} else if (type.Has<ComponentAttribute>()) {
|
||
|
if (!isPartOfModule) RegisterComponent(type);
|
||
|
} else if (type.Has<TagAttribute>()) {
|
||
|
if (!isPartOfModule) RegisterTag(type);
|
||
|
} else if (type.Has<EntityAttribute>()) {
|
||
|
if (!isPartOfModule) RegisterEntity(type);
|
||
|
} else if (type.Has<ModuleAttribute>())
|
||
|
RegisterModule(type);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 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;
|
||
|
}
|