From c13a6cf34094cee8841c5e2cbace019422ca04f2 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Tue, 18 Jun 2024 17:31:26 -0300 Subject: [PATCH 1/5] fix: Adding components during some events would crash the engine --- Basalt.TestField/Components/TestTrigger.cs | 5 ++++ Basalt.TestField/Program.cs | 22 ++++++---------- Basalt/Common/Entities/Entity.cs | 29 ++++++++++++++++------ 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/Basalt.TestField/Components/TestTrigger.cs b/Basalt.TestField/Components/TestTrigger.cs index 0e8e351..007a450 100644 --- a/Basalt.TestField/Components/TestTrigger.cs +++ b/Basalt.TestField/Components/TestTrigger.cs @@ -1,6 +1,7 @@ using Basalt.Common.Components; using Basalt.Common.Entities; using Basalt.Raylib.Components; +using System.Numerics; namespace Basalt.TestField.Components { @@ -14,8 +15,12 @@ public override void OnCollision(Collider other) { if (other.Entity.Id == "entity.player") { + Entity.Collider!.Enabled = false; Console.WriteLine($"Trigger collided with {other.Entity.Id}"); Entity.GetComponent().ModelCacheKey = "sphere"; + Entity.AddComponent(new RaylibParticleSystem(Entity) { Looping = true, ModelCacheKey = "knot"}); + var ps = Entity.GetComponent(); + ps.UpdateDefaults(new Types.Particle { Velocity = Vector3.UnitY }); } } } diff --git a/Basalt.TestField/Program.cs b/Basalt.TestField/Program.cs index 0650bb7..6633278 100644 --- a/Basalt.TestField/Program.cs +++ b/Basalt.TestField/Program.cs @@ -15,6 +15,7 @@ using Basalt.Raylib.Sound; using Basalt.Raylib.Utils; using Basalt.TestField; +using Basalt.TestField.Components; using Basalt.Types; using Raylib_cs; using System.Numerics; @@ -65,20 +66,13 @@ Engine.CreateEntity(player); -var emitter = new Entity(); -emitter.Transform.Position = new Vector3(0, 10, 0); -emitter.AddComponent(new RaylibParticleSystem(emitter) { ModelCacheKey = "cube" }); -Engine.CreateEntity(emitter); - -var ps = emitter.GetComponent()!; - -ps.SubscribeOnParticleReset((ref Particle p) => -{ - p.Velocity = new(Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5, Random.Shared.NextSingle() * 10 - 5); - // Apply random rotation - p.Rotation = Quaternion.CreateFromYawPitchRoll(Random.Shared.NextSingle() * MathF.PI * 2, Random.Shared.NextSingle() * MathF.PI * 2, Random.Shared.NextSingle() * MathF.PI * 2); - -}); +var trigger = new Entity(); +trigger.AddComponent(new ModelRenderer(trigger) { ModelCacheKey = "cube", Size = new Vector3(1f), ColorTint = Color.Green }); +trigger.AddComponent(new BoxCollider(trigger) { Size = new Vector3(1, 1, 1), IsTrigger = true }); +trigger.AddComponent(new TestTrigger(trigger)); +trigger.AddComponent(new Rigidbody(trigger) { IsKinematic = true }); +trigger.Transform.Position = new Vector3(0, 1, 5); +Engine.CreateEntity(trigger); TestingUtils.SetupTestingScene(250); TestingUtils.SetupDebugInfo(); \ No newline at end of file diff --git a/Basalt/Common/Entities/Entity.cs b/Basalt/Common/Entities/Entity.cs index 4f3b401..c7b64e4 100644 --- a/Basalt/Common/Entities/Entity.cs +++ b/Basalt/Common/Entities/Entity.cs @@ -21,6 +21,9 @@ public class Entity private List componentDtos = new(); private List components = new(); + [JsonIgnore] + private int componentCount = 0; + [JsonProperty("Id")] public string Id { get; set; } = System.Guid.NewGuid().ToString(); /// @@ -207,6 +210,7 @@ public void AddComponent(Component component, bool overwrite = false) // Handle other cases if necessary break; } + componentCount++; } @@ -244,6 +248,7 @@ private void ForceAddComponent(Component component) // Handle other cases if necessary break; } + componentCount++; } /// @@ -257,6 +262,7 @@ public void RemoveComponent(Component component) if (component.GetType() == typeof(Rigidbody)) Rigidbody = null; + componentCount--; } /// @@ -273,8 +279,9 @@ public void RemoveComponent(Component component) if (typeof(T) == typeof(Collider)) return Collider as T; - foreach (var component in components) + for (int i = 0; i < components.Count; i++) { + Component? component = components[i]; if (component is T match) { return match; @@ -344,13 +351,15 @@ public void Destroy() { Destroyed = true; Engine.RemoveEntity(this); - foreach (var child in Children) + for (int i = 0; i < Children.Count; i++) { + Entity? child = Children[i]; child.Destroy(); } - foreach (var component in components) + for (int i = 0; i < components.Count; i++) { + Component? component = components[i]; component.onDestroy(); } } @@ -361,8 +370,9 @@ public void Destroy() public void Create() { Engine.CreateEntity(this); - foreach (var child in Children) + for (int i = 0; i < Children.Count; i++) { + Entity? child = Children[i]; child.Create(); } } @@ -371,8 +381,9 @@ public void CallOnCollision(Collider other) { if (Destroyed) return; - foreach (var component in components) - component.OnCollision(other); + + for (int i = 0; i < componentCount; i++) + components[i].OnCollision(other); } private static Type? ByName(string name) @@ -394,8 +405,9 @@ public void CallOnCollision(Collider other) internal void CallStart() { - foreach (var component in components) + for (int i = 0; i < components.Count; i++) { + Component? component = components[i]; if (!component.started) { var dependencyAttribute = component.GetType().GetCustomAttribute(); @@ -431,8 +443,9 @@ public Entity Clone() { var result = new Entity(); result.Id = Id + Guid.NewGuid().ToString(); - foreach (var component in components) + for (int i = 0; i < components.Count; i++) { + Component? component = components[i]; var c = Activator.CreateInstance(component.GetType(), result) as Component; foreach (var prop in c.GetType().GetProperties()) { From 7aab9696b8da0ddda0076eef0be87d29e28461ec Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Tue, 18 Jun 2024 17:40:54 -0300 Subject: [PATCH 2/5] fix: Components are now initialized when added after entity creation --- Basalt.TestField/Components/TestTrigger.cs | 7 +------ Basalt/Common/Entities/Entity.cs | 15 ++++++++++++--- Basalt/Common/Events/EventBus.cs | 5 ++--- Basalt/Engine.cs | 1 + 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Basalt.TestField/Components/TestTrigger.cs b/Basalt.TestField/Components/TestTrigger.cs index 007a450..d3bb576 100644 --- a/Basalt.TestField/Components/TestTrigger.cs +++ b/Basalt.TestField/Components/TestTrigger.cs @@ -15,12 +15,7 @@ public override void OnCollision(Collider other) { if (other.Entity.Id == "entity.player") { - Entity.Collider!.Enabled = false; - Console.WriteLine($"Trigger collided with {other.Entity.Id}"); - Entity.GetComponent().ModelCacheKey = "sphere"; - Entity.AddComponent(new RaylibParticleSystem(Entity) { Looping = true, ModelCacheKey = "knot"}); - var ps = Entity.GetComponent(); - ps.UpdateDefaults(new Types.Particle { Velocity = Vector3.UnitY }); + Entity.Destroy(); } } } diff --git a/Basalt/Common/Entities/Entity.cs b/Basalt/Common/Entities/Entity.cs index c7b64e4..e3eb93d 100644 --- a/Basalt/Common/Entities/Entity.cs +++ b/Basalt/Common/Entities/Entity.cs @@ -21,6 +21,8 @@ public class Entity private List componentDtos = new(); private List components = new(); + [JsonIgnore] + internal bool created = false; [JsonIgnore] private int componentCount = 0; @@ -210,6 +212,10 @@ public void AddComponent(Component component, bool overwrite = false) // Handle other cases if necessary break; } + + if(created) + component.OnStartEvent(this, EventArgs.Empty); + componentCount++; } @@ -357,7 +363,8 @@ public void Destroy() child.Destroy(); } - for (int i = 0; i < components.Count; i++) + var count = componentCount; + for (int i = 0; i < count; i++) { Component? component = components[i]; component.onDestroy(); @@ -382,7 +389,8 @@ public void CallOnCollision(Collider other) if (Destroyed) return; - for (int i = 0; i < componentCount; i++) + var count = componentCount; + for (int i = 0; i < count; i++) components[i].OnCollision(other); } @@ -405,7 +413,8 @@ public void CallOnCollision(Collider other) internal void CallStart() { - for (int i = 0; i < components.Count; i++) + var count = componentCount; + for (int i = 0; i < count; i++) { Component? component = components[i]; if (!component.started) diff --git a/Basalt/Common/Events/EventBus.cs b/Basalt/Common/Events/EventBus.cs index 4e38c28..1591b16 100644 --- a/Basalt/Common/Events/EventBus.cs +++ b/Basalt/Common/Events/EventBus.cs @@ -1,4 +1,5 @@ using Basalt.Core.Common.Abstractions.Engine; +using System.Collections.Concurrent; namespace Basalt.Common.Events { /// @@ -6,17 +7,15 @@ namespace Basalt.Common.Events /// public class EventBus : IEventBus { - private readonly List observers; private readonly object lockObject; // Game Events - private Dictionary eventHandlers = new Dictionary(); + private ConcurrentDictionary eventHandlers = new ConcurrentDictionary(); /// /// Initializes a new instance of the class. /// public EventBus() { - observers = new List(); lockObject = new object(); } diff --git a/Basalt/Engine.cs b/Basalt/Engine.cs index 99628fa..600ba97 100644 --- a/Basalt/Engine.cs +++ b/Basalt/Engine.cs @@ -177,6 +177,7 @@ public static void CreateEntity(Entity entity) Instance.EntityManager.AddEntity(entity); Instance.GetEngineComponent()?.AddEntityToSimulation(entity); entity.CallStart(); + entity.created = true; } /// From 7d19af5b30834bbce56df88c45e4697cf361e610 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Tue, 18 Jun 2024 17:50:48 -0300 Subject: [PATCH 3/5] fix: Particle Lifetime and starting position now work as expected --- Basalt.TestField/Components/TestTrigger.cs | 11 ++++++++++- Basalt/Common/Components/BaseParticleSystem.cs | 2 ++ Basalt/Engine.cs | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Basalt.TestField/Components/TestTrigger.cs b/Basalt.TestField/Components/TestTrigger.cs index d3bb576..4e44b36 100644 --- a/Basalt.TestField/Components/TestTrigger.cs +++ b/Basalt.TestField/Components/TestTrigger.cs @@ -1,6 +1,7 @@ using Basalt.Common.Components; using Basalt.Common.Entities; using Basalt.Raylib.Components; +using System.Drawing; using System.Numerics; namespace Basalt.TestField.Components @@ -11,11 +12,19 @@ public TestTrigger(Entity entity) : base(entity) { } + public override void OnStart() + { + Entity.AddComponent(new RaylibParticleSystem(Entity) { Looping = true, ModelCacheKey = "cube" }); + var ps = Entity.GetComponent(); + ps.UpdateDefaults(new() { Color = Color.RebeccaPurple, Velocity = new Vector3(0, 1f, 0) }); + ps.OnStartEvent(this, EventArgs.Empty); + } + public override void OnCollision(Collider other) { if (other.Entity.Id == "entity.player") { - Entity.Destroy(); + } } } diff --git a/Basalt/Common/Components/BaseParticleSystem.cs b/Basalt/Common/Components/BaseParticleSystem.cs index 5b02ffa..dfbae23 100644 --- a/Basalt/Common/Components/BaseParticleSystem.cs +++ b/Basalt/Common/Components/BaseParticleSystem.cs @@ -186,6 +186,7 @@ public void UnsubscribeOnParticleReset(ParticleUpdateDelegate update) /// The new default particle value public void UpdateDefaults(Particle particle) { + particle.Position = Entity.Transform.Position; defaults = particle; } @@ -198,6 +199,7 @@ public void Reset() for (int i = 0; i < _particles.Length; i++) { _particles[i] = defaults; + _particles[i].Lifetime = ParticleLifetime / _length * i; } } diff --git a/Basalt/Engine.cs b/Basalt/Engine.cs index 600ba97..e8a2d77 100644 --- a/Basalt/Engine.cs +++ b/Basalt/Engine.cs @@ -176,8 +176,8 @@ public static void CreateEntity(Entity entity) Instance.Logger?.LogDebug($"Creating entity {entity.Id}..."); Instance.EntityManager.AddEntity(entity); Instance.GetEngineComponent()?.AddEntityToSimulation(entity); - entity.CallStart(); entity.created = true; + entity.CallStart(); } /// From 7fb27224e79ecfbbfd6eb6d4d5220b2cb4e94b0e Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Tue, 18 Jun 2024 17:52:15 -0300 Subject: [PATCH 4/5] chore(release): 1.9.1 --- Basalt.Core/Basalt.Core.csproj | 2 +- Basalt.Raylib/Basalt.Raylib.csproj | 2 +- Basalt/Basalt.csproj | 2 +- CHANGELOG.md | 9 +++++++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Basalt.Core/Basalt.Core.csproj b/Basalt.Core/Basalt.Core.csproj index a031b73..14c8d4d 100644 --- a/Basalt.Core/Basalt.Core.csproj +++ b/Basalt.Core/Basalt.Core.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 1.9.0 + 1.9.1 True BasaltLogoBg.png README.md diff --git a/Basalt.Raylib/Basalt.Raylib.csproj b/Basalt.Raylib/Basalt.Raylib.csproj index 952ccd7..383038c 100644 --- a/Basalt.Raylib/Basalt.Raylib.csproj +++ b/Basalt.Raylib/Basalt.Raylib.csproj @@ -5,7 +5,7 @@ enable enable true - 1.9.0 + 1.9.1 True BasaltLogoBg.png README.md diff --git a/Basalt/Basalt.csproj b/Basalt/Basalt.csproj index 9a957ea..015cd5b 100644 --- a/Basalt/Basalt.csproj +++ b/Basalt/Basalt.csproj @@ -5,7 +5,7 @@ enable enable true - 1.9.0 + 1.9.1 True Basalt BasaltLogoBg.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 52f11c7..d7e2f2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. See [versionize](https://github.com/versionize/versionize) for commit guidelines. + +## [1.9.1](https://www.github.com/thiagomvas/Basalt/releases/tag/v1.9.1) (2024-06-18) + +### Bug Fixes + +* Adding components during some events would crash the engine ([c13a6cf](https://www.github.com/thiagomvas/Basalt/commit/c13a6cf34094cee8841c5e2cbace019422ca04f2)) +* Components are now initialized when added after entity creation ([7aab969](https://www.github.com/thiagomvas/Basalt/commit/7aab9696b8da0ddda0076eef0be87d29e28461ec)) +* Particle Lifetime and starting position now work as expected ([7d19af5](https://www.github.com/thiagomvas/Basalt/commit/7d19af5b30834bbce56df88c45e4697cf361e610)) + ## [1.9.0](https://www.github.com/thiagomvas/Basalt/releases/tag/v1.9.0) (2024-06-18) From 55bcc7526d2c6a296ffa6f6e9e57a22b6d25ccb2 Mon Sep 17 00:00:00 2001 From: Thiago Menezes Date: Tue, 18 Jun 2024 18:01:00 -0300 Subject: [PATCH 5/5] Remove test ensuring references are deserialized from Entity.DeserializeFromJson() Entity.DeserializeFromJson() should not be used to deserialize references, as that might cause many issues down the line --- .../Integration/EntityIntegrationTests.cs | 24 ------------------- Basalt/Common/Entities/Entity.cs | 3 +++ 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/Basalt.Tests/Integration/EntityIntegrationTests.cs b/Basalt.Tests/Integration/EntityIntegrationTests.cs index b4dd029..c0b72ca 100644 --- a/Basalt.Tests/Integration/EntityIntegrationTests.cs +++ b/Basalt.Tests/Integration/EntityIntegrationTests.cs @@ -169,30 +169,6 @@ public void EntitySerializeToJson_WhenComponentHasEntityReference_ShouldSerializ Assert.That(json, Does.Contain("entity.target")); } - [Test] - public void EntityDeserializeFromJson_WhenComponentHasEntityReference_ShouldDeserializeReference() - { - // Arrange - var target = new Entity(); - target.Id = "entity.target"; - var entity = new Entity(); - entity.Id = "entity.testing"; - entity.AddComponent(new TestComponent(entity) { Target = target }); - - // Act - Engine.Instance.Initialize(); - Engine.CreateEntity(target); - var json = entity.SerializeToJson(); - var newEntity = Entity.DeserializeFromJson(json); - Engine.CreateEntity(newEntity); - - // Assert - Assert.IsNotNull(newEntity, "Deserialization failed, result entity was null"); - Assert.IsNotNull(newEntity.GetComponent(), "Test component was null"); - Assert.IsTrue(newEntity.GetComponent().HasStarted, "Component did not initialize"); - Assert.IsNotNull(newEntity.GetComponent().Target, "Target was null"); - Assert.That(newEntity.GetComponent().Target.Id, Is.EqualTo(target.Id), "Ids are different"); - } [Test] public void EntityDeserializeToJson_WhenMissingIdField_ShouldGenerateNew() diff --git a/Basalt/Common/Entities/Entity.cs b/Basalt/Common/Entities/Entity.cs index e3eb93d..acb43fd 100644 --- a/Basalt/Common/Entities/Entity.cs +++ b/Basalt/Common/Entities/Entity.cs @@ -118,6 +118,9 @@ public string SerializeToJson() /// /// The json string to deserialize from /// An entity instance from the JSON string + /// + /// It is not recommended to rely on deserialization to keep references as they will break. + /// public static Entity DeserializeFromJson(string json) { JObject jObject = JObject.Parse(json);