Compare commits

...

4 Commits

Author SHA1 Message Date
copygirl 6a38f97830 Update to newer gaemstone.ECS and Flecs 1 year ago
copygirl c97c4a530f Improve built-in entities 1 year ago
copygirl 03b7e5ce42 Add [Set] attribute to set entity's component 1 year ago
copygirl fe54cc3637 Fix IModuleInitializer.Initialize not being called 1 year ago
  1. 6
      src/Immersion/Program.cs
  2. 39
      src/gaemstone.Client/Systems/InputManager.cs
  3. 2
      src/gaemstone.ECS
  4. 12
      src/gaemstone.SourceGen/Descriptors.cs
  5. 23
      src/gaemstone.SourceGen/ModuleGenerator.cs
  6. 3
      src/gaemstone.SourceGen/RelevantSymbolReceiver.cs
  7. 61
      src/gaemstone.SourceGen/Structure/BaseEntityInfo.cs
  8. 5
      src/gaemstone.SourceGen/Structure/MethodEntityInfo.cs
  9. 8
      src/gaemstone.SourceGen/Structure/ModuleEntityInfo.cs
  10. 22
      src/gaemstone.SourceGen/Structure/TypeEntityInfo.cs
  11. 16
      src/gaemstone/ECS/Module+Attributes.cs
  12. 47
      src/gaemstone/Flecs/Core.cs
  13. 22
      src/gaemstone/Universe+Modules.cs

@ -23,8 +23,8 @@ var universe = new Universe<Program>();
var world = universe.World;
// TODO: Figure out a nice way to get rid of "compile errors" here.
// FIXME: universe.Modules.Register<gaemstone.Flecs.Systems.Monitor>();
universe.Modules.Register<gaemstone.Flecs.Systems.Rest>();
universe.Modules.Register<gaemstone.Flecs.Systems.Monitor>();
var window = Window.Create(WindowOptions.Default with {
Title = "gæmstone",
@ -34,8 +34,8 @@ var window = Window.Create(WindowOptions.Default with {
window.Initialize();
window.Center();
// universe.Modules.Register<ObserverTest>();
// universe.Modules.Register<ManagedComponentTest>();
// universe.Modules.Register<Immersion.ObserverTest>();
// universe.Modules.Register<Immersion.ManagedComponentTest>();
universe.Modules.Register<gaemstone.Client.Systems.Windowing>();
universe.Modules.Register<gaemstone.Components.TransformComponents>();

@ -109,11 +109,18 @@ public partial class InputManager
{
entity.GetMut<RawValue1D>() = current;
if (current >= ActivationThreshold) {
ref var active = ref entity.GetRefOrNull<Active>();
if (Unsafe.IsNullRef(ref active)) {
// TODO: Find out why using GetRefOrNull here crashes.
if (entity.Has<Active>()) {
entity.GetMut<Active>().Duration += delta;
} else {
entity.Set(new Active());
entity.Add<Activated>();
} else active.Duration += delta;
}
// ref var active = ref entity.GetRefOrNull<Active>();
// if (Unsafe.IsNullRef(ref active)) {
// entity.Set(new Active());
// entity.Add<Activated>();
// } else active.Duration += delta;
} else if (current <= DeactivationThreshold)
entity.Remove<Active>();
}
@ -123,11 +130,17 @@ public partial class InputManager
entity.GetMut<RawValue2D>() = current;
var magnitude = current.Length();
if (magnitude >= ActivationThreshold) {
ref var active = ref entity.GetRefOrNull<Active>();
if (Unsafe.IsNullRef(ref active)) {
if (entity.Has<Active>()) {
entity.GetMut<Active>().Duration += delta;
} else {
entity.Set(new Active());
entity.Add<Activated>();
} else active.Duration += delta;
}
// ref var active = ref entity.GetRefOrNull<Active>();
// if (Unsafe.IsNullRef(ref active)) {
// entity.Set(new Active());
// entity.Add<Activated>();
// } else active.Duration += delta;
} else if (magnitude <= DeactivationThreshold)
entity.Remove<Active>();
}
@ -138,12 +151,12 @@ public partial class InputManager
// public static void OnActiveAdded(EntityRef entity, Active _)
// => entity.Add<Activated>();
[Observer<Core.OnRemove>]
public static void OnActiveRemoved<T>(Entity<T> entity, Active _)
=> entity.Add<Deactivated>();
// [Observer<Core.OnRemove>]
// public static void OnActiveRemoved<T>(Entity<T> entity, Active _)
// => entity.Add<Deactivated>();
[System]
[DependsOn<Pipeline.PostFrame>]
public static void ClearDeActivated<T>(Entity<T> entity, Has<Or<Activated, Deactivated>> _)
=> entity.Remove<Activated>().Remove<Deactivated>();
// [System]
// [DependsOn<Pipeline.PostFrame>]
// public static void ClearDeActivated<T>(Entity<T> entity, Has<Or<Activated, Deactivated>> _)
// => entity.Remove<Activated>().Remove<Deactivated>();
}

@ -1 +1 @@
Subproject commit 9eade0adb1200223dffcb2077ecbc992569f4080
Subproject commit eec968d6361930eebad180f6300d126c4bac70f3

@ -36,6 +36,12 @@ public static class Descriptors
// Diagnostics relating to the combined usage of attributes.
public static readonly DiagnosticDescriptor AttributesRequireTypeOrPath = new(
$"gSG{_idCounter++:00}", "Entity attributes require type or path",
"Entity attributes require a type (such as [Entity]) or [Path].",
DiagnosticCategory, DiagnosticSeverity.Error, true);
public static readonly DiagnosticDescriptor InvalidAttributeCombination = new(
$"gSG{_idCounter++:00}", "Invalid combination of entity attributes",
"The combination of entity attributes {0} is not valid.",
@ -61,7 +67,10 @@ public static class Descriptors
"A [BuiltIn, Module] must also have a [Path] set.",
DiagnosticCategory, DiagnosticSeverity.Info, true);
public static readonly DiagnosticDescriptor BuiltInModuleMustNotHaveMethods = new(
$"gSG{_idCounter++:00}", "BuiltIn Module must not have System / Observer",
"A [BuiltIn, Module] must not have [System] or [Observer] methods.",
DiagnosticCategory, DiagnosticSeverity.Info, true);
// Diagnostics relating to keywords / modifiers used with the symbol.
@ -182,6 +191,7 @@ public static class Descriptors
DiagnosticCategory, DiagnosticSeverity.Error, true);
// TODO: Make this more descriptive. (Special = Has<>, Not<> and [Tag]?)
public static readonly DiagnosticDescriptor SpecialMustNotBeByRef = new(
$"gSG{_idCounter++:00}", "Special parameter must not be ByRef",
"Special parameter must not be ByRef (in/out/ref).",

@ -5,6 +5,7 @@ using System.Text;
using gaemstone.SourceGen.Structure;
using gaemstone.SourceGen.Utility;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace gaemstone.SourceGen;
@ -106,6 +107,7 @@ public class ModuleGenerator
""");
// TODO: Built-in modules should not have dependencies.
var dependencies = module.GetDependencies().ToList();
sb.Append("\tstatic IReadOnlyList<string> IModule.Dependencies { get; } = ");
if (dependencies.Count > 0) {
@ -132,6 +134,9 @@ public class ModuleGenerator
// TODO: Can BuiltIn modules have systems and such?
if (module.HasInitializer)
sb.AppendLine("\t\tInitialize(module);");
sb.AppendLine("\t}");
sb.AppendLine("}");
@ -151,26 +156,30 @@ public class ModuleGenerator
foreach (var e in entities) {
var @var = $"_{e.Name}_Entity";
var path = (e.EntityPath ?? e.Name).ToStringLiteral();
sb.AppendLine($"\t\tvar {@var} = world.New({path}, module)");
if (e.EntitySymbol != null)
sb.AppendLine($"\t\t\t.Symbol({e.EntitySymbol.ToStringLiteral()})");
// When looking for a BuiltIn entity, error if it doesn't exist.
var lookupFunc = (e.IsBuiltIn ? "LookupPathOrThrow" : "New");
sb.AppendLine($"\t\tvar {@var} = world.{lookupFunc}({path}, module)");
// Since this is a custom gaemstone tag, we want to add it even for [BuiltIn] modules.
if (e.IsRelation) sb.AppendLine("\t\t\t.Add<gaemstone.Doc.Relation>()");
// Other than [Relation], we don't add anything to entities defined in [BuiltIn] modules.
if (module.IsBuiltIn)
{
sb.AppendLine($"\t\t\t.Build().CreateLookup<{e.FullName}>()");
sb.AppendLine($"\t\t\t.CreateLookup<{e.FullName}>()");
}
else
{
// TODO: if (e.IsPublic) sb.AppendLine($"\t\t\t.Symbol(\"{e.Name}\")");
if (e.EntitySymbol != null)
sb.AppendLine($"\t\t\t.Symbol({e.EntitySymbol.ToStringLiteral()})");
// Tags and relations in Flecs are marked as empty components.
if (e.IsTag || e.IsRelation) sb.AppendLine("\t\t\t.Add<gaemstone.Flecs.Core.Component>()");
if (e.IsTag && e.IsRelation) sb.AppendLine("\t\t\t.Add<gaemstone.Flecs.Core.Tag>()");
sb.Append( "\t\t\t.Build()");
sb.Append( "\t\t\t");
if (!e.IsBuiltIn) sb.Append(".Build()");
if (e.IsComponent) sb.Append($".InitComponent<{e.FullName}>()");
else sb.Append($".CreateLookup<{e.FullName}>()");
sb.AppendLine();
@ -289,6 +298,8 @@ public class ModuleGenerator
sb.AppendLine($"\t\t{@var}.Add<{a.GetFullName()}>();");
foreach (var (r, t) in e.RelationsToAdd)
sb.AppendLine($"\t\t{@var}.Add<{r.GetFullName()}, {t.GetFullName()}>();");
foreach (var (c, args) in e.ComponentsToAdd)
sb.AppendLine($"\t\t{@var}.Set(new {c.GetFullName()}({string.Join(", ", args.Select(a => a.ToCSharpString()))}));");
// If system doesn't have an explicit phase set, default to OnUpdate.
if (e is MethodEntityInfo { IsSystem: true, HasPhaseSet: false })

@ -24,9 +24,10 @@ public class RelevantSymbolReceiver
"Observer",
// Entity properties that specify additional info / behavior
"Path",
"Path", // TODO: When referring to a pre-existing entity, only [Path] should be necessary, right?
"Symbol",
"Add",
"Set",
"BuiltIn", // Valid on [Module]
"Expression", // Valid on [System] and [Observer]

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
@ -14,7 +15,11 @@ public abstract class BaseEntityInfo : BaseInfo
public List<INamedTypeSymbol> EntitiesToAdd { get; } = new();
public List<(INamedTypeSymbol Relation, INamedTypeSymbol Target)> RelationsToAdd { get; } = new();
public virtual bool HasEntitiesToAdd => (EntitiesToAdd.Count > 0) || (RelationsToAdd.Count > 0);
public List<(INamedTypeSymbol Component, ImmutableArray<TypedConstant> Arguments)> ComponentsToAdd { get; } = new();
public virtual bool HasEntitiesToAdd => (EntitiesToAdd.Count > 0)
|| (RelationsToAdd.Count > 0)
|| (ComponentsToAdd.Count > 0);
public BaseEntityInfo(ISymbol symbol)
: base(symbol)
@ -41,33 +46,49 @@ public abstract class BaseEntityInfo : BaseInfo
Descriptors.EntityMustBeInModule, Location);
}
// Add entities and relationships specified using [Add<...>] attributes.
foreach (var attr in Symbol.GetAttributes()) {
// Add entities and relationships specified using [Add<...>] attributes.
for (var attrType = attr.AttributeClass; attrType != null; attrType = attrType.BaseType) {
if (RelevantSymbolReceiver.ToRelevantAttributeName(attrType) != "Add") continue;
var attrName = RelevantSymbolReceiver.ToRelevantAttributeName(attrType);
if (attrName is "Add" or "Set") {
var allTypeArgumentsValid = true;
for (var i = 0; i < attrType.TypeArguments.Length; i++) {
var arg = attrType.TypeArguments[i];
var param = attrType.TypeParameters[i];
if (arg is not INamedTypeSymbol) {
yield return Diagnostic.Create(
Descriptors.InvalidTypeArgument, param.Locations.Single());
allTypeArgumentsValid = false;
var allTypeArgumentsValid = true;
for (var i = 0; i < attrType.TypeArguments.Length; i++) {
var arg = attrType.TypeArguments[i];
var param = attrType.TypeParameters[i];
if (arg is not INamedTypeSymbol) {
yield return Diagnostic.Create(
Descriptors.InvalidTypeArgument, param.Locations.Single());
allTypeArgumentsValid = false;
}
// TODO: Make sure entities being added have appropriate attributes as well.
}
// TODO: Make sure entities being added have appropriate attributes as well.
}
if (!allTypeArgumentsValid) continue;
if (allTypeArgumentsValid) {
switch (attrType.TypeArguments) {
case [ INamedTypeSymbol entity ]:
EntitiesToAdd.Add(entity);
switch (attrName) {
case "Add":
switch (attrType.TypeArguments) {
case [ INamedTypeSymbol entity ]:
EntitiesToAdd.Add(entity);
break;
case [ INamedTypeSymbol relation, INamedTypeSymbol target ]:
RelationsToAdd.Add((relation, target));
break;
default: throw new InvalidOperationException(
"Type argument pattern matching failed");
}
break;
case [ INamedTypeSymbol relation, INamedTypeSymbol target ]:
RelationsToAdd.Add((relation, target));
case "Set":
var component = (INamedTypeSymbol)attrType.TypeArguments.Single();
var arguments = attr.ConstructorArguments.Single().Values;
// TODO: Verify arguments actually match a constructor.
// Right now this will just error in the generated code. Probably good enough?
ComponentsToAdd.Add((component, arguments));
break;
default: throw new InvalidOperationException(
"Type argument pattern matching failed");
"Invalid relevant attribute name");
}
}
}

@ -25,6 +25,8 @@ public class MethodEntityInfo : BaseEntityInfo
public bool HasPhaseSet { get; private set; }
public override bool HasEntitiesToAdd => base.HasEntitiesToAdd || !HasPhaseSet;
// TODO: Support [Source].
public MethodEntityInfo(ISymbol symbol)
: base(symbol)
{
@ -43,6 +45,9 @@ public class MethodEntityInfo : BaseEntityInfo
if (IsSystem && IsObserver) yield return Diagnostic.Create(
Descriptors.InvalidAttributeCombination, Location, "[System, Observer]");
if (Parent is ModuleEntityInfo { IsBuiltIn: true }) yield return Diagnostic.Create(
Descriptors.BuiltInModuleMustNotHaveMethods, Location);
if (Symbol.IsAbstract) yield return Diagnostic.Create(
Descriptors.MethodMustNotBeAbstract, Location);
if (Symbol.IsAsync) yield return Diagnostic.Create(

@ -10,15 +10,17 @@ namespace gaemstone.SourceGen.Structure;
public class ModuleEntityInfo : TypeEntityInfo
{
public bool IsPartial { get; }
public bool IsBuiltIn { get; }
// TODO: Support [Source].
public bool HasInitializer { get; }
public ModuleEntityInfo(ISymbol symbol)
: base(symbol)
{
var classDecl = (TypeDeclarationSyntax)Symbol.DeclaringSyntaxReferences.First().GetSyntax();
IsPartial = classDecl.Modifiers.Any(t => t.IsKind(SyntaxKind.PartialKeyword));
HasInitializer = Symbol.AllInterfaces.Any(i =>
i.GetFullName(FullNameStyle.NoGeneric) == "gaemstone.ECS.IModuleInitializer");
IsBuiltIn = Has("BuiltIn");
}

@ -17,6 +17,11 @@ public class TypeEntityInfo : BaseEntityInfo
public bool IsSingleton { get; }
public bool IsRelation { get; }
// Built-in entities error if they don't exist.
// When a module is built-in, most attributes become descriptive,
// and don't change or affect the entity they're attached to.
public bool IsBuiltIn { get; protected set; }
public bool IsValueType => Symbol.IsValueType;
public bool IsReferenceType => Symbol.IsReferenceType;
// TODO: Make sure that component has (instance) fields, non-component entity does not have fields.
@ -30,6 +35,10 @@ public class TypeEntityInfo : BaseEntityInfo
IsComponent = Has("Component");
IsSingleton = Has("Singleton");
IsRelation = Has("Relation");
IsBuiltIn = !IsModule && !IsEntity && !IsTag
&& !IsComponent && !IsSingleton && !IsRelation
&& (EntityPath != null);
}
protected override IEnumerable<Diagnostic> ValidateSelf()
@ -49,7 +58,14 @@ public class TypeEntityInfo : BaseEntityInfo
if (IsComponent) attributeList.Add("Component");
if (IsSingleton) attributeList.Add("Singleton");
switch (attributeList) {
switch (attributeList)
{
// No attributes are only valid if [Path] is set.
case [ ]:
if (!IsBuiltIn) yield return Diagnostic.Create(
Descriptors.AttributesRequireTypeOrPath, Location,
$"[{string.Join(", ", attributeList)}]");
break;
// A single attribute is valid.
case [ _ ]:
// The following are valid attribute combinations.
@ -70,6 +86,10 @@ public class TypeEntityInfo : BaseEntityInfo
break;
}
// In in a built-in module, this entity is built-in, too.
if (Parent is ModuleEntityInfo { IsBuiltIn: true })
IsBuiltIn = true;
// Singletons are special kinds of components.
if (IsSingleton) IsComponent = true;
}

@ -40,6 +40,22 @@ public class AddAttribute<TEntity> : Attribute { }
public class AddAttribute<TRelation, TTarget> : Attribute { }
/// <summary>
/// Marked entity automatically has the specified component added to it when
/// automatically registered. Equivalent to <see cref="EntityBuilder.Set{TComponent}"/>.
/// Arguments are passed to a valid constructor with matching arguments. Only
/// attribute-safe primitives are allowed for the specified arguments.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct
| AttributeTargets.Enum | AttributeTargets.Method,
AllowMultiple = true)]
public class SetAttribute<TComponent> : Attribute
{
public object[] Arguments { get; }
public SetAttribute(params object[] args) => Arguments = args;
}
/// <seealso cref="IsA"/>
public class IsAAttribute<TTarget> : AddAttribute<IsA, TTarget> { }
/// <seealso cref="ChildOf"/>

@ -7,35 +7,22 @@ public partial class Core
{
// Entity Tags
[Tag] public struct Name { }
[Tag] public struct Symbol { }
[Tag] public struct Alias { }
[Tag] public struct Module { }
[Tag] public struct Private { }
[Tag] public struct Prefab { }
[Tag] public struct SlotOf { }
[Tag] public struct Disabled { }
[Tag] public struct Empty { }
// Entities
[Entity, Path("*")] public struct Wildcard { }
[Entity, Path("_")] public struct Any { }
[Entity] public struct This { }
[Entity, Path("$")] public struct Variable { }
[Entity] public struct Flag { }
// Entity Relationships
[Relation, Tag] public struct IsA { }
[Relation, Tag] public struct ChildOf { }
[Relation, Tag] public struct DependsOn { }
[Tag] public struct SlotOf { }
[Tag] public struct Flag { }
// Component / Relationship Properties
[Entity, Path("*")] public struct Wildcard { }
[Entity, Path("_")] public struct Any { }
[Entity, Path("this")] public struct This { }
[Entity, Path("$")] public struct Variable { }
[Tag] public struct Transitive { }
[Tag] public struct Reflexive { }
[Tag] public struct Symmetric { }
@ -45,16 +32,34 @@ public partial class Core
[Tag] public struct Union { }
[Tag] public struct Exclusive { }
[Tag] public struct Acyclic { }
[Tag] public struct Traversable { }
[Relation, Tag] public struct With { }
[Tag] public struct OneOf { }
// Entity Relationships
[Relation, Tag] public struct ChildOf { }
[Relation, Tag] public struct IsA { }
[Relation, Tag] public struct DependsOn { }
// Identifier Tags
[Tag] public struct Name { }
[Tag] public struct Symbol { }
[Tag] public struct Alias { }
// Observer Events
[Entity] public struct OnAdd { }
[Entity] public struct OnRemove { }
[Entity] public struct OnSet { }
[Entity] public struct UnSet { }
[Entity] public struct OnTableCreate { }
[Entity] public struct OnTableDelete { }
[Entity] public struct OnTableEmpty { }
[Entity] public struct OnTableFilled { }

@ -91,11 +91,18 @@ public class ModuleManager<TContext>
public ModuleInfo(Universe<TContext> universe)
{
var world = universe.World;
var builder = world.New(T.Path);
var deps = new List<ModuleDependency>();
var world = universe.World;
if (T.IsBuiltIn)
{
Entity = world.LookupPathOrThrow(T.Path);
Dependencies = Array.Empty<ModuleDependency>();
}
else
{
var builder = world.New(T.Path);
var deps = new List<ModuleDependency>();
if (!T.IsBuiltIn) {
builder.Add<Module>();
foreach (var dependsPath in T.Dependencies) {
var dependency = world.LookupPathOrNull(dependsPath) ??
@ -108,15 +115,14 @@ public class ModuleManager<TContext>
if (!isDepInit) builder.Add<Disabled>();
builder.Add<DependsOn>(dependency);
}
}
Entity = builder.Build().CreateLookup<T>();
Dependencies = deps.AsReadOnly();
Entity = builder.Build().CreateLookup<T>();
Dependencies = deps.AsReadOnly();
if (!T.IsBuiltIn)
// Ensure all parent entities have the Module tag set.
for (var p = Entity.Parent; p is Entity<TContext> parent; p = parent.Parent)
parent.Add<Module>();
}
}
public void Enable()

Loading…
Cancel
Save