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.

187 lines
5.9 KiB

2 years ago
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();
2 years ago
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)
2 years ago
=> _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));
2 years ago
public Entity Lookup<T>()
=> TryLookup<T>().ThrowIfNone();
public Entity Lookup(Type type)
=> TryLookup(type).ThrowIfNone();
2 years ago
public Entity Lookup(ecs_entity_t value)
=> TryLookup(value).ThrowIfNone();
public Entity Lookup(string path)
=> TryLookup(path).ThrowIfNone();
2 years ago
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();
}
2 years ago
}
}
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 },
});
2 years ago
_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();
2 years ago
}
public static implicit operator ecs_world_t*(Universe w) => w.Handle;
}