Fixed ECS so now components can actually be added and removed accordingly (still nothing renders though)

This commit is contained in:
2025-12-29 11:42:02 +01:00
parent 4e6fd1f52f
commit 36a88b280c
9 changed files with 138 additions and 138 deletions

View File

@@ -2,5 +2,5 @@
public abstract class Component
{
public Entity Entity;
}

View File

@@ -1,4 +1,6 @@
using System.ComponentModel;
using Silk.NET.Maths;
using Silk.NET.OpenGL;
namespace EngineSharp.Core.ECS;
@@ -6,29 +8,88 @@ namespace EngineSharp.Core.ECS;
// https://github.com/friflo/Friflo.Engine.ECS/tree/main
// OR: https://github.com/genaray/Arch
public struct Entity
public class Entity
{
public readonly long Id;
private Scene _scene;
public required string Name { get; set; }
internal Entity(long id, Scene scene)
{
Id = id;
_scene = scene;
}
// TODO: Maybe use a Hashset and implement a comparer or something? Should be faster than the current .OfType<>().Any() implementation
private readonly List<LogicComponent> _logicComponents = [];
private readonly List<DataComponent> _dataComponents = [];
private readonly List<RenderComponent> _renderComponents = [];
/// <summary>
/// If the component already exists on the entity, it will not be added.
/// Do not set this property manually! Will be set via the ECS.
/// </summary>
public void AddComponent(Component component)
{
_scene.AddComponent(this, component);
}
public long Id { get; init; }
public string Name { get; init; }
/// <summary>
/// If the component already exists on the entity, it will not be added
/// </summary>
public bool AddComponent<T>(T component)
where T : Component
{
component.Entity = this;
return component switch
{
LogicComponent logic => AddComponent(logic, _logicComponents),
DataComponent data => AddComponent(data, _dataComponents),
RenderComponent render => AddComponent(render, _renderComponents),
_ => false
};
}
public T? GetComponent<T>(T component)
where T : Component
{
return component switch
{
LogicComponent => _logicComponents.OfType<T>().SingleOrDefault(),
DataComponent => _dataComponents.OfType<T>().SingleOrDefault(),
RenderComponent => _renderComponents.OfType<T>().SingleOrDefault(),
_ => null
};
}
internal void InitialiseComponents()
{
foreach (var components in _logicComponents)
{
components.Initialise();
}
}
internal void UpdateComponents(double deltaTime)
{
foreach (var components in _logicComponents)
{
components.OnUpdate(deltaTime);
}
}
internal void RenderComponents(GL gl, Matrix4X4<float> projectionMatrix, Matrix4X4<float> viewMatrix)
{
// TODO: retrieve the data component "Transform" which every Entity must have (although currently there is no transform component)
var modelMatrix = Matrix4X4.CreateTranslation(0, 0, -10f);
foreach (var components in _renderComponents)
{
components.Render(gl, projectionMatrix, viewMatrix, modelMatrix);
}
}
/// <summary>
/// If the component already exists on the entity, it will not be added
/// </summary>
private static bool AddComponent<TComponent, TArchetype>(TComponent component, List<TArchetype> componentStore)
where TArchetype : Component
where TComponent : TArchetype
{
if (componentStore.OfType<TComponent>().Any())
{
return false;
}
componentStore.Add(component);
return true;
}
public Component? GetComponent(Component component)
{
return _scene.GetComponent(this, component);
}
}

View File

@@ -1,113 +1,48 @@
using System.Collections.Generic;
using System.Linq;
using Silk.NET.Maths;
using Silk.NET.Maths;
using Silk.NET.OpenGL;
namespace EngineSharp.Core.ECS;
public class Scene
{
// TODO: Maybe instead of a List, use a HashSet. Implement a equality comparer to ensure, only one element per type can be present
private readonly Dictionary<long, List<LogicComponent>> _logicComponents = new();
private readonly Dictionary<long, List<DataComponent>> _dataComponents = new();
private readonly Dictionary<long, List<RenderComponent>> _renderComponents = new();
private readonly Dictionary<long, Entity> _entities = [];
private long _nextSceneId; // not the prettiest solution but it works
public Entity CreateEntity(string name)
public TEntity CreateEntity<TEntity>(string name)
where TEntity : Entity, new()
{
return new Entity(_nextSceneId++, this)
var entity = new TEntity
{
Id = _nextSceneId++,
Name = name,
};
_entities.Add(entity.Id, entity);
return entity;
}
internal void InitialiseComponents()
internal void InitialiseEntities()
{
foreach (var components in _logicComponents.Values)
foreach (var entity in _entities.Values)
{
components.ForEach(c => c.Initialise());
entity.InitialiseComponents();
}
}
internal void UpdateComponents(double deltaTime)
{
foreach (var components in _logicComponents.Values)
foreach (var entity in _entities.Values)
{
components.ForEach(c => c.OnUpdate(deltaTime));
entity.UpdateComponents(deltaTime);
}
}
internal void RenderComponents(GL gl, Matrix4X4<float> projectionMatrix, Matrix4X4<float> viewMatrix)
{
foreach (var components in _renderComponents.Values)
foreach (var entity in _entities.Values)
{
// TODO: retrieve the transform component of the entity to generate the model matrix
// TODO: make a record which holds the matrices and maybe more, so the parameter list doesn't explode
var modelMatrix = Matrix4X4<float>.Identity;
var t = Matrix4X4.CreateTranslation(0, 0, -10f);
components.ForEach(c => c.Render(gl, projectionMatrix, viewMatrix, t));
}
}
/// <summary>
/// If the component already exists on the entity, it will not be added
/// </summary>
internal void AddComponent<T>(Entity entity, T component)
where T : Component
{
switch (component)
{
case LogicComponent logic:
AddComponent(entity.Id, logic, _logicComponents);
break;
case DataComponent data:
AddComponent(entity.Id, data, _dataComponents);
break;
case RenderComponent render:
AddComponent(entity.Id, render, _renderComponents);
break;
}
}
internal T? GetComponent<T>(Entity entity, T component)
where T : Component
{
return component switch
{
LogicComponent => GetComponent<T, LogicComponent>(entity.Id, _logicComponents),
DataComponent => GetComponent<T, DataComponent>(entity.Id, _dataComponents),
RenderComponent => GetComponent<T, RenderComponent>(entity.Id, _renderComponents),
_ => null
};
}
private static TComponent? GetComponent<TComponent, TArchetype>(long id, Dictionary<long, List<TArchetype>> componentStore)
where TComponent : Component
{
// ReSharper disable once ConvertIfStatementToReturnStatement
if (componentStore.TryGetValue(id, out var components))
{
return components.OfType<TComponent>().SingleOrDefault();
}
return null;
}
/// <summary>
/// If the component already exists on the entity, it will not be added
/// </summary>
private static void AddComponent<TComponent, TArchetype>(long id, TComponent component, Dictionary<long, List<TArchetype>> componentStore)
where TArchetype : Component
where TComponent : TArchetype
{
if (componentStore.TryGetValue(id, out var components) && !components.OfType<TComponent>().Any())
{
components.Add(component);
}
else
{
componentStore.Add(id, [component]);
entity.RenderComponents(gl, projectionMatrix, viewMatrix);
}
}
}

View File

@@ -75,7 +75,7 @@ internal class OpenGLEngine : Engine
if (_scenes.TryGetValue(sceneName, out var scene))
{
_currentScene = scene;
_currentScene.InitialiseComponents();
_currentScene.InitialiseEntities();
}
else
{

View File

@@ -14,7 +14,6 @@ public class MeshRenderer : RenderComponent
internal override void Render(GL gl, Matrix4X4<float> projectionMatrix, Matrix4X4<float> viewMatrix, Matrix4X4<float> modelMatrix)
{
GenerateRenderableMesh(gl, projectionMatrix, viewMatrix, modelMatrix);
gl.BindVertexArray(vao);
gl.DrawElements(PrimitiveType.Triangles, (uint)Mesh.Indices.Length, DrawElementsType.UnsignedInt, 0);
}

View File

@@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using EngineSharp.Core.Rendering;
using EngineSharp.Core.Rendering;
using EngineSharp.Extensions;
using Silk.NET.Maths;
namespace Engine_silk.NET;
namespace EngineSharp;
public class IcoSphere
{

View File

@@ -1,34 +1,11 @@
using Engine_silk.NET;
using EngineSharp.Core;
using EngineSharp.Core.Rendering;
namespace EngineSharp;
public class Planet : Core.ECS.LogicComponent
public class Planet : Core.ECS.Entity
{
public MeshRenderer PlanetMesh;
public override void Initialise()
public Planet()
{
var shader = Shader.GetShader("./assets/shaders/sphere.vert");
var material = new Material
{
Shader = shader,
};
var sphereGenerator = new IcoSphere
{
Resolution = 10,
Material = material,
};
PlanetMesh = new MeshRenderer
{
Mesh = sphereGenerator.CreateSphere(),
};
}
public override void OnUpdate(double deltaTime)
{
// Nothing to do at the moment
AddComponent(new PlanetGenerator());
}
}

View File

@@ -0,0 +1,33 @@
using EngineSharp.Core.Rendering;
namespace EngineSharp;
public class PlanetGenerator : Core.ECS.LogicComponent
{
public override void Initialise()
{
var shader = Shader.GetShader("./assets/shaders/sphere.vert");
var material = new Material
{
Shader = shader,
};
var sphereGenerator = new IcoSphere
{
Resolution = 10,
Material = material,
};
// TODO: MeshRenderer should probably not have the mesh as a required property.
var planetMesh = new MeshRenderer
{
Mesh = sphereGenerator.CreateSphere(),
};
Entity.AddComponent(planetMesh);
}
public override void OnUpdate(double deltaTime)
{
// Nothing to do at the moment
}
}

View File

@@ -18,10 +18,7 @@ internal static class Program
var engine = EngineFactory.Create(GraphicsAPI.OpenGL, options);
var mainScene = SceneManager.CreateDefaultScene("default");
var planet = mainScene.CreateEntity("planet");
// TODO: Find a way to call "AddComponent" from a component. Otherwise the MeshRenderer I created inside the Planet-Component doesn't get registered and can't therefore be rendered
planet.AddComponent(new Planet());
var planet = mainScene.CreateEntity<Planet>("planet");
engine.Start();
}