diff --git a/AutoMap/AutoMap.cs b/AutoMap/AutoMap.cs index 0e838ea8..344bb252 100644 --- a/AutoMap/AutoMap.cs +++ b/AutoMap/AutoMap.cs @@ -19,8 +19,8 @@ class AutoMap [UsingDependency] private static IIsometricCameraFactory isometricCameraFactory; - [UsingDependency] - private static IEntityFactory EntityFactory; + //[UsingDependency] + //private static IEntityFactory EntityFactory; public static void Main(string[] args) { @@ -35,11 +35,11 @@ public static void Main(string[] args) IIsometricCamera camera = isometricCameraFactory.CreateCamera(); //Create the entity to hold and move the camera - EntityFactory.CreateEmptyEntity((mainCameraEntity) => { - mainCameraEntity.AddComponent(new TransformComponent()); + //EntityFactory.CreateEmptyEntity((mainCameraEntity) => { + // mainCameraEntity.AddComponent(new TransformComponent()); //mainCameraEntity.AddComponent(new PlayerMovementComponent()); //mainCameraEntity.AddComponent(new CameraComponent(camera)); - }); + //}); //Set the main camera CorgEngMain.SetMainCamera(camera); diff --git a/AutoMap/Rendering/AutoMapRenderCore.cs b/AutoMap/Rendering/AutoMapRenderCore.cs index 31a5484b..ccc67b11 100644 --- a/AutoMap/Rendering/AutoMapRenderCore.cs +++ b/AutoMap/Rendering/AutoMapRenderCore.cs @@ -1,6 +1,7 @@ using CorgEng.Core; using CorgEng.Core.Dependencies; using CorgEng.Core.Rendering; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Rendering.Renderers.SpriteRendering; using System; using System.Collections.Generic; @@ -18,6 +19,10 @@ public class AutoMapRenderCore : RenderCore private ISpriteRenderer spriteRenderer; + public AutoMapRenderCore(IWorld world) : base(world) + { + } + public override void Initialize() { spriteRenderer = SpriteRendererFactory.CreateSpriteRenderer(0); diff --git a/CorgEng.AiBehaviour/BehaviourManager.cs b/CorgEng.AiBehaviour/BehaviourManager.cs index d04d0dc9..15207145 100644 --- a/CorgEng.AiBehaviour/BehaviourManager.cs +++ b/CorgEng.AiBehaviour/BehaviourManager.cs @@ -11,7 +11,7 @@ namespace CorgEng.AiBehaviour { - internal class BehaviourManager : IBehaviourManager + internal class BehaviourManager : WorldObject, IBehaviourManager { //Are we currently processing? Prevents AIs from having multiple AI trees thinking @@ -22,7 +22,7 @@ internal class BehaviourManager : IBehaviourManager private TransformComponent _transform; - public IVector Position => _transform.Position; + public IVector Position => _transform.Position.Value; /// /// The pawn entity we are attached to @@ -57,15 +57,16 @@ public IEntity PawnEntity /// /// The root behaviour node /// - internal BehaviourRoot root = new BehaviourRoot(); + internal BehaviourRoot root; /// /// Setup the behaviour manager /// /// - public BehaviourManager(IEntity pawnEntity) + public BehaviourManager(IWorld world, IEntity pawnEntity) : base(world) { PawnEntity = pawnEntity; + root = new BehaviourRoot(world); } public async Task Process() diff --git a/CorgEng.AiBehaviour/BehaviourNode.cs b/CorgEng.AiBehaviour/BehaviourNode.cs index 558e8b7b..c79fa157 100644 --- a/CorgEng.AiBehaviour/BehaviourNode.cs +++ b/CorgEng.AiBehaviour/BehaviourNode.cs @@ -2,6 +2,7 @@ using CorgEng.Core.Dependencies; using CorgEng.GenericInterfaces.AiBehaviours; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Logging; using CorgEng.GenericInterfaces.UtilityTypes.BinaryLists; using System; @@ -12,7 +13,7 @@ namespace CorgEng.AiBehaviour { - public abstract class BehaviourNode : IBehaviourNode + public abstract class BehaviourNode : WorldObject, IBehaviourNode { [UsingDependency] @@ -27,7 +28,7 @@ public abstract class BehaviourNode : IBehaviourNode public abstract BehaviourContinuationMode ContinuationMode { get; } - public BehaviourNode() + public BehaviourNode(IWorld world) : base(world) { Subtasks = BinaryListFactory.CreateEmpty(); } diff --git a/CorgEng.AiBehaviour/BehaviourRoot.cs b/CorgEng.AiBehaviour/BehaviourRoot.cs index 3bfced44..a65d04c6 100644 --- a/CorgEng.AiBehaviour/BehaviourRoot.cs +++ b/CorgEng.AiBehaviour/BehaviourRoot.cs @@ -1,4 +1,5 @@ using CorgEng.GenericInterfaces.AiBehaviours; +using CorgEng.GenericInterfaces.EntityComponentSystem; using System; using System.Collections.Generic; using System.Linq; @@ -9,6 +10,9 @@ namespace CorgEng.AiBehaviour { internal class BehaviourRoot : BehaviourNode { + public BehaviourRoot(IWorld world) : base(world) + { + } public override BehaviourContinuationMode ContinuationMode => BehaviourContinuationMode.CANCEL_ON_FAIL; diff --git a/CorgEng.AiBehaviour/Factories/BehaviourManagerFactory.cs b/CorgEng.AiBehaviour/Factories/BehaviourManagerFactory.cs index 44203b93..caaffd1a 100644 --- a/CorgEng.AiBehaviour/Factories/BehaviourManagerFactory.cs +++ b/CorgEng.AiBehaviour/Factories/BehaviourManagerFactory.cs @@ -13,9 +13,9 @@ namespace CorgEng.AiBehaviour.Factories internal class BehaviourManagerFactory : IBehaviourManagerFactory { - public IBehaviourManager CreateBehaviourManager(IEntity attachedPawn, params IBehaviourNode[] behaviourNodes) + public IBehaviourManager CreateBehaviourManager(IWorld world, IEntity attachedPawn, params IBehaviourNode[] behaviourNodes) { - BehaviourManager createdManager = new BehaviourManager(attachedPawn); + BehaviourManager createdManager = new BehaviourManager(world, attachedPawn); int i = 0; foreach (IBehaviourNode behaviourNode in behaviourNodes) diff --git a/CorgEng.AiBehaviour/Systems/AiActionProcessingSystem.cs b/CorgEng.AiBehaviour/Systems/AiActionProcessingSystem.cs index 5b73ee2e..3413fd14 100644 --- a/CorgEng.AiBehaviour/Systems/AiActionProcessingSystem.cs +++ b/CorgEng.AiBehaviour/Systems/AiActionProcessingSystem.cs @@ -17,7 +17,7 @@ internal class AiActionProcessingSystem : ProcessingSystem protected override int ProcessDelay => 100; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(OnComponentAdded); } diff --git a/CorgEng.AiBehaviour/Systems/BehaviourProcessingSystem.cs b/CorgEng.AiBehaviour/Systems/BehaviourProcessingSystem.cs index 77701cfc..2dc4a309 100644 --- a/CorgEng.AiBehaviour/Systems/BehaviourProcessingSystem.cs +++ b/CorgEng.AiBehaviour/Systems/BehaviourProcessingSystem.cs @@ -22,7 +22,7 @@ internal class BehaviourProcessingSystem : ProcessingSystem protected override int ProcessDelay => 1000; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(OnComponentAdded); } diff --git a/CorgEng.Audio/Systems/AudioListenerSystem.cs b/CorgEng.Audio/Systems/AudioListenerSystem.cs index 1e69efd6..3425e068 100644 --- a/CorgEng.Audio/Systems/AudioListenerSystem.cs +++ b/CorgEng.Audio/Systems/AudioListenerSystem.cs @@ -18,7 +18,7 @@ internal class AudioListenerSystem : EntitySystem public override EntitySystemFlags SystemFlags => EntitySystemFlags.CLIENT_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(AudioListenerMoved); RegisterLocalEvent(AudioListenerInitialised); @@ -32,7 +32,7 @@ private void AudioListenerMoved(IEntity entity, AudioListenerComponent audioList private void AudioListenerInitialised(IEntity entity, AudioListenerComponent audioListenerComponent, InitialiseEvent initialiseEvent) { - IVector position = audioListenerComponent.Transform.Position; + IVector position = audioListenerComponent.Transform.Position.Value; AudioMaster.UpdateListener(position.X, position.Y, 0); } diff --git a/CorgEng.Claims/Systems/ClaimSystem.cs b/CorgEng.Claims/Systems/ClaimSystem.cs index 9e3bd392..c8e69b88 100644 --- a/CorgEng.Claims/Systems/ClaimSystem.cs +++ b/CorgEng.Claims/Systems/ClaimSystem.cs @@ -16,7 +16,7 @@ internal class ClaimSystem : EntitySystem public override EntitySystemFlags SystemFlags => EntitySystemFlags.HOST_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { //Deletable is common to all entities, so register to that component. RegisterLocalEvent(HandleClaimRequest); diff --git a/CorgEng.ContentLoading/DefinitionNodes/ArrayNode.cs b/CorgEng.ContentLoading/DefinitionNodes/ArrayNode.cs index aa85f01e..b22f42b6 100644 --- a/CorgEng.ContentLoading/DefinitionNodes/ArrayNode.cs +++ b/CorgEng.ContentLoading/DefinitionNodes/ArrayNode.cs @@ -1,4 +1,5 @@ using CorgEng.GenericInterfaces.ContentLoading.DefinitionNodes; +using CorgEng.GenericInterfaces.EntityComponentSystem; using System; using System.Collections.Generic; using System.Linq; @@ -26,7 +27,7 @@ public override void ParseSelf(XmlNode node) arrayType = EntityLoader.TypePaths[node.Attributes["type"].Value]; } - public override object CreateInstance(object parent, Dictionary instanceRefs) + public override object CreateInstance(IWorld world, object parent, Dictionary instanceRefs) { //Create the array object Array createdArray = Array.CreateInstance(arrayType, Children.Count); @@ -34,7 +35,7 @@ public override object CreateInstance(object parent, Dictionary int i = 0; foreach (DefinitionNode child in Children) { - createdArray.SetValue(child.CreateInstance(createdArray, instanceRefs), i++); + createdArray.SetValue(child.CreateInstance(world, createdArray, instanceRefs), i++); } //Add a reference to the created array if (Key != null) diff --git a/CorgEng.ContentLoading/DefinitionNodes/ComponentNode.cs b/CorgEng.ContentLoading/DefinitionNodes/ComponentNode.cs index ea4e9d3c..c427d2cf 100644 --- a/CorgEng.ContentLoading/DefinitionNodes/ComponentNode.cs +++ b/CorgEng.ContentLoading/DefinitionNodes/ComponentNode.cs @@ -19,10 +19,10 @@ public ComponentNode(DefinitionNode parent) : base(parent) Parent = parent as EntityNode; } - public override object CreateInstance(object parent, Dictionary instanceRefs) + public override object CreateInstance(IWorld world, object parent, Dictionary instanceRefs) { //Create the component - IComponent createdComponent = base.CreateInstance(parent, instanceRefs) as IComponent; + IComponent createdComponent = base.CreateInstance(world, parent, instanceRefs) as IComponent; //Add the compoennt IEntity parentEntity = parent as IEntity; parentEntity.AddComponent(createdComponent); diff --git a/CorgEng.ContentLoading/DefinitionNodes/DefinitionNode.cs b/CorgEng.ContentLoading/DefinitionNodes/DefinitionNode.cs index f077692b..be04619f 100644 --- a/CorgEng.ContentLoading/DefinitionNodes/DefinitionNode.cs +++ b/CorgEng.ContentLoading/DefinitionNodes/DefinitionNode.cs @@ -1,4 +1,5 @@ -using System; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -16,8 +17,11 @@ internal abstract class DefinitionNode public string? ID { get; set; } + protected IWorld world; + public DefinitionNode(DefinitionNode parent) { + this.world = world; parent?.Children.Add(this); } @@ -36,7 +40,7 @@ public virtual void ParseSelf(XmlNode node) /// Called when the instance needs to be created /// /// - public abstract object CreateInstance(object parent, Dictionary instanceRefs); + public abstract object CreateInstance(IWorld world, object parent, Dictionary instanceRefs); } } diff --git a/CorgEng.ContentLoading/DefinitionNodes/DependencyNode.cs b/CorgEng.ContentLoading/DefinitionNodes/DependencyNode.cs index 549e9425..f0ea23a6 100644 --- a/CorgEng.ContentLoading/DefinitionNodes/DependencyNode.cs +++ b/CorgEng.ContentLoading/DefinitionNodes/DependencyNode.cs @@ -1,6 +1,7 @@ using CorgEng.Core.Dependencies; using CorgEng.GenericInterfaces.ContentLoading.DefinitionNodes; using CorgEng.GenericInterfaces.DependencyInjection; +using CorgEng.GenericInterfaces.EntityComponentSystem; using System; using System.Collections.Generic; using System.ComponentModel; @@ -85,7 +86,7 @@ public override void ParseSelf(XmlNode node) } } - public override object CreateInstance(object parent, Dictionary instanceRefs) + public override object CreateInstance(IWorld world, object parent, Dictionary instanceRefs) { //Set the parameters if (hasDynamicParams) @@ -94,7 +95,7 @@ public override object CreateInstance(object parent, Dictionary { if (isDynamic[i]) { - parameters[i] = Children[i].CreateInstance(parent, instanceRefs); + parameters[i] = Children[i].CreateInstance(world, parent, instanceRefs); } } } diff --git a/CorgEng.ContentLoading/DefinitionNodes/DictionaryNode.cs b/CorgEng.ContentLoading/DefinitionNodes/DictionaryNode.cs index f351070d..23646523 100644 --- a/CorgEng.ContentLoading/DefinitionNodes/DictionaryNode.cs +++ b/CorgEng.ContentLoading/DefinitionNodes/DictionaryNode.cs @@ -1,4 +1,5 @@ using CorgEng.GenericInterfaces.ContentLoading.DefinitionNodes; +using CorgEng.GenericInterfaces.EntityComponentSystem; using System; using System.Collections.Generic; using System.Linq; @@ -41,7 +42,7 @@ public override void ParseSelf(XmlNode node) BindingFlags.Public | BindingFlags.Instance); } - public override object CreateInstance(object parent, Dictionary instanceRefs) + public override object CreateInstance(IWorld world, object parent, Dictionary instanceRefs) { //Create the array object object dictionary = Activator.CreateInstance(dictionaryType); @@ -49,7 +50,7 @@ public override object CreateInstance(object parent, Dictionary foreach (DefinitionNode child in Children) { addMethod.Invoke(dictionary, new object[] { - child.CreateInstance(dictionary, instanceRefs) + child.CreateInstance(world, dictionary, instanceRefs) }); } //Add a reference to the created array diff --git a/CorgEng.ContentLoading/DefinitionNodes/ElementNode.cs b/CorgEng.ContentLoading/DefinitionNodes/ElementNode.cs index 6e3663e5..a5ee85b1 100644 --- a/CorgEng.ContentLoading/DefinitionNodes/ElementNode.cs +++ b/CorgEng.ContentLoading/DefinitionNodes/ElementNode.cs @@ -1,4 +1,5 @@ using CorgEng.GenericInterfaces.ContentLoading.DefinitionNodes; +using CorgEng.GenericInterfaces.EntityComponentSystem; using System; using System.Collections.Generic; using System.ComponentModel; @@ -70,9 +71,9 @@ protected void ParseValue(XmlNode node) } } - public override object CreateInstance(object parent, Dictionary instanceRefs) + public override object CreateInstance(IWorld world, object parent, Dictionary instanceRefs) { - object propertyValue = GetValue(parent, instanceRefs); + object propertyValue = GetValue(world, parent, instanceRefs); if (Key != null) { instanceRefs.Add(Key, propertyValue); @@ -82,7 +83,7 @@ public override object CreateInstance(object parent, Dictionary private static MethodInfo cachedMethod = null; - private object GetValue(object parent, Dictionary instanceRefs) + private object GetValue(IWorld world, object parent, Dictionary instanceRefs) { //Has 2 children, key value pair if (Children.Count == 2) @@ -99,8 +100,8 @@ private object GetValue(object parent, Dictionary instanceRefs) } //Make the generic method and call it return cachedMethod.MakeGenericMethod(dictionaryParentKeyType, dictionaryParentValueType).Invoke(null, new object[] { - childKey.CreateInstance(parent, instanceRefs), - childValue.CreateInstance(parent, instanceRefs) + childKey.CreateInstance(world, parent, instanceRefs), + childValue.CreateInstance(world, parent, instanceRefs) }); } @@ -119,7 +120,7 @@ private object GetValue(object parent, Dictionary instanceRefs) } //1 Child, return value // Either object or value node - return Children[0].CreateInstance(null, instanceRefs); + return Children[0].CreateInstance(world, null, instanceRefs); } } diff --git a/CorgEng.ContentLoading/DefinitionNodes/EntityNode.cs b/CorgEng.ContentLoading/DefinitionNodes/EntityNode.cs index 0d2e6bb0..7cc17800 100644 --- a/CorgEng.ContentLoading/DefinitionNodes/EntityNode.cs +++ b/CorgEng.ContentLoading/DefinitionNodes/EntityNode.cs @@ -15,9 +15,6 @@ namespace CorgEng.GenericInterfaces.ContentLoading.DefinitionNodes internal class EntityNode : DefinitionNode, IEntityDefinition { - [UsingDependency] - public static IEntityFactory EntityFactory; - public bool Abstract { get; set; } = false; /// @@ -50,19 +47,19 @@ public override void ParseSelf(XmlNode node) /// Create an entity from this node /// /// - public IEntity CreateEntity(Action setupAction) + public IEntity CreateEntity(IWorld world, Action setupAction) { - return (IEntity)CreateInstance(null, new Dictionary(), setupAction); + return (IEntity)CreateInstance(world, null, new Dictionary(), setupAction); } - public override object CreateInstance(object parent, Dictionary instanceRefs) + public override object CreateInstance(IWorld world, object parent, Dictionary instanceRefs) { - return CreateInstance(parent, instanceRefs, null); + return CreateInstance(world, parent, instanceRefs, null); } - public object CreateInstance(object parent, Dictionary instanceRefs, Action setupAction) + public object CreateInstance(IWorld world, object parent, Dictionary instanceRefs, Action setupAction) { - IEntity createdEntity = EntityFactory.CreateUninitialisedEntity(); + IEntity createdEntity = world.EntityManager.CreateUninitialisedEntity(); //Store the key if (Key != null) { @@ -71,7 +68,7 @@ public object CreateInstance(object parent, Dictionary instanceR //Add on properties foreach (DefinitionNode childNode in Children) { - childNode.CreateInstance(createdEntity, instanceRefs); + childNode.CreateInstance(world, createdEntity, instanceRefs); } //Run init event setupAction?.Invoke(createdEntity); diff --git a/CorgEng.ContentLoading/DefinitionNodes/InstanceNode.cs b/CorgEng.ContentLoading/DefinitionNodes/InstanceNode.cs index 066d94d1..2f5bb905 100644 --- a/CorgEng.ContentLoading/DefinitionNodes/InstanceNode.cs +++ b/CorgEng.ContentLoading/DefinitionNodes/InstanceNode.cs @@ -1,4 +1,5 @@ using CorgEng.GenericInterfaces.ContentLoading.DefinitionNodes; +using CorgEng.GenericInterfaces.EntityComponentSystem; using System; using System.Collections.Generic; using System.Linq; @@ -25,7 +26,7 @@ public override void ParseSelf(XmlNode node) referenceName = node.InnerText.Trim(); } - public override object CreateInstance(object parent, Dictionary instanceRefs) + public override object CreateInstance(IWorld world, object parent, Dictionary instanceRefs) { if (instanceRefs.ContainsKey(referenceName)) { @@ -33,7 +34,7 @@ public override object CreateInstance(object parent, Dictionary } else { - return EntityLoader.GetDefinition(referenceName).CreateInstance(parent, instanceRefs); + return EntityLoader.GetDefinition(referenceName).CreateInstance(world, parent, instanceRefs); } } diff --git a/CorgEng.ContentLoading/DefinitionNodes/KeyNode.cs b/CorgEng.ContentLoading/DefinitionNodes/KeyNode.cs index 339fa6b1..30e2e36e 100644 --- a/CorgEng.ContentLoading/DefinitionNodes/KeyNode.cs +++ b/CorgEng.ContentLoading/DefinitionNodes/KeyNode.cs @@ -1,4 +1,5 @@ using CorgEng.GenericInterfaces.ContentLoading.DefinitionNodes; +using CorgEng.GenericInterfaces.EntityComponentSystem; using System; using System.Collections.Generic; using System.Linq; diff --git a/CorgEng.ContentLoading/DefinitionNodes/ObjectNode.cs b/CorgEng.ContentLoading/DefinitionNodes/ObjectNode.cs index fc515d02..81423ebf 100644 --- a/CorgEng.ContentLoading/DefinitionNodes/ObjectNode.cs +++ b/CorgEng.ContentLoading/DefinitionNodes/ObjectNode.cs @@ -1,4 +1,5 @@ using CorgEng.ContentLoading; +using CorgEng.GenericInterfaces.EntityComponentSystem; using System; using System.Collections.Generic; using System.ComponentModel; @@ -78,10 +79,10 @@ protected void ParseValue(XmlNode node) } } - public override object CreateInstance(object parent, Dictionary instanceRefs) + public override object CreateInstance(IWorld world, object parent, Dictionary instanceRefs) { //Create ourself - object createdObject = PropertyValue ?? Activator.CreateInstance(ObjectType); + object createdObject = PropertyValue ?? (typeof(WorldObject).IsAssignableFrom(ObjectType) ? Activator.CreateInstance(ObjectType, new object[] { world }) : Activator.CreateInstance(ObjectType)); //Store it if (Key != null) { @@ -90,7 +91,7 @@ public override object CreateInstance(object parent, Dictionary //Pass ourself to our children foreach (DefinitionNode childNode in Children) { - childNode.CreateInstance(createdObject, instanceRefs); + childNode.CreateInstance(world, createdObject, instanceRefs); } //Return the created object return createdObject; diff --git a/CorgEng.ContentLoading/DefinitionNodes/ParameterNode.cs b/CorgEng.ContentLoading/DefinitionNodes/ParameterNode.cs index 56465561..7900b6ea 100644 --- a/CorgEng.ContentLoading/DefinitionNodes/ParameterNode.cs +++ b/CorgEng.ContentLoading/DefinitionNodes/ParameterNode.cs @@ -1,4 +1,5 @@ using CorgEng.GenericInterfaces.ContentLoading.DefinitionNodes; +using CorgEng.GenericInterfaces.EntityComponentSystem; using System; using System.Collections.Generic; using System.Linq; @@ -13,9 +14,9 @@ public ParameterNode(DefinitionNode parent) : base(parent) { } - public override object CreateInstance(object parent, Dictionary instanceRefs) + public override object CreateInstance(IWorld world, object parent, Dictionary instanceRefs) { - object created = Children.First().CreateInstance(parent, instanceRefs); + object created = Children.First().CreateInstance(world, parent, instanceRefs); if (Key != null) { instanceRefs.Add(Key, created); diff --git a/CorgEng.ContentLoading/DefinitionNodes/PropertyNode.cs b/CorgEng.ContentLoading/DefinitionNodes/PropertyNode.cs index 0487095d..e651298f 100644 --- a/CorgEng.ContentLoading/DefinitionNodes/PropertyNode.cs +++ b/CorgEng.ContentLoading/DefinitionNodes/PropertyNode.cs @@ -1,5 +1,6 @@ using CorgEng.ContentLoading; using CorgEng.ContentLoading.DefinitionNodes; +using CorgEng.GenericInterfaces.EntityComponentSystem; using System; using System.Collections.Generic; using System.ComponentModel; @@ -70,7 +71,7 @@ protected void ParseValue(XmlNode node) } } - public override object CreateInstance(object parent, Dictionary instanceRefs) + public override object CreateInstance(IWorld world, object parent, Dictionary instanceRefs) { if (PropertyValue != null) { @@ -82,7 +83,7 @@ public override object CreateInstance(object parent, Dictionary } else { - object createdObject = Children[0].CreateInstance(null, instanceRefs); + object createdObject = Children[0].CreateInstance(world, null, instanceRefs); if (Key != null) { instanceRefs.Add(Key, createdObject); diff --git a/CorgEng.ContentLoading/DefinitionNodes/ValueNode.cs b/CorgEng.ContentLoading/DefinitionNodes/ValueNode.cs index c24083af..816ab172 100644 --- a/CorgEng.ContentLoading/DefinitionNodes/ValueNode.cs +++ b/CorgEng.ContentLoading/DefinitionNodes/ValueNode.cs @@ -1,4 +1,5 @@ using CorgEng.GenericInterfaces.ContentLoading.DefinitionNodes; +using CorgEng.GenericInterfaces.EntityComponentSystem; using System; using System.Collections.Generic; using System.Linq; diff --git a/CorgEng.ContentLoading/EntityCreator.cs b/CorgEng.ContentLoading/EntityCreator.cs index 302c30c7..75629b11 100644 --- a/CorgEng.ContentLoading/EntityCreator.cs +++ b/CorgEng.ContentLoading/EntityCreator.cs @@ -34,13 +34,13 @@ internal class EntityCreator : IEntityCreator /// /// /// - public IEntity CreateEntity(string entityName, Action preInitialisationEvents) + public IEntity CreateEntity(IWorld world, string entityName, Action preInitialisationEvents) { try { if (EntityNodesByName.ContainsKey(entityName)) { - IEntity createdEntity = EntityNodesByName[entityName].CreateEntity(preInitialisationEvents); + IEntity createdEntity = EntityNodesByName[entityName].CreateEntity(world, preInitialisationEvents); createdEntity.DefinitionName = entityName; return createdEntity; } @@ -60,13 +60,13 @@ public IEntity CreateEntity(string entityName, Action preInitialisation /// /// /// - public object CreateObject(string objectIdentifier) + public object CreateObject(IWorld world, string objectIdentifier) { try { if (EntityLoader.LoadedDefinitions.ContainsKey(objectIdentifier)) { - return EntityLoader.LoadedDefinitions[objectIdentifier].CreateInstance(null, new Dictionary()); + return EntityLoader.LoadedDefinitions[objectIdentifier].CreateInstance(world, null, new Dictionary()); } //Entity not found :( throw new Exception($"The object with name {objectIdentifier} could not be spawned as it doesn't exist."); diff --git a/CorgEng.Contents/Systems/ContentsSystem.cs b/CorgEng.Contents/Systems/ContentsSystem.cs index bd0641e3..76148cc6 100644 --- a/CorgEng.Contents/Systems/ContentsSystem.cs +++ b/CorgEng.Contents/Systems/ContentsSystem.cs @@ -28,7 +28,7 @@ internal class ContentsSystem : EntitySystem public override EntitySystemFlags SystemFlags => EntitySystemFlags.HOST_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(OnContainedEntityDeleted); RegisterLocalEvent(OnContentHolderDeleted); @@ -55,7 +55,7 @@ private void ExitContainer(IEntity entity, ContainedComponent containedComponent TransformComponent? locatedTransform = containedComponent.Parent.FindComponent(); if (locatedTransform != null) { - new SetPositionEvent(locatedTransform.Position).Raise(entity); + new SetPositionEvent(locatedTransform.Position.Value).Raise(entity); } //Raise the left contents event new ContentsChangedEvent(containedComponent.Parent, null).Raise(entity); diff --git a/CorgEng.Core/Content/Shaders/CoreShader/CoreShader.frag b/CorgEng.Core/Content/Shaders/CoreShader/CoreShader.frag index b8850a0f..85d58bc3 100644 --- a/CorgEng.Core/Content/Shaders/CoreShader/CoreShader.frag +++ b/CorgEng.Core/Content/Shaders/CoreShader/CoreShader.frag @@ -5,11 +5,15 @@ in vec2 fragUV; out vec4 result; uniform sampler2D renderTexture; +uniform sampler2D depthTexture; + +uniform float depthIncrement = 0; void main() { result = texture(renderTexture, fragUV); - result.r = result.r > 1 ? 0 : result.r; - result.g = result.g > 1 ? 0 : result.g; - result.b = result.b > 1 ? 0 : result.b; + float depthValue = texture(depthTexture, fragUV).r + depthIncrement; + gl_FragDepth = depthValue; + //result = texture(depthTexture, fragUV); + //result.a *= depthValue; } diff --git a/CorgEng.Core/Content/Shaders/DepthShader/DepthShader.frag b/CorgEng.Core/Content/Shaders/DepthShader/DepthShader.frag new file mode 100644 index 00000000..62184d90 --- /dev/null +++ b/CorgEng.Core/Content/Shaders/DepthShader/DepthShader.frag @@ -0,0 +1,13 @@ +#version 330 core + +in vec2 fragUV; + +out vec4 result; + +uniform sampler2D renderTexture; +uniform sampler2D depthTexture; + +void main() +{ + result.rgb = vec3(gl_FragCoord.z, gl_FragCoord.z, gl_FragCoord.z); +} diff --git a/CorgEng.Core/Content/Shaders/DepthShader/DepthShader.vert b/CorgEng.Core/Content/Shaders/DepthShader/DepthShader.vert new file mode 100644 index 00000000..c55684e5 --- /dev/null +++ b/CorgEng.Core/Content/Shaders/DepthShader/DepthShader.vert @@ -0,0 +1,7 @@ +#version 330 core +layout (location = 0) in vec3 inVertexPos; + +void main() +{ + gl_Position = vec4(inVertexPos, 1.0); +} diff --git a/CorgEng.Core/CorgEng.Core.csproj b/CorgEng.Core/CorgEng.Core.csproj index a9aaf6a5..2dfc246a 100644 --- a/CorgEng.Core/CorgEng.Core.csproj +++ b/CorgEng.Core/CorgEng.Core.csproj @@ -28,6 +28,12 @@ Always + + Always + + + Always + Always diff --git a/CorgEng.Core/CorgEng.Core.sln b/CorgEng.Core/CorgEng.Core.sln index af705004..cbf8b97e 100644 --- a/CorgEng.Core/CorgEng.Core.sln +++ b/CorgEng.Core/CorgEng.Core.sln @@ -34,8 +34,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CorgEng.ContentLoading", ". EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CorgEng.MapLoading", "..\CorgEng.MapLoading\CorgEng.MapLoading.csproj", "{B324FB97-A466-4E19-BA67-9536023EE5B9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoMap", "..\AutoMap\AutoMap.csproj", "{ADA9CC94-5705-499C-9C93-888E96F87DEA}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CorgEng.UserInterface", "..\CorgEng.UserInterface\CorgEng.UserInterface.csproj", "{19E048D9-1587-40CC-BEE3-E1883A21FEC8}" ProjectSection(ProjectDependencies) = postProject {BFC5583F-3A05-43E4-9D07-81D80B1EC685} = {BFC5583F-3A05-43E4-9D07-81D80B1EC685} @@ -77,6 +75,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CorgEng.Lighting", "..\Corg EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CorgEng.EntityQuery", "..\CorgEng.EntityQuery\CorgEng.EntityQuery.csproj", "{CD4E414F-536D-454B-8423-9631952EF705}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CorgEng.Functional", "..\CorgEng.Functional\CorgEng.Functional.csproj", "{0C23728A-E2A5-48A7-BF6F-C6EC865900D0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -331,26 +331,6 @@ Global {B324FB97-A466-4E19-BA67-9536023EE5B9}.Release|x64.Build.0 = Release|x64 {B324FB97-A466-4E19-BA67-9536023EE5B9}.Release|x86.ActiveCfg = Release|x86 {B324FB97-A466-4E19-BA67-9536023EE5B9}.Release|x86.Build.0 = Release|x86 - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Debug|ARM32.ActiveCfg = Debug|Any CPU - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Debug|ARM32.Build.0 = Debug|Any CPU - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Debug|ARM64.Build.0 = Debug|Any CPU - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Debug|x64.ActiveCfg = Debug|x64 - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Debug|x64.Build.0 = Debug|x64 - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Debug|x86.ActiveCfg = Debug|x86 - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Debug|x86.Build.0 = Debug|x86 - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Release|Any CPU.Build.0 = Release|Any CPU - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Release|ARM32.ActiveCfg = Release|Any CPU - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Release|ARM32.Build.0 = Release|Any CPU - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Release|ARM64.ActiveCfg = Release|Any CPU - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Release|ARM64.Build.0 = Release|Any CPU - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Release|x64.ActiveCfg = Release|x64 - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Release|x64.Build.0 = Release|x64 - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Release|x86.ActiveCfg = Release|x86 - {ADA9CC94-5705-499C-9C93-888E96F87DEA}.Release|x86.Build.0 = Release|x86 {19E048D9-1587-40CC-BEE3-E1883A21FEC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {19E048D9-1587-40CC-BEE3-E1883A21FEC8}.Debug|Any CPU.Build.0 = Debug|Any CPU {19E048D9-1587-40CC-BEE3-E1883A21FEC8}.Debug|ARM32.ActiveCfg = Debug|Any CPU @@ -671,6 +651,26 @@ Global {CD4E414F-536D-454B-8423-9631952EF705}.Release|x64.Build.0 = Release|Any CPU {CD4E414F-536D-454B-8423-9631952EF705}.Release|x86.ActiveCfg = Release|Any CPU {CD4E414F-536D-454B-8423-9631952EF705}.Release|x86.Build.0 = Release|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Debug|ARM32.ActiveCfg = Debug|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Debug|ARM32.Build.0 = Debug|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Debug|ARM64.Build.0 = Debug|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Debug|x64.ActiveCfg = Debug|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Debug|x64.Build.0 = Debug|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Debug|x86.ActiveCfg = Debug|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Debug|x86.Build.0 = Debug|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Release|Any CPU.Build.0 = Release|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Release|ARM32.ActiveCfg = Release|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Release|ARM32.Build.0 = Release|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Release|ARM64.ActiveCfg = Release|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Release|ARM64.Build.0 = Release|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Release|x64.ActiveCfg = Release|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Release|x64.Build.0 = Release|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Release|x86.ActiveCfg = Release|Any CPU + {0C23728A-E2A5-48A7-BF6F-C6EC865900D0}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CorgEng.Core/CorgEngMain.cs b/CorgEng.Core/CorgEngMain.cs index a43c4fa2..b0ab5967 100644 --- a/CorgEng.Core/CorgEngMain.cs +++ b/CorgEng.Core/CorgEngMain.cs @@ -4,6 +4,7 @@ using CorgEng.Core.Modules; using CorgEng.Core.Rendering; using CorgEng.Core.Rendering.Exceptions; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Logging; using CorgEng.GenericInterfaces.Rendering; using GLFW; @@ -14,6 +15,7 @@ using System.Linq; using System.Reflection; using System.Runtime.Loader; +using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; @@ -79,6 +81,24 @@ public static class CorgEngMain public static event Action OnReadyEvents = null; + private static IWorld primaryWorld = null; + + /// + /// The main world to use for the game for when one isn't accessible + /// + public static IWorld PrimaryWorld + { + get => primaryWorld; + set { + primaryWorld = value; + WorldInit(primaryWorld); + } + } + /// + /// All current active worlds + /// + public static ConcurrentBag WorldList { get; } = new ConcurrentBag(); + /// /// List of actions queued to be execuetd on the main thread /// @@ -92,6 +112,8 @@ public static void Initialize(bool disableRendering = false) { try { + // Reset the world list + WorldList.Clear(); //Load priority modules (Logging) PriorityModuleInit(); Logger?.WriteLine("Starting CorgEng Application", LogType.DEBUG); @@ -167,6 +189,8 @@ public static void TransferToRenderingThread() } } } + // Trigger any deferred rendering thread code + CheckQueuedExecutions(); //Swap the framebuffers GameWindow.SwapFramebuffers(); //Poll for system events to prevent the program from showing as hanging @@ -226,6 +250,8 @@ public static void Shutdown() TriggerTerminateMethods(); //Terminate GLFW Glfw.Terminate(); + // Reset the world bag + WorldList.Clear(); } /// @@ -270,7 +296,7 @@ public static void LoadConfig(string filePath, bool embeddedResource = true, boo //Unit test support. if (Assembly.GetCallingAssembly() != null && Assembly.GetCallingAssembly() != Assembly.GetEntryAssembly()) loadedAssemblies.Add(Assembly.GetCallingAssembly()); - if(Assembly.GetExecutingAssembly() != null) + if (Assembly.GetExecutingAssembly() != null) loadedAssemblies.Add(Assembly.GetExecutingAssembly()); foreach (XElement dependency in childElement.Elements()) { @@ -303,7 +329,7 @@ public static void LoadConfig(string filePath, bool embeddedResource = true, boo Console.Error.WriteLine("Please reinstall the program and ensure that if developing a CorgEng game, the config is set as an embedded resource."); Console.Error.WriteLine("The application will now be terminated."); Console.Error.WriteLine("Press any key to continue..."); - if(awaitOnError) + if (awaitOnError) Console.ReadKey(); throw; } @@ -333,10 +359,11 @@ private static void PriorityModuleInit() ModuleLoadAttributes = LoadedAssemblyModules .SelectMany(assembly => assembly.GetTypes() .SelectMany(type => type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static) - .Where(method => method.GetCustomAttribute() != null ))); - Parallel.ForEach(ModuleLoadAttributes, (MethodInfo) => { + .Where(method => method.GetCustomAttribute() != null))); + Parallel.ForEach(ModuleLoadAttributes, (MethodInfo) => + { Console.WriteLine(MethodInfo.Name); - if(MethodInfo.GetCustomAttribute().priority + if (MethodInfo.GetCustomAttribute().priority && !MethodInfo.GetCustomAttribute().mainThread) MethodInfo.Invoke(null, new object[] { }); }); @@ -353,7 +380,8 @@ private static void PriorityModuleInit() /// private static void ModuleInit() { - Parallel.ForEach(ModuleLoadAttributes, (MethodInfo) => { + Parallel.ForEach(ModuleLoadAttributes, (MethodInfo) => + { if (!MethodInfo.GetCustomAttribute().priority && !MethodInfo.GetCustomAttribute().mainThread) { @@ -370,6 +398,21 @@ private static void ModuleInit() ModuleLoadAttributes = null; } + /// + /// Calls priority method modules + /// + private static void WorldInit(IWorld world) + { + IEnumerable worldMethods = LoadedAssemblyModules + .SelectMany(assembly => assembly.GetTypes() + .SelectMany(type => type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static) + .Where(method => method.GetCustomAttribute() != null))); + foreach (MethodInfo methodToInvoke in worldMethods) + { + methodToInvoke.Invoke(null, new object[] { world }); + } + } + /// /// Triggers termination methods /// @@ -403,5 +446,113 @@ public static void ExecuteOnRenderingThread(Action action) } } + /// + /// The lowest time of the thing that we want to fire + /// + private static double nextBucketFireTime = double.PositiveInfinity; + + /// + /// A heap of the things that we want to execute on this thread and the + /// time of when we want to execute them. + /// + private static PriorityQueue executeInQueue = new PriorityQueue(); + + private static Thread headlessExecutionThread = null; + + /// + /// Add something to a bucket + /// + /// + /// The time to wait in milliseconds + public static void ExecuteIn(Action action, double executeTime) + { + if (!IsRendering && headlessExecutionThread == null) + { + // Lock something random so that we can only enter this once + lock (executeInQueue) + { + if (headlessExecutionThread == null) + { + // This is horrible + headlessExecutionThread = new Thread(() => { + while (!Terminated) + { + CheckQueuedExecutions(); + Thread.Yield(); + } + lock (executeInQueue) + { + headlessExecutionThread = null; + } + }); + headlessExecutionThread.Start(); + } + } + } + double timeToFire = Time + executeTime * 0.001; + Logger.WriteLine($"Action queued to fire at {timeToFire}", LogType.WARNING); + // 0 or negative execution time + if (timeToFire <= Time) + { + //Rendering thread exists, queue the action + queuedActions.Enqueue(action); + return; + } + // Queue the action to be fired + lock (executeInQueue) + { + nextBucketFireTime = Math.Min(nextBucketFireTime, timeToFire); + executeInQueue.Enqueue(action, timeToFire); + } + } + + /// + /// Check the queue of things that we want to fire on this thread and fire them if + /// we are ready for them. + /// + internal static void CheckQueuedExecutions() + { + if (nextBucketFireTime > Time) + return; + lock (executeInQueue) + { + while (nextBucketFireTime <= Time) + { + // Invoke the action + Action lowest = executeInQueue.Dequeue(); + //Logger.WriteLine($"Action invoked at {Time}. NEXT BUCKET TIME: {nextBucketFireTime}", LogType.WARNING); + lowest.Invoke(); + // Move to the next + if (executeInQueue.TryPeek(out Action _, out double nextFireTime)) + { + nextBucketFireTime = nextFireTime; + } + else + { + nextBucketFireTime = double.PositiveInfinity; + return; + } + } + } + } + + /** + * Fully cleanup the CorgEng application, resetting it into a state + * of startup. + */ + public static void Cleanup() + { + MainCamera = null; + primaryWorld = null; + foreach (IWorld world in WorldList) + { + world.Cleanup(); + } + WorldList.Clear(); + queuedActions.Clear(); + MainRenderCore = null; + Logger.WriteLine("Full cleanup of CorgEng application completed.", LogType.DEBUG); + } + } } diff --git a/CorgEng.Core/Modules/WorldInitialiseAttribute.cs b/CorgEng.Core/Modules/WorldInitialiseAttribute.cs new file mode 100644 index 00000000..75eacd45 --- /dev/null +++ b/CorgEng.Core/Modules/WorldInitialiseAttribute.cs @@ -0,0 +1,15 @@ +using JetBrains.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.Core.Modules +{ + [MeansImplicitUse] + [AttributeUsage(AttributeTargets.Method)] + public class WorldInitialiseAttribute : Attribute + { + } +} diff --git a/CorgEng.Core/Rendering/RenderCore.cs b/CorgEng.Core/Rendering/RenderCore.cs index 6d8966da..43b1687d 100644 --- a/CorgEng.Core/Rendering/RenderCore.cs +++ b/CorgEng.Core/Rendering/RenderCore.cs @@ -1,14 +1,16 @@ using CorgEng.Core.Dependencies; using CorgEng.GenericInterfaces.Core; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Logging; using CorgEng.GenericInterfaces.Rendering.Shaders; using CorgEng.GenericInterfaces.UtilityTypes; using System; +using System.Collections.Generic; using static OpenGL.Gl; namespace CorgEng.Core.Rendering { - public abstract class RenderCore : IRenderCore + public abstract class RenderCore : WorldObject, IRenderCore { [UsingDependency] @@ -38,8 +40,14 @@ public abstract class RenderCore : IRenderCore private static IShaderSet shaderSet; + private static IShaderSet depthShaderSet; + private static int textureUniformLocation; + private static int depthUniformLocation; + + private static int depthIncrementUniformLocation; + //Create a program for rendering private static uint programUint; @@ -58,9 +66,14 @@ public abstract class RenderCore : IRenderCore /// public uint RenderTextureUint { get; } - public int Width { get; internal set; } = 1920; + /// + /// The ID of the depth texture generated by rendering this render core. + /// + public uint DepthTextureUint { get; } - public int Height { get; internal set; } = 1080; + public virtual int Width { get; internal set; } = 1920; + + public virtual int Height { get; internal set; } = 1080; private static IColour _currentBackColour; private static DepthModes _currentDepthMode = DepthModes.KEEP_DEPTH; @@ -82,6 +95,16 @@ public abstract class RenderCore : IRenderCore /// public virtual DepthModes DepthMode { get; } = DepthModes.KEEP_DEPTH; + /// + /// The scaling mode of this render core. + /// + public virtual ScalingModes ScalingMode { get; } = ScalingModes.NEAREST_NEIGHBOUR; + + /// + /// If this is set to true, the render core will be allowed to be resized through the Resize() function. + /// + public virtual bool CanResize { get; } = true; + /// /// The path of the shaders to use. /// Only accessed during initialisation. @@ -90,7 +113,15 @@ public abstract class RenderCore : IRenderCore public virtual IColour BackColour { get; } = ColourFactory.GetColour(0, 0, 0, 0); - public unsafe RenderCore() + /// + /// How much depth should we add to this render core? + /// + public virtual float DepthAdd { get; } = 0; + + [UsingDependency] + private static IWorldFactory WorldFactory; + + public unsafe RenderCore(IWorld world) : base(world) { if (!CorgEngMain.IsRendering) return; @@ -111,10 +142,24 @@ public unsafe RenderCore() //Set the texture parameters glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - //Bind the render depth buffer to the framebuffer - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, RenderBufferUint); //Bind the framebuffer to the texture glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, RenderTextureUint, 0); + //glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, Width, Height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); + //Bind the render depth buffer to the framebuffer + //glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, RenderBufferUint); + //Bind the framebuffer to the texture + //glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, RenderTextureUint, 0); + //========================= + // Setup the depth texture + //========================= + glActiveTexture(GL_TEXTURE1); + DepthTextureUint = glGenTexture(); + glBindTexture(GL_TEXTURE_2D, DepthTextureUint); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, Width, Height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, RenderBufferUint); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, DepthTextureUint, 0); //Check for issues if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { @@ -157,7 +202,21 @@ public unsafe static void SetupRendering() glLinkProgram(programUint); //Fetch uniform locations textureUniformLocation = glGetUniformLocation(programUint, "renderTexture"); + depthUniformLocation = glGetUniformLocation(programUint, "depthTexture"); + depthIncrementUniformLocation = glGetUniformLocation(programUint, "depthIncrement"); + } + } + + private static Dictionary loadedShaders = new Dictionary(); + + private IShaderSet GetShader(string name) + { + if (!loadedShaders.ContainsKey(name)) + { + IShaderSet createdShader = ShaderFactory.CreateShaderSet(name); + loadedShaders.Add(name, createdShader); } + return loadedShaders[name]; } public void PreRender() @@ -173,12 +232,13 @@ public void PreRender() /// /// Do rendering /// - public void DoRender() + public void DoRender(Action? preRenderAction = null) { RenderModes prev = SwitchBlendMode(BlendMode); DepthModes prevDepth = SwitchDepthMode(DepthMode); IColour prevColour = SwitchBackColour(BackColour); PreRender(); + preRenderAction?.Invoke(); PerformRender(); SwitchBlendMode(prev); SwitchDepthMode(prevDepth); @@ -251,7 +311,7 @@ public unsafe void Resize(int width, int height) Height = height; //Log Logger?.WriteLine($"Render core resized to {Width}x{Height}", LogType.DEBUG); - if (!CorgEngMain.IsRendering) + if (!CorgEngMain.IsRendering || !CanResize) return; //Update the tex image //Bind the created texture so we can modify it @@ -259,14 +319,22 @@ public unsafe void Resize(int width, int height) //Load the texture scale glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); //Set the texture parameters - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ScalingMode == ScalingModes.NEAREST_NEIGHBOUR ? GL_NEAREST : GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ScalingMode == ScalingModes.NEAREST_NEIGHBOUR ? GL_NEAREST : GL_LINEAR); + // Bind the depth texture + glBindTexture(GL_TEXTURE_2D, DepthTextureUint); + //Load the texture scale + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, Width, Height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); + //Set the texture parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ScalingMode == ScalingModes.NEAREST_NEIGHBOUR ? GL_NEAREST : GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ScalingMode == ScalingModes.NEAREST_NEIGHBOUR ? GL_NEAREST : GL_LINEAR); } public unsafe void DrawToBuffer(uint buffer, int drawX, int drawY, int bufferWidth, int bufferHeight) { //Reset the framebuffer (We want to draw to the screen, not a frame buffer) glBindFramebuffer(GL_FRAMEBUFFER, buffer); + //glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, RenderBufferUint); //Draw to full screen glViewport(drawX, drawY, bufferWidth, bufferHeight); @@ -278,7 +346,9 @@ public unsafe void DrawToBuffer(uint buffer, int drawX, int drawY, int bufferWid //Set the using program to our program uint glUseProgram(programUint); //Bind uniform variables - glUniform1ui(textureUniformLocation, 0); + glUniform1i(textureUniformLocation, 0); + glUniform1i(depthUniformLocation, 1); + glUniform1f(depthIncrementUniformLocation, DepthAdd); //Bind the vertex buffer glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); @@ -295,6 +365,10 @@ public unsafe void DrawToBuffer(uint buffer, int drawX, int drawY, int bufferWid //Bind the texture glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, RenderTextureUint); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, DepthTextureUint); + //glBindRenderbuffer(RenderBufferUint); + //glBindTexture(GL_RENDERBUFFER, RenderBufferUint); //Draw glDrawArrays(GL_TRIANGLES, 0, 6); //Disable the vertex attrib array @@ -304,5 +378,6 @@ public unsafe void DrawToBuffer(uint buffer, int drawX, int drawY, int bufferWid SwitchDepthMode(prevBlend); SwitchBackColour(prevColour); } + } } diff --git a/CorgEng.Core/Rendering/ScalingModes.cs b/CorgEng.Core/Rendering/ScalingModes.cs new file mode 100644 index 00000000..82330f02 --- /dev/null +++ b/CorgEng.Core/Rendering/ScalingModes.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.Core.Rendering +{ + public enum ScalingModes + { + NEAREST_NEIGHBOUR, + LINEAR_SCALING, + } +} diff --git a/CorgEng.Debug/CorgEng.Debug.csproj b/CorgEng.Debug/CorgEng.Debug.csproj new file mode 100644 index 00000000..3fc0077e --- /dev/null +++ b/CorgEng.Debug/CorgEng.Debug.csproj @@ -0,0 +1,13 @@ + + + + net7.0 + enable + enable + + + + + + + diff --git a/CorgEng.Debug/RenderCores/DebugOverlayRenderCore.cs b/CorgEng.Debug/RenderCores/DebugOverlayRenderCore.cs new file mode 100644 index 00000000..b0a63d81 --- /dev/null +++ b/CorgEng.Debug/RenderCores/DebugOverlayRenderCore.cs @@ -0,0 +1,28 @@ +using CorgEng.Core.Rendering; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.Debug.RenderCores +{ + internal class DebugOverlayRenderCore : RenderCore + { + public DebugOverlayRenderCore(IWorld world) : base(world) + { + } + + public override void Initialize() + { + throw new NotImplementedException(); + } + + public override void PerformRender() + { + throw new NotImplementedException(); + } + + } +} diff --git a/CorgEng.DependencyInjection/Dependencies/NonDependencyAttribute.cs b/CorgEng.DependencyInjection/Dependencies/NonDependencyAttribute.cs new file mode 100644 index 00000000..f20ce4b1 --- /dev/null +++ b/CorgEng.DependencyInjection/Dependencies/NonDependencyAttribute.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.DependencyInjection.Dependencies +{ + /// + /// Indicdates that an interface should not be used + /// as a dependency and will cause dependency injection + /// to fail. This is used when an interface that was a dependency + /// before is no longer being used as a dependency. + /// + [AttributeUsage(AttributeTargets.Interface)] + internal class NonDependencyAttribute : Attribute + { + + public bool fail = true; + + public string message; + + } +} diff --git a/CorgEng.EntityComponentSystem/Components/Component.cs b/CorgEng.EntityComponentSystem/Components/Component.cs index f7b649fa..16d78ac8 100644 --- a/CorgEng.EntityComponentSystem/Components/Component.cs +++ b/CorgEng.EntityComponentSystem/Components/Component.cs @@ -1,4 +1,5 @@ using CorgEng.Core.Dependencies; +using CorgEng.EntityComponentSystem.Components.ComponentVariables; using CorgEng.EntityComponentSystem.Entities; using CorgEng.EntityComponentSystem.Events; using CorgEng.EntityComponentSystem.Events.Events; @@ -9,17 +10,22 @@ using CorgEng.GenericInterfaces.UtilityTypes; using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; using static CorgEng.EntityComponentSystem.Entities.Entity; using static CorgEng.EntityComponentSystem.Systems.EntitySystem; +using static CorgEng.GenericInterfaces.EntityComponentSystem.IEntity; +using static CorgEng.GenericInterfaces.EntityComponentSystem.IEntitySystemManager; namespace CorgEng.EntityComponentSystem.Components { + public abstract class Component : IInstantiatable, IVersionSynced, IComponent { [UsingDependency] private static ILogger Logger = null!; - + /// /// The parent of this component /// @@ -32,29 +38,63 @@ public abstract class Component : IInstantiatable, IVersionSynced, IComponent //TODO: This is very memory expensive as its stored on ALL component instances, when it kind of works per-component. private List componentInjectionLambdas = new List(); + private static Dictionary cvarTypeCache = new Dictionary(); + + public Component() + { + if (!cvarTypeCache.ContainsKey(GetType())) + { + lock (cvarTypeCache) + { + if (!cvarTypeCache.ContainsKey(GetType())) + { + cvarTypeCache.Add(GetType(), GetType() + .GetProperties() + .Where(x => typeof(IComponentVariable).IsAssignableFrom(x.PropertyType)) + .ToArray()); + } + } + } + foreach (PropertyInfo cvarProperty in cvarTypeCache[GetType()]) + { + IComponentVariable attachedCVar = (IComponentVariable)cvarProperty.GetValue(this); + attachedCVar.AssociateTo(this); + } + } + + /// + /// Super cool optimisation + /// + /// + private sealed class CompTemplate + where T : Component + { + internal static readonly T Template = Activator.CreateInstance(); + } + + /// + /// Get the template component so that signals can be registered to its CVars + /// + /// + /// + public static T GetTemplate() + where T : Component + { + return CompTemplate.Template; + } + /// /// Register existing signals when we are added /// to an entity. /// internal void OnComponentAdded(Entity parent) { - //Check if we have any registered signals - if (!EventManager.RegisteredEvents.ContainsKey(GetType())) - { - //Send the component added event - new ComponentAddedEvent(this).Raise(parent); - return; - } //Locate all event types we are listening for - foreach (Type eventType in EventManager.RegisteredEvents[GetType()]) + foreach (Type eventType in parent.world.EntitySystemManager.GetRegisteredEventTypes(GetType())) { EventComponentPair key = new EventComponentPair(eventType, GetType()); //Locate the monitoring system's callback handler - if (!RegisteredSystemSignalHandlers.ContainsKey(key)) - { - continue; - } - List systemEventHandlers = RegisteredSystemSignalHandlers[key]; + IEnumerable systemEventHandlers = parent.world.EntitySystemManager.GetRegisteredSystemEventHandlers(key); //Create a lambda function that injects this component and relays it to the system InternalSignalHandleDelegate componentInjectionLambda = (IEntity entity, IEvent signal, bool synchronous, string callingFile, string callingMember, int callingLine) => { foreach(SystemEventHandlerDelegate systemEventHandler in systemEventHandlers) @@ -90,17 +130,12 @@ internal void OnComponentRemoved(Entity parent) { //Raise component removed event. new ComponentRemovedEvent(this).Raise(parent); - //Check if we have any registered signals - if (!EventManager.RegisteredEvents.ContainsKey(GetType())) - return; //Locate all event types we are listening for - foreach (Type eventType in EventManager.RegisteredEvents[GetType()]) + foreach (Type eventType in parent.world.EntitySystemManager.GetRegisteredEventTypes(GetType())) { EventComponentPair key = new EventComponentPair(eventType, GetType()); //Locate the monitoring system's callback handler - if (!RegisteredSystemSignalHandlers.ContainsKey(key)) - continue; - List systemEventHandlers = RegisteredSystemSignalHandlers[key]; + IEnumerable systemEventHandlers = parent.world.EntitySystemManager.GetRegisteredSystemEventHandlers(key); //Start listening for this event if (parent.EventListeners == null) //Probably shouldn't happen { diff --git a/CorgEng.EntityComponentSystem/Components/ComponentExtensions.cs b/CorgEng.EntityComponentSystem/Components/ComponentSignalInjector.cs similarity index 60% rename from CorgEng.EntityComponentSystem/Components/ComponentExtensions.cs rename to CorgEng.EntityComponentSystem/Components/ComponentSignalInjector.cs index c7620963..6eb51ac7 100644 --- a/CorgEng.EntityComponentSystem/Components/ComponentExtensions.cs +++ b/CorgEng.EntityComponentSystem/Components/ComponentSignalInjector.cs @@ -12,41 +12,36 @@ using System.Threading.Tasks; using static CorgEng.EntityComponentSystem.Entities.Entity; using static CorgEng.EntityComponentSystem.Systems.EntitySystem; +using static CorgEng.GenericInterfaces.EntityComponentSystem.IEntity; +using static CorgEng.GenericInterfaces.EntityComponentSystem.IEntitySystemManager; namespace CorgEng.EntityComponentSystem.Components { - internal static class ComponentExtensions + internal class ComponentSignalInjector : WorldObject, IComponentSignalInjector { [UsingDependency] private static ILogger Logger; - private static ConcurrentDictionary> componentInjectionLambdas = new ConcurrentDictionary>(); + private ConcurrentDictionary> componentInjectionLambdas = new ConcurrentDictionary>(); + + public ComponentSignalInjector(IWorld world) : base(world) + { + } /// /// Register existing signals when we are added /// to an entity. /// - internal static void OnComponentAdded(this IComponent component, Entity parent) + public void OnComponentAdded(IComponent component, IEntity parent) { component.Parent = parent; - //Check if we have any registered signals - if (!EventManager.RegisteredEvents.ContainsKey(component.GetType())) - { - //Send the component added event - new ComponentAddedEvent(component).Raise(parent); - return; - } //Locate all event types we are listening for - foreach (Type eventType in EventManager.RegisteredEvents[component.GetType()]) + foreach (Type eventType in world.EntitySystemManager.GetRegisteredEventTypes(component.GetType())) { EventComponentPair key = new EventComponentPair(eventType, component.GetType()); //Locate the monitoring system's callback handler - if (!RegisteredSystemSignalHandlers.ContainsKey(key)) - { - continue; - } - List systemEventHandlers = RegisteredSystemSignalHandlers[key]; + List systemEventHandlers = world.EntitySystemManager.GetRegisteredSystemEventHandlers(key); //Create a lambda function that injects this component and relays it to the system InternalSignalHandleDelegate componentInjectionLambda = (IEntity entity, IEvent signal, bool synchronous, string callingFile, string callingMember, int callingLine) => { @@ -64,12 +59,7 @@ internal static void OnComponentAdded(this IComponent component, Entity parent) componentInjectionLambdas[component].Add(componentInjectionLambda); } //Start listening for this event - if (parent.EventListeners == null) - parent.EventListeners = new Dictionary>(); - if (parent.EventListeners.ContainsKey(eventType)) - parent.EventListeners[eventType].Add(componentInjectionLambda); - else - parent.EventListeners.Add(eventType, new List() { componentInjectionLambda }); + parent.AddEventListener(eventType, componentInjectionLambda); } //Send the component added event ComponentAddedEvent componentAddedEvent = new ComponentAddedEvent(component); @@ -81,7 +71,7 @@ internal static void OnComponentAdded(this IComponent component, Entity parent) /// Cleanup registered signals and remove any static references to the entity. /// Allow for safe garbage collection /// - internal static void OnComponentRemoved(this IComponent component, Entity parent, bool silent) + public void OnComponentRemoved(IComponent component, IEntity parent, bool silent) { component.Parent = null; //Raise component removed event. @@ -89,36 +79,23 @@ internal static void OnComponentRemoved(this IComponent component, Entity parent { new ComponentRemovedEvent(component).Raise(parent); } - //Check if we have any registered signals - if (!EventManager.RegisteredEvents.ContainsKey(component.GetType())) - return; //Locate all event types we are listening for - foreach (Type eventType in EventManager.RegisteredEvents[component.GetType()]) + foreach (Type eventType in world.EntitySystemManager.GetRegisteredEventTypes(component.GetType())) { EventComponentPair key = new EventComponentPair(eventType, component.GetType()); //Locate the monitoring system's callback handler - if (!RegisteredSystemSignalHandlers.ContainsKey(key)) - continue; - List systemEventHandlers = RegisteredSystemSignalHandlers[key]; - //Start listening for this event - if (parent.EventListeners == null) //Probably shouldn't happen - { - Logger.WriteLine($"Parent event listeners is null.", LogType.WARNING); - continue; - } + List systemEventHandlers = world.EntitySystemManager.GetRegisteredSystemEventHandlers(key); //Locate and removed - if (parent.EventListeners.ContainsKey(eventType)) + if (componentInjectionLambdas.ContainsKey(component)) { lock (componentInjectionLambdas[component]) { for (int i = componentInjectionLambdas[component].Count - 1; i >= 0; i--) { InternalSignalHandleDelegate signalHandleDelegate = componentInjectionLambdas[component][i]; - if (parent.EventListeners[eventType].Contains(signalHandleDelegate)) - { - parent.EventListeners[eventType].Remove(signalHandleDelegate); + parent.RemoveEventListenter(eventType, signalHandleDelegate); + if (componentInjectionLambdas.ContainsKey(component)) componentInjectionLambdas[component].Remove(signalHandleDelegate); - } } if (componentInjectionLambdas[component].Count == 0) { @@ -126,8 +103,6 @@ internal static void OnComponentRemoved(this IComponent component, Entity parent } } } - else - Logger.WriteLine($"Parent event listener isn't listening for {eventType}", LogType.WARNING); } } diff --git a/CorgEng.EntityComponentSystem/Components/ComponentVariables/CVar.cs b/CorgEng.EntityComponentSystem/Components/ComponentVariables/CVar.cs new file mode 100644 index 00000000..6217bfc6 --- /dev/null +++ b/CorgEng.EntityComponentSystem/Components/ComponentVariables/CVar.cs @@ -0,0 +1,42 @@ +using CorgEng.UtilityTypes.BindableProperties; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.EntityComponentSystem.Components.ComponentVariables +{ + /// + /// TODO: + /// - Automatic networking update + /// - Updating this variable on the proper threads + /// + /// + public class CVar : BindableProperty, IComponentVariable + { + + public Component Parent { get; set; } + + public CVar() : this(default) + { + } + + public CVar(TValueType value) : base(value) + { + // Determine our component type and prepare signal reaction handlers + } + + public void AssociateTo(Component component) + { + Parent = component; + } + + public CVar InitialValue(TValueType value) + { + Value = value; + return this; + } + + } +} diff --git a/CorgEng.EntityComponentSystem/Components/ComponentVariables/IComponentVariable.cs b/CorgEng.EntityComponentSystem/Components/ComponentVariables/IComponentVariable.cs new file mode 100644 index 00000000..d98293e0 --- /dev/null +++ b/CorgEng.EntityComponentSystem/Components/ComponentVariables/IComponentVariable.cs @@ -0,0 +1,21 @@ +using CorgEng.GenericInterfaces.EntityComponentSystem; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.EntityComponentSystem.Components.ComponentVariables +{ + internal interface IComponentVariable + { + + /// + /// Associate this component variable with a specific component. + /// Called via reflection. + /// + /// + void AssociateTo(Component component); + + } +} diff --git a/CorgEng.EntityComponentSystem/Components/ComponentVariables/Networking/INetVar.cs b/CorgEng.EntityComponentSystem/Components/ComponentVariables/Networking/INetVar.cs new file mode 100644 index 00000000..e37f3aa2 --- /dev/null +++ b/CorgEng.EntityComponentSystem/Components/ComponentVariables/Networking/INetVar.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.EntityComponentSystem.Components.ComponentVariables.Networking +{ + public interface INetVar + { + + Type GetStoredType(); + + object GetValue(); + + } +} diff --git a/CorgEng.EntityComponentSystem/Components/ComponentVariables/Networking/NetCVar.cs b/CorgEng.EntityComponentSystem/Components/ComponentVariables/Networking/NetCVar.cs new file mode 100644 index 00000000..70a2c617 --- /dev/null +++ b/CorgEng.EntityComponentSystem/Components/ComponentVariables/Networking/NetCVar.cs @@ -0,0 +1,132 @@ +using CorgEng.Core.Dependencies; +using CorgEng.GenericInterfaces.Logging; +using CorgEng.GenericInterfaces.Networking.Clients; +using CorgEng.GenericInterfaces.Networking.Config; +using CorgEng.GenericInterfaces.Networking.Serialisation; +using CorgEng.GenericInterfaces.Serialization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.EntityComponentSystem.Components.ComponentVariables.Networking +{ + + public static class NetVar + { + + [UsingDependency] + internal static INetworkConfig NetworkConfig; + + [UsingDependency] + internal static ILogger Logger; + + [UsingDependency] + internal static IAutoSerialiser AutoSerialiser; + + public static HashSet DirtyNetvars = new HashSet(); + } + + /** + * Networked version of a CVar + */ + public class NetCVar : CVar, INetVar, ICustomSerialisationBehaviour + { + + /// + /// The number of netvars active in the world, start from 1 as 0 represents an + /// uninitialised netvar. + /// + private static ulong _netVarCount = 1; + + public ulong NetVarID { get; private set; } + + public bool PrototypeSerialised { get; set; } + + public NetCVar() + { + if (NetVar.NetworkConfig == null || !NetVar.NetworkConfig.NetworkingActive) + throw new Exception("Attempted to initialise a netvar prior to network initialisation."); + if (!NetVar.NetworkConfig.ProcessServerSystems) + { + if (!NetVar.NetworkConfig.ProcessClientSystems) + throw new Exception("Attempted to initialise a netvar without active networking. This may result in issues."); + return; + } + // Set the NetVarID + NetVarID = _netVarCount++; + } + + public NetCVar(TValueType value) : base(value) + { + if (NetVar.NetworkConfig == null || !NetVar.NetworkConfig.NetworkingActive) + throw new Exception("Attempted to initialise a netvar prior to network initialisation."); + if (!NetVar.NetworkConfig.ProcessServerSystems) + { + if (!NetVar.NetworkConfig.ProcessClientSystems) + throw new Exception("Attempted to initialise a netvar without active networking. This may result in issues."); + return; + } + // Determine our component type and prepare signal reaction handlers + NetVarID = _netVarCount++; + } + + public override void TriggerChanged() + { + base.TriggerChanged(); + if (NetVar.NetworkConfig == null || !NetVar.NetworkConfig.NetworkingActive) + return; + MarkDirty(); + } + + public void MarkDirty() + { + if (NetVarID == 0) + throw new Exception("Attempting to mark an uninitialised NetVar as dirty."); + lock (NetVar.DirtyNetvars) + { + if (NetVar.DirtyNetvars.Contains(this)) + return; + NetVar.DirtyNetvars.Add(this); + } + } + + public NetCVar SetPrototypeSerialised(bool value) + { + PrototypeSerialised = value; + return this; + } + + public object GetValue() + { + if (NetVarID == 0) + throw new Exception("Attempting to access an uninitialised NetVar."); + return Value; + } + + public Type GetStoredType() + { + return typeof(TValueType); + } + + public int GetSerialisationLength() + { + return NetVar.AutoSerialiser.SerialisationLength(typeof(ulong), NetVarID) + + NetVar.AutoSerialiser.SerialisationLength(typeof(TValueType), Value); + } + + public void SerialiseInto(BinaryWriter binaryWriter) + { + NetVar.AutoSerialiser.SerializeInto(typeof(ulong), NetVarID, binaryWriter); + NetVar.AutoSerialiser.SerializeInto(typeof(TValueType), Value, binaryWriter); + } + + public void DeserialiseFrom(BinaryReader binaryReader) + { + NetVarID = (ulong)NetVar.AutoSerialiser.Deserialize(typeof(ulong), binaryReader); + Value = (TValueType)NetVar.AutoSerialiser.Deserialize(typeof(TValueType), binaryReader); + } + } +} diff --git a/CorgEng.EntityComponentSystem/CorgEng.EntityComponentSystem.csproj b/CorgEng.EntityComponentSystem/CorgEng.EntityComponentSystem.csproj index 2a6a070f..fe0ad3ae 100644 --- a/CorgEng.EntityComponentSystem/CorgEng.EntityComponentSystem.csproj +++ b/CorgEng.EntityComponentSystem/CorgEng.EntityComponentSystem.csproj @@ -22,7 +22,6 @@ - diff --git a/CorgEng.EntityComponentSystem/Entities/Entity.cs b/CorgEng.EntityComponentSystem/Entities/Entity.cs index 4ffeb131..06d3a466 100644 --- a/CorgEng.EntityComponentSystem/Entities/Entity.cs +++ b/CorgEng.EntityComponentSystem/Entities/Entity.cs @@ -3,22 +3,23 @@ using CorgEng.EntityComponentSystem.Events; using CorgEng.EntityComponentSystem.Events.Events; using CorgEng.EntityComponentSystem.Implementations.Deletion; +using CorgEng.EntityComponentSystem.Systems; using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Logging; using CorgEng.GenericInterfaces.UtilityTypes; +using CorgEng.UtilityTypes.Monads; using System; using System.Collections.Generic; using System.Reflection; using System.Threading; +using static CorgEng.GenericInterfaces.EntityComponentSystem.IEntity; namespace CorgEng.EntityComponentSystem.Entities { - public class Entity : IEntity + public class Entity : WorldObject, IEntity { - internal delegate void InternalSignalHandleDelegate(IEntity entity, IEvent signal, bool synchronous, string callingFile, string callingMember, int callingLine); - [UsingDependency] private static ILogger Logger; @@ -51,32 +52,32 @@ public class Entity : IEntity /// Components register themselves to this when needed, and this gets fired off to the component /// which then can fire off itself to the system. /// - internal Dictionary> EventListeners { get; set; } = null; + public Dictionary> EventListeners { get; set; } = null; /// /// Name of the entity definition /// public string DefinitionName { get; set; } - internal Entity() + internal Entity(IWorld world) : base(world) { - Identifier = EntityManager.GetNewEntityId(); - EntityManager.RegisterEntity(this); + Identifier = world.EntityManager.GetNewEntityId(); + world.EntityManager.RegisterEntity(this); //Entities are deletable by default AddComponent(new DeleteableComponent()); } - public Entity(uint identifier) + internal Entity(IWorld world, uint identifier) : base(world) { Identifier = identifier; - EntityManager.RegisterEntity(this); + world.EntityManager.RegisterEntity(this); //Entities are deletable by default AddComponent(new DeleteableComponent()); } ~Entity() { - Interlocked.Increment(ref EntityManager.GarbageCollectionCount); + Interlocked.Increment(ref ((EntityManager)world.EntityManager).GarbageCollectionCount); //Debug //Logger.WriteLine($"Entity GC'd. {EntityManager.GarbageCollectionCount}/{EntityManager.DeletionCount}", LogType.TEMP); } @@ -97,7 +98,7 @@ public void AddComponent(IComponent component) } #endif Components.Add(component); - component.OnComponentAdded(this); + world.ComponentSignalInjector.OnComponentAdded(component, this); } } @@ -109,7 +110,7 @@ public void RemoveComponent(IComponent component, bool networked, bool silent = { lock (Components) { - component.OnComponentRemoved(this, silent); + world.ComponentSignalInjector.OnComponentRemoved(component, this, silent); Components.Remove(component); } } @@ -200,5 +201,38 @@ public bool TryGetComponent(out T component) return false; } + public Result TryGetComponent() + { + //Get derived types too + lock (Components) + { + foreach (IComponent _component in Components) + { + if (_component is T componentAsT) + { + return new Result(componentAsT); + } + } + } + return new Failure(); + } + + public void AddEventListener(Type eventType, InternalSignalHandleDelegate eventHandler) + { + if (EventListeners == null) + EventListeners = new Dictionary>(); + if (EventListeners.ContainsKey(eventType)) + EventListeners[eventType].Add(eventHandler); + else + EventListeners.Add(eventType, new List() { eventHandler }); + } + + public void RemoveEventListenter(Type eventType, InternalSignalHandleDelegate eventHandler) + { + if (EventListeners[eventType].Contains(eventHandler)) + { + EventListeners[eventType].Remove(eventHandler); + } + } } } diff --git a/CorgEng.EntityComponentSystem/Entities/EntityFactory.cs b/CorgEng.EntityComponentSystem/Entities/EntityFactory.cs deleted file mode 100644 index f19ad5f0..00000000 --- a/CorgEng.EntityComponentSystem/Entities/EntityFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ -using CorgEng.DependencyInjection.Dependencies; -using CorgEng.EntityComponentSystem.Events; -using CorgEng.EntityComponentSystem.Events.Events; -using CorgEng.GenericInterfaces.EntityComponentSystem; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CorgEng.EntityComponentSystem.Entities -{ - [Dependency] - internal class EntityFactory : IEntityFactory - { - - public IEntity CreateEmptyEntity(Action preInitialisationEvents) - { - //Create the blank entity. - IEntity createdEntity = new Entity(); - //Run any events that need to happen before initialisation - preInitialisationEvents?.Invoke(createdEntity); - //Raise the initialise event - new InitialiseEvent().Raise(createdEntity, true); - //Return the entity that was created - return createdEntity; - } - - public IEntity CreateUninitialisedEntity() - { - return new Entity(); - } - } -} diff --git a/CorgEng.EntityComponentSystem/Entities/EntityManager.cs b/CorgEng.EntityComponentSystem/Entities/EntityManager.cs index 595f1af7..45da57c5 100644 --- a/CorgEng.EntityComponentSystem/Entities/EntityManager.cs +++ b/CorgEng.EntityComponentSystem/Entities/EntityManager.cs @@ -12,24 +12,31 @@ namespace CorgEng.EntityComponentSystem.Entities { - public static class EntityManager + public class EntityManager : IEntityManager { [UsingDependency] private static ILogger Logger; - private static IEntity[] entityList = new IEntity[1024]; + private IEntity[] entityList = new IEntity[1024]; - internal static int GarbageCollectionCount = 0; + internal int GarbageCollectionCount = 0; - internal static int DeletionCount = 0; + internal int DeletionCount = 0; /// /// Amount of created entities /// - private static uint CreatedEntityCount = 0; + private uint CreatedEntityCount = 0; - public static void RegisterEntity(IEntity entity) + private IWorld world; + + public EntityManager(IWorld world) + { + this.world = world; + } + + public void RegisterEntity(IEntity entity) { while (entityList.Length <= entity.Identifier) { @@ -40,20 +47,20 @@ public static void RegisterEntity(IEntity entity) } entityList[entity.Identifier] = entity; //Raise a new entity created event - new NewEntityEvent(entity.Identifier).RaiseGlobally(); + new NewEntityEvent(entity.Identifier).RaiseGlobally(world); } - public static uint GetNewEntityId() + public uint GetNewEntityId() { return CreatedEntityCount++; } - public static void RemoveEntity(IEntity entity) + public void RemoveEntity(IEntity entity) { entityList[entity.Identifier] = null; } - public static IEntity GetEntity(uint identifier) + public IEntity GetEntity(uint identifier) { if (identifier < 0 || identifier >= entityList.Length) return null; @@ -70,7 +77,7 @@ public static IEntity GetEntity(uint identifier) /// -> Delete() method /// -> Component local removal + EntityManager removal /// - internal static void Delete(this IEntity entity) + public void InternallyDelete(IEntity entity) { if ((entity.EntityFlags & EntityFlags.DESTROYED) != 0) { @@ -95,10 +102,31 @@ internal static void Delete(this IEntity entity) //Logger.WriteLine($"Entity deletion triggered. {GarbageCollectionCount}/{DeletionCount}", LogType.TEMP); } - public static IEntity[] GetEntityArrayUnsafe() + public IEntity[] GetEntityArrayUnsafe() { return entityList; } + public IEntity CreateEmptyEntity(Action preInitialisationEvents) + { + //Create the blank entity. + IEntity createdEntity = new Entity(world); + //Run any events that need to happen before initialisation + preInitialisationEvents?.Invoke(createdEntity); + //Raise the initialise event + new InitialiseEvent().Raise(createdEntity, true); + //Return the entity that was created + return createdEntity; + } + + public IEntity CreateUninitialisedEntity() + { + return new Entity(world); + } + + public IEntity CreateUninitialisedEntity(uint entityIdentifier) + { + return new Entity(world, entityIdentifier); + } } } diff --git a/CorgEng.EntityComponentSystem/Events/EventExtensions.cs b/CorgEng.EntityComponentSystem/Events/EventExtensions.cs index f2165161..cd061584 100644 --- a/CorgEng.EntityComponentSystem/Events/EventExtensions.cs +++ b/CorgEng.EntityComponentSystem/Events/EventExtensions.cs @@ -1,6 +1,7 @@ using CorgEng.Core.Dependencies; using CorgEng.EntityComponentSystem.Entities; using CorgEng.EntityComponentSystem.Events.Events; +using CorgEng.EntityComponentSystem.WorldManager; using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Logging; using CorgEng.GenericInterfaces.Networking.Config; @@ -14,6 +15,7 @@ using System.Threading.Tasks; using static CorgEng.EntityComponentSystem.Entities.Entity; using static CorgEng.EntityComponentSystem.Systems.EntitySystem; +using static CorgEng.GenericInterfaces.EntityComponentSystem.IEntitySystemManager; namespace CorgEng.EntityComponentSystem.Events { @@ -54,7 +56,6 @@ public static void Raise( [CallerMemberName] string callingMember = "", [CallerLineNumber] int callingLine = 0) { - Logger.WriteMetric("event_raised", signal.ToString()); //Handle the signal target.HandleSignal(signal, synchronous, callingFile, callingMember, callingLine); //Inform the entity that networked event was raised @@ -69,24 +70,18 @@ public static void Raise( /// public static void RaiseGlobally( this IEvent signal, + IWorld world, bool synchronous = false, [CallerFilePath] string callingFile = "", [CallerMemberName] string callingMember = "", [CallerLineNumber] int callingLine = 0) { - Logger.WriteMetric("global_event_raised", signal.ToString()); - //Check if we have any registered signals - if (!EventManager.RegisteredEvents.ContainsKey(typeof(GlobalEventComponent))) - return; //Locate all event types we are listening for EventComponentPair key = new EventComponentPair(signal.GetType(), typeof(GlobalEventComponent)); //Locate the monitoring system's callback handler - if (RegisteredSystemSignalHandlers.ContainsKey(key)) - { - List systemEventHandlers = RegisteredSystemSignalHandlers[key]; - for (int i = systemEventHandlers.Count - 1; i >= 0; i--) - systemEventHandlers[i].Invoke(null, null, signal, synchronous, callingFile, callingMember, callingLine); - } + List systemEventHandlers = world.EntitySystemManager.GetRegisteredSystemEventHandlers(key); + for (int i = systemEventHandlers.Count - 1; i >= 0; i--) + systemEventHandlers[i].Invoke(null, null, signal, synchronous, callingFile, callingMember, callingLine); } /// @@ -94,6 +89,7 @@ public static void RaiseGlobally( /// public static void RaiseGlobally( this INetworkedEvent signal, + IWorld world, bool sourcedLocally = true, bool synchronous = false, [CallerFilePath] string callingFile = "", @@ -101,18 +97,12 @@ public static void RaiseGlobally( [CallerLineNumber] int callingLine = 0) { Logger.WriteMetric("global_event_raised", signal.ToString()); - //Check if we have any registered signals - if (!EventManager.RegisteredEvents.ContainsKey(typeof(GlobalEventComponent))) - return; //Locate all event types we are listening for EventComponentPair key = new EventComponentPair(signal.GetType(), typeof(GlobalEventComponent)); //Locate the monitoring system's callback handler - if (RegisteredSystemSignalHandlers.ContainsKey(key)) - { - List systemEventHandlers = RegisteredSystemSignalHandlers[key]; - for (int i = systemEventHandlers.Count - 1; i >= 0; i--) - systemEventHandlers[i].Invoke(null, null, signal, synchronous, callingFile, callingMember, callingLine); - } + List systemEventHandlers = world.EntitySystemManager.GetRegisteredSystemEventHandlers(key); + for (int i = systemEventHandlers.Count - 1; i >= 0; i--) + systemEventHandlers[i].Invoke(null, null, signal, synchronous, callingFile, callingMember, callingLine); //Don't relay messages coming from other clients already if (!sourcedLocally) return; @@ -124,11 +114,7 @@ public static void RaiseGlobally( //Locate all event types we are listening for EventComponentPair networkKey = new EventComponentPair(typeof(NetworkedEventRaisedEvent), typeof(GlobalEventComponent)); //Locate the monitoring system's callback handler - if (!RegisteredSystemSignalHandlers.ContainsKey(networkKey)) - { - return; - } - List networkedEventHandlers = RegisteredSystemSignalHandlers[networkKey]; + List networkedEventHandlers = world.EntitySystemManager.GetRegisteredSystemEventHandlers(networkKey); for (int i = networkedEventHandlers.Count - 1; i >= 0; i--) networkedEventHandlers[i].Invoke(null, null, new NetworkedEventRaisedEvent(signal), synchronous, callingFile, callingMember, callingLine); } diff --git a/CorgEng.EntityComponentSystem/Events/EventManager.cs b/CorgEng.EntityComponentSystem/Events/EventManager.cs deleted file mode 100644 index 9c6aa102..00000000 --- a/CorgEng.EntityComponentSystem/Events/EventManager.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace CorgEng.EntityComponentSystem.Events -{ - internal static class EventManager - { - - /// - /// Matches component type to types of registered events - /// - internal static Dictionary> RegisteredEvents = new Dictionary>(); - - } -} diff --git a/CorgEng.EntityComponentSystem/Implementations/Deletion/EntityDeletionSystem.cs b/CorgEng.EntityComponentSystem/Implementations/Deletion/EntityDeletionSystem.cs index 9e7ddfd0..33e4c25f 100644 --- a/CorgEng.EntityComponentSystem/Implementations/Deletion/EntityDeletionSystem.cs +++ b/CorgEng.EntityComponentSystem/Implementations/Deletion/EntityDeletionSystem.cs @@ -17,7 +17,7 @@ internal class EntityDeletionSystem : EntitySystem //entities when requested. public override EntitySystemFlags SystemFlags { get; } = EntitySystemFlags.HOST_SYSTEM | EntitySystemFlags.CLIENT_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(OnEntityDeleted); } @@ -25,7 +25,7 @@ public override void SystemSetup() private void OnEntityDeleted(IEntity entity, DeleteableComponent deleteableComponent, DeleteEntityEvent entityDeletedEvent) { //Delete the entity - entity.Delete(); + world.EntityManager.InternallyDelete(entity); } } diff --git a/CorgEng.EntityComponentSystem/Implementations/Initialisation/EntityInitialisationSystem.cs b/CorgEng.EntityComponentSystem/Implementations/Initialisation/EntityInitialisationSystem.cs index 31902436..793696f6 100644 --- a/CorgEng.EntityComponentSystem/Implementations/Initialisation/EntityInitialisationSystem.cs +++ b/CorgEng.EntityComponentSystem/Implementations/Initialisation/EntityInitialisationSystem.cs @@ -16,7 +16,7 @@ internal class EntityInitialisationSystem : EntitySystem public override EntitySystemFlags SystemFlags => EntitySystemFlags.HOST_SYSTEM | EntitySystemFlags.CLIENT_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(OnInitialisation); } diff --git a/CorgEng.EntityComponentSystem/Implementations/Transform/MoveEvent.cs b/CorgEng.EntityComponentSystem/Implementations/Transform/MoveEvent.cs index 4ebbabb8..43069851 100644 --- a/CorgEng.EntityComponentSystem/Implementations/Transform/MoveEvent.cs +++ b/CorgEng.EntityComponentSystem/Implementations/Transform/MoveEvent.cs @@ -10,6 +10,7 @@ namespace CorgEng.EntityComponentSystem.Implementations.Transform { + [Obsolete("MoveEvent has been replaced by CVar events.", error:false)] public class MoveEvent : INetworkedEvent { diff --git a/CorgEng.EntityComponentSystem/Implementations/Transform/RotationEvent.cs b/CorgEng.EntityComponentSystem/Implementations/Transform/RotationEvent.cs index 43179a86..270f4816 100644 --- a/CorgEng.EntityComponentSystem/Implementations/Transform/RotationEvent.cs +++ b/CorgEng.EntityComponentSystem/Implementations/Transform/RotationEvent.cs @@ -9,7 +9,7 @@ namespace CorgEng.EntityComponentSystem.Implementations.Transform { - internal class RotationEvent : INetworkedEvent + public class RotationEvent : INetworkedEvent { public bool CanBeRaisedByClient => false; diff --git a/CorgEng.EntityComponentSystem/Implementations/Transform/TransformComponent.cs b/CorgEng.EntityComponentSystem/Implementations/Transform/TransformComponent.cs index 954ad2a9..a889fcd7 100644 --- a/CorgEng.EntityComponentSystem/Implementations/Transform/TransformComponent.cs +++ b/CorgEng.EntityComponentSystem/Implementations/Transform/TransformComponent.cs @@ -1,4 +1,5 @@ using CorgEng.EntityComponentSystem.Components; +using CorgEng.EntityComponentSystem.Components.ComponentVariables.Networking; using CorgEng.GenericInterfaces.ContentLoading; using CorgEng.GenericInterfaces.Networking.Attributes; using CorgEng.UtilityTypes.Vectors; @@ -28,13 +29,15 @@ public TransformComponent() : base("_world") /// Start with a zero value /// [NetworkSerialized(prototypeInclude = false)] - public Vector Position { get; internal set; } = new Vector(0, 0); + public NetCVar> Position { get; internal set; } = new NetCVar>(new Vector(0, 0)) + .SetPrototypeSerialised(false); /// /// Rotation property of this transform /// [NetworkSerialized(prototypeInclude = false)] - public Vector Rotation { get; internal set; } = new Vector(0); + public NetCVar> Rotation { get; internal set; } = new NetCVar>(new Vector(0)) + .SetPrototypeSerialised(false); public override TransformComponent Transform => this; diff --git a/CorgEng.EntityComponentSystem/Implementations/Transform/TransformSystem.cs b/CorgEng.EntityComponentSystem/Implementations/Transform/TransformSystem.cs index dcdc31a5..81fddffa 100644 --- a/CorgEng.EntityComponentSystem/Implementations/Transform/TransformSystem.cs +++ b/CorgEng.EntityComponentSystem/Implementations/Transform/TransformSystem.cs @@ -22,13 +22,13 @@ public sealed class TransformSystem : EntitySystem [UsingDependency] private static ILogger Logger; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(SetEntityPosition); RegisterLocalEvent(TranslateEntity); RegisterLocalEvent(SetEntityRotation); RegisterLocalEvent((e, c, s) => { - c.Position = s.NewPosition; + c.Position.Value = s.NewPosition; }); } @@ -40,20 +40,20 @@ public override void SystemSetup() /// private void SetEntityRotation(IEntity entity, TransformComponent transform, SetRotationEvent setRotationEvent) { - RotationEvent rotationEvent = new RotationEvent(transform.Rotation, setRotationEvent.NewRotation); + RotationEvent rotationEvent = new RotationEvent(transform.Rotation.Value, setRotationEvent.NewRotation); //Update the rotation of the transform component - transform.Rotation = setRotationEvent.NewRotation; + transform.Rotation.Value = setRotationEvent.NewRotation; //Raise a rotationEvent rotationEvent.Raise(entity); } private void TranslateEntity(IEntity entity, TransformComponent transform, TranslateEvent translationEvent) { - Vector newPosition = transform.Position + translationEvent.TranslationDelta; + Vector newPosition = transform.Position.Value + translationEvent.TranslationDelta; //Create a new on move event - MoveEvent moveEvent = new MoveEvent(transform.Position, newPosition); + MoveEvent moveEvent = new MoveEvent(transform.Position.Value, newPosition); //Update the position of the transform - transform.Position = newPosition; + transform.Position.Value = newPosition; //Trigger the on move event moveEvent.Raise(entity); } @@ -61,9 +61,9 @@ private void TranslateEntity(IEntity entity, TransformComponent transform, Trans private void SetEntityPosition(IEntity entity, TransformComponent transform, SetPositionEvent setPositionEvent) { //Create a new on move event - MoveEvent moveEvent = new MoveEvent(transform.Position, setPositionEvent.NewPosition); + MoveEvent moveEvent = new MoveEvent(transform.Position.Value, setPositionEvent.NewPosition); //Update the position of the transform - transform.Position = setPositionEvent.NewPosition; + transform.Position.Value = setPositionEvent.NewPosition; //Trigger the on move event moveEvent.Raise(entity); } diff --git a/CorgEng.EntityComponentSystem/Systems/EntitySystem.cs b/CorgEng.EntityComponentSystem/Systems/EntitySystem.cs index 01146322..66be1183 100644 --- a/CorgEng.EntityComponentSystem/Systems/EntitySystem.cs +++ b/CorgEng.EntityComponentSystem/Systems/EntitySystem.cs @@ -12,16 +12,23 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.Metrics; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using static CorgEng.GenericInterfaces.EntityComponentSystem.IEntitySystemManager; namespace CorgEng.EntityComponentSystem.Systems { public abstract class EntitySystem { + public static int EntitySystemCount = 0; + + private string name; + /// /// Internal global event component specifically for handling global signals /// @@ -38,36 +45,11 @@ internal class GlobalEventComponent : Component [UsingDependency] protected static INetworkConfig NetworkConfig; - internal delegate void SystemEventHandlerDelegate(IEntity entity, IComponent component, IEvent signal, bool synchronous, string file, string member, int lineNumber); - - /// - /// Matches event and component types to registered signal handlers on systems - /// - internal static Dictionary> RegisteredSystemSignalHandlers { get; } = new Dictionary>(); - - /// - /// Wait handler. This will cause the thread to pause until an event that needs handling is raised. - /// - protected readonly AutoResetEvent waitHandle = new AutoResetEvent(false); - - protected volatile bool isWaiting = false; - - /// - /// A queue of actions that other parts of the code are waiting for the action's completion. - /// These will be execution before anything in the invokation queue. - /// - protected readonly ConcurrentQueue priorityInvokationQueue = new ConcurrentQueue(); - /// /// The invokation queue. A queue of actions that need to be triggered (Raised events) /// protected readonly ConcurrentQueue invokationQueue = new ConcurrentQueue(); - /// - /// A static list of all entity systems in use - /// - private static ConcurrentDictionary EntitySystems = new ConcurrentDictionary(); - /// /// The flags of this system /// @@ -79,117 +61,219 @@ internal class GlobalEventComponent : Component protected bool assassinated = false; /// - /// has setup been completed? + /// The world that we belong to /// - public static bool SetupCompleted = false; + protected IWorld world; /// - /// Actions to run after setup + /// The thread manager flags. /// - public static event Action postSetupAction; + internal ThreadManagerFlags threadManagerFlags; + + private EntitySystemThreadManager threadManager; + + internal bool calledFromProcess = false; public EntitySystem() { - Task task = new Task(SystemThread); - //task.Name = $"{this} thread"; - task.Start(); + name = $"{GetType().Name}-{Interlocked.Increment(ref EntitySystemCount)}"; + } + + public void JoinWorld(IWorld world) + { + this.world = world; //Register the global signal to handle closing the game RegisterGlobalEvent((GameClosedEvent e) => { }); } - public abstract void SystemSetup(); + public void JoinEntitySystemManager(EntitySystemThreadManager threadManager) + { + this.threadManager = threadManager; + // Queue the system for a single fire, in case it is a processing system + QueueProcessing(); + } + + public abstract void SystemSetup(IWorld world); + + /// + /// Amount of runs we should perform before stopping + /// + private int tickRunsRemaining = 0; /// - /// Called when the ECS module is loaded. - /// Creates all System types and tracks the to prevent GC + /// The system thread. Waits until an invokation is required and then triggers it + /// on the system's thread. + /// Returns true if the run was completed, returns false if the system should be + /// requeued for further processing. /// - [ModuleLoad] - private static void CreateAllSystems() + public virtual bool PerformRun(EntitySystemThreadManager threadManager) { - Logger?.WriteLine($"Setting up systems...", LogType.LOG); - //Locate all system types using reflection. - //Note that we need all systems in all loaded modules - IEnumerable locatedSystems = CorgEngMain.LoadedAssemblyModules - .SelectMany(assembly => assembly.GetTypes() - .Where(type => typeof(EntitySystem).IsAssignableFrom(type) && !type.IsAbstract)); - locatedSystems.Select((type) => + // Refresh the tick runs that we need to + if (tickRunsRemaining <= 0) + { + tickRunsRemaining = invokationQueue.Count; + } + while (!invokationQueue.IsEmpty && tickRunsRemaining-- > 0) + { + InvokationAction firstInvokation; + invokationQueue.TryDequeue(out firstInvokation); + if (firstInvokation != null) { - Logger?.WriteLine($"Initializing {type}...", LogType.LOG); - EntitySystem createdSystem = (EntitySystem)type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.HasThis, new Type[0], null).Invoke(new object[0]); - EntitySystems.TryAdd(createdSystem.GetType(), createdSystem); - return createdSystem; - }) - // Very important that we fully resolve the enumerator and don't evaluate it lazilly - .ToList() - // Now do the foreach after they have all been created - .ForEach(entitySystem => entitySystem.SystemSetup()); - SetupCompleted = true; - // Run post-setup actions - postSetupAction?.Invoke(); - postSetupAction = null; - //Trigger the event when this is all done and loaded - CorgEngMain.OnReadyEvents += () => { - new GameReadyEvent().RaiseGlobally(); - }; - Logger?.WriteLine($"Successfully created and setup all systems!", LogType.LOG); + try + { + //Invoke the provided action + firstInvokation.Action(); + } + catch (Exception e) + { + Logger?.WriteLine($"Event Called From: {firstInvokation.CallingMemberName}:{firstInvokation.CallingLineNumber} in {firstInvokation.CallingFile}\n{e}", LogType.ERROR); + } + } + // Check to see if someone else is doing something more important than us + if (CheckRelinquishControl()) + { + return false; + } + } + return invokationQueue.IsEmpty; } + private object _controlLockObject = new object(); + /// - /// Gets a specific entity system + /// Check if we should relinquish control of this system to another source. + /// This will happen if we are a low priority processing thread and a high priority + /// lock has been requested. /// - /// /// - public static T GetSingleton() + internal bool CheckRelinquishControl() { - return (T)(object)EntitySystems[typeof(T)]; + if ((threadManagerFlags & ThreadManagerFlags.REQUESTED_HIGH) == ThreadManagerFlags.REQUESTED_HIGH) + { + // Release our thread manager lock + lock (this) + { + threadManagerFlags &= ~ThreadManagerFlags.LOCKED_LOW; + lock (_controlLockObject) + { + Monitor.PulseAll(_controlLockObject); + } + } + return true; + } + return false; } - [ModuleTerminate] - private static void TerminateSubsystems() + /// + /// Try to acquire a low priority lock for this processing system. + /// Returns false if we were unable to acquire the lock due to this system + /// already processing. + /// + /// + internal bool TryAcquireLowPriorityLock() { - new GameClosedEvent().RaiseGlobally(); + lock (this) + { + if ((threadManagerFlags & (ThreadManagerFlags.LOCKED_HIGH | ThreadManagerFlags.LOCKED_LOW | ThreadManagerFlags.REQUESTED_HIGH)) != 0) + return false; + threadManagerFlags |= ThreadManagerFlags.LOCKED_LOW; + return true; + } } /// - /// The system thread. Waits until an invokation is required and then triggers it - /// on the system's thread. + /// Release the lock currently held by this thread. + /// Ownership of the lock is not enforced, I trust the internal + /// code to actually have ownership of a lock. /// - protected virtual void SystemThread() + public void ReleaseLock() { - while (!CorgEngMain.Terminated && !assassinated) + lock (this) { - //Wait until we are awoken again - if (invokationQueue.Count == 0 && priorityInvokationQueue.Count == 0) + threadManagerFlags &= ~ThreadManagerFlags.LOCKED_LOW; + threadManagerFlags &= ~ThreadManagerFlags.LOCKED_HIGH; + // If we aren't requested, then add ourselves to the processing queue + if ((threadManagerFlags & (ThreadManagerFlags.REQUESTED_HIGH)) == 0) { - isWaiting = true; - //Protection from concurrency dangers - if (invokationQueue.Count == 0 && priorityInvokationQueue.Count == 0) - { - waitHandle.WaitOne(); - } - isWaiting = false; + QueueProcessing(); } - InvokationAction firstInvokation; - if (priorityInvokationQueue.Count != 0) - priorityInvokationQueue.TryDequeue(out firstInvokation); + } + lock (_controlLockObject) + { + Monitor.PulseAll(_controlLockObject); + } + } + + internal void ReleaseInternalLock() + { + lock (this) + { + threadManagerFlags &= ~ThreadManagerFlags.LOCKED_LOW; + } + lock (_controlLockObject) + { + Monitor.PulseAll(_controlLockObject); + } + } + + /// + /// Acquire a high priority lock. This action is blocking + /// until the lock is acquired. + /// Always returns true. + /// + public bool AcquireHighPriorityLock() + { + // Request access control + bool isControlled = false; + lock (this) + { + isControlled = (threadManagerFlags & (ThreadManagerFlags.LOCKED_LOW | ThreadManagerFlags.LOCKED_HIGH)) != 0; + if (isControlled) + threadManagerFlags |= ThreadManagerFlags.REQUESTED_HIGH; else - invokationQueue.TryDequeue(out firstInvokation); - if (firstInvokation != null) + threadManagerFlags |= ThreadManagerFlags.LOCKED_HIGH; + } + while (isControlled) + { + // Wait until the thread manager flags are changed + lock (_controlLockObject) { - try - { - //Invoke the provided action - firstInvokation.Action(); - } - catch (Exception e) + // Add the flag to indicate that we want to claim the system + lock (this) { - Logger?.WriteLine($"Event Called From: {firstInvokation.CallingMemberName}:{firstInvokation.CallingLineNumber} in {firstInvokation.CallingFile}\n{e}", LogType.ERROR); + // Check to see if we are controlled + isControlled = (threadManagerFlags & (ThreadManagerFlags.LOCKED_LOW | ThreadManagerFlags.LOCKED_HIGH)) != 0; + if (!isControlled) + { + // Claim the system and release our request + threadManagerFlags |= ThreadManagerFlags.LOCKED_HIGH; + threadManagerFlags &= ~ThreadManagerFlags.REQUESTED_HIGH; + return true; + } + // Request high priority + threadManagerFlags |= ThreadManagerFlags.REQUESTED_HIGH; } + Monitor.Wait(_controlLockObject); } } - //Terminated - Logger?.WriteLine($"Terminated EntitySystem thread: {this}", LogType.LOG); + // We now have a high priority lock over this system + return true; + } + + /// + /// Queue the entity system to process if it isn't already queued to process. + /// + internal void QueueProcessing() + { + lock (this) + { + // If the system is not queued, queue it. + if ((threadManagerFlags & ThreadManagerFlags.QUEUED_SYSTEM) != 0) + return; + threadManagerFlags |= ThreadManagerFlags.QUEUED_SYSTEM; + threadManager.EnqueueSystemForProcessing(this); + } } /// @@ -200,8 +284,7 @@ public void Kill() //Effective assassinated = true; //Tell the system to process its death - if (isWaiting) - waitHandle.Set(); + QueueProcessing(); } private Dictionary _globalLinkedHandlers = new Dictionary(); @@ -214,61 +297,58 @@ public void Kill() public void RegisterGlobalEvent(Action eventHandler) where GEvent : IEvent { - //Register the component to recieve the target event on the event manager - lock (EventManager.RegisteredEvents) - { - if (!EventManager.RegisteredEvents.ContainsKey(typeof(GlobalEventComponent))) - EventManager.RegisteredEvents.Add(typeof(GlobalEventComponent), new List()); - if (!EventManager.RegisteredEvents[typeof(GlobalEventComponent)].Contains(typeof(GEvent))) - EventManager.RegisteredEvents[typeof(GlobalEventComponent)].Add(typeof(GEvent)); - } + world.EntitySystemManager.RegisterEventType(typeof(GlobalEventComponent), typeof(GEvent)); //Register the system to receieve the event EventComponentPair eventComponentPair = new EventComponentPair(typeof(GEvent), typeof(GlobalEventComponent)); - lock (RegisteredSystemSignalHandlers) + // Everything inside this is an extremely hot path since its the game engine's function call. + // This should be pretty cheap (although it probably isn't very cheap right now). + SystemEventHandlerDelegate createdEventHandler = (IEntity entity, IComponent component, IEvent signal, bool synchronous, string file, string member, int lineNumber) => { - if (!RegisteredSystemSignalHandlers.ContainsKey(eventComponentPair)) - RegisteredSystemSignalHandlers.Add(eventComponentPair, new List()); - SystemEventHandlerDelegate createdEventHandler = (IEntity entity, IComponent component, IEvent signal, bool synchronous, string file, string member, int lineNumber) => + //Check to see if we were processing when the event was fired + if (NetworkConfig != null + && NetworkConfig.NetworkingActive + && ((SystemFlags & EntitySystemFlags.HOST_SYSTEM) == 0 || !NetworkConfig.ProcessServerSystems) + && ((SystemFlags & EntitySystemFlags.CLIENT_SYSTEM) == 0 || !NetworkConfig.ProcessClientSystems)) + return; + // Attempt to gain ownership of the system + // There is a potential issue here: Synchronous event deadlock. + if ((synchronous && AcquireHighPriorityLock()) || ((SystemFlags & EntitySystemFlags.NO_IMMEDIATE_ASYNCHRONOUS_EVENTS) == 0 && TryAcquireLowPriorityLock())) { - ConcurrentQueue targetQueue = synchronous ? priorityInvokationQueue : invokationQueue; - AutoResetEvent synchronousWaitEvent = synchronous ? new AutoResetEvent(false) : null; + // Fire the event on the current thread + try + { + // Now that the current thread has the processing key, we can immediately invoke the requested action + eventHandler.Invoke((GEvent)signal); + } + catch (Exception e) + { + Logger?.WriteLine($"Event Called (and fired immediately) from: {Environment.StackTrace}\n{e}", LogType.ERROR); + } + finally + { + // Ensure that we definitely release this lock + ReleaseLock(); + } + } + else + { + // If this is not a synchronous event, just queue the event + ConcurrentQueue targetQueue = invokationQueue; targetQueue.Enqueue(new InvokationAction(() => { - try - { - //Check if we don't process - if (NetworkConfig != null - && NetworkConfig.NetworkingActive - && ((SystemFlags & EntitySystemFlags.HOST_SYSTEM) == 0 || !NetworkConfig.ProcessServerSystems) - && ((SystemFlags & EntitySystemFlags.CLIENT_SYSTEM) == 0 || !NetworkConfig.ProcessClientSystems)) - return; - eventHandler.Invoke((GEvent)signal); - } - finally - { - //If we were synchronous, indicate that the queuer can continue - if (synchronous) - { - synchronousWaitEvent.Set(); - Thread.Yield(); - } - } + eventHandler.Invoke((GEvent)signal); }, file, member, lineNumber)); //Wake up the system if its sleeping - if (isWaiting) - waitHandle.Set(); - //If this event is synchronous, wait for completion - if (synchronous) - synchronousWaitEvent.WaitOne(); - }; - lock (_globalLinkedHandlers) - { - if (_globalLinkedHandlers.ContainsKey(eventHandler)) - throw new Exception("Attempting to register a global signal handler that is already registered to this system."); - _globalLinkedHandlers.Add(eventHandler, createdEventHandler); + QueueProcessing(); } - RegisteredSystemSignalHandlers[eventComponentPair].Add(createdEventHandler); + }; + lock (_globalLinkedHandlers) + { + if (_globalLinkedHandlers.ContainsKey(eventHandler)) + throw new Exception("Attempting to register a global signal handler that is already registered to this system."); + _globalLinkedHandlers.Add(eventHandler, createdEventHandler); } + world.EntitySystemManager.RegisterSystemEventHandler(eventComponentPair, createdEventHandler); } /// @@ -280,28 +360,16 @@ public void RegisterGlobalEvent(Action eventHandler) public void UnregisterGlobalEvent(Action eventHandler) where GEvent : IEvent { - //Register the component to recieve the target event on the event manager - lock (EventManager.RegisteredEvents) - { - if (!EventManager.RegisteredEvents.ContainsKey(typeof(GlobalEventComponent))) - throw new Exception("Attempted to unregister an event that was not present on the target entity system. (Component is not registered, are you using the right generic types?)"); - if (!EventManager.RegisteredEvents[typeof(GlobalEventComponent)].Contains(typeof(GEvent))) - throw new Exception("Attempted to unregister an event that was not present on the target entity system. (Event was not registered, are you using the right generic types?)"); - } + world.EntitySystemManager.UnregisterEventType(typeof(GlobalEventComponent), typeof(GEvent)); //Register the system to receieve the event EventComponentPair eventComponentPair = new EventComponentPair(typeof(GEvent), typeof(GlobalEventComponent)); - lock (RegisteredSystemSignalHandlers) + //Hnadle unregistering + lock (_globalLinkedHandlers) { - if (!RegisteredSystemSignalHandlers.ContainsKey(eventComponentPair)) - RegisteredSystemSignalHandlers.Add(eventComponentPair, new List()); - //Hnadle unregistering - lock (_globalLinkedHandlers) - { - if (!_globalLinkedHandlers.ContainsKey(eventHandler)) - throw new Exception("Attempting to unregister a global event handler that isn't currently registered."); - RegisteredSystemSignalHandlers[eventComponentPair].Remove(_globalLinkedHandlers[eventHandler]); - _globalLinkedHandlers.Remove(eventHandler); - } + if (!_globalLinkedHandlers.ContainsKey(eventHandler)) + throw new Exception("Attempting to unregister a global event handler that isn't currently registered."); + world.EntitySystemManager.UnregisterSystemEventHandler(eventComponentPair, _globalLinkedHandlers[eventHandler]); + _globalLinkedHandlers.Remove(eventHandler); } } @@ -328,64 +396,57 @@ public void RegisterLocalEvent(Action()); - if (!EventManager.RegisteredEvents[typeToRegister].Contains(typeof(GEvent))) - EventManager.RegisteredEvents[typeToRegister].Add(typeof(GEvent)); - } + world.EntitySystemManager.RegisterEventType(typeToRegister, typeof(GEvent)); //Register the system to receieve the event EventComponentPair eventComponentPair = new EventComponentPair(typeof(GEvent), typeToRegister); - lock (RegisteredSystemSignalHandlers) + //Create and return an event handler so that it can be + createdEventHandler = (IEntity entity, IComponent component, IEvent signal, bool synchronous, string callingFile, string callingMember, int callingLine) => { - if (!RegisteredSystemSignalHandlers.ContainsKey(eventComponentPair)) - RegisteredSystemSignalHandlers.Add(eventComponentPair, new List()); - //Create and return an event handler so that it can be - createdEventHandler = (IEntity entity, IComponent component, IEvent signal, bool synchronous, string callingFile, string callingMember, int callingLine) => + //Check to see if we were processing when the event was fired + if (NetworkConfig != null + && NetworkConfig.NetworkingActive + && ((SystemFlags & EntitySystemFlags.HOST_SYSTEM) == 0 || !NetworkConfig.ProcessServerSystems) + && ((SystemFlags & EntitySystemFlags.CLIENT_SYSTEM) == 0 || !NetworkConfig.ProcessClientSystems)) + return; + // Attempt to gain ownership of the system + // There is a potential issue here: Synchronous event deadlock. + if ((synchronous && AcquireHighPriorityLock()) || ((SystemFlags & EntitySystemFlags.NO_IMMEDIATE_ASYNCHRONOUS_EVENTS) == 0 && TryAcquireLowPriorityLock())) + { + // Fire the event on the current thread + try + { + // Now that the current thread has the processing key, we can immediately invoke the requested action + eventHandler.Invoke(entity, (GComponent)component, (GEvent)signal); + } + catch (Exception e) + { + Logger?.WriteLine($"Event Called (and fired immediately) from: {Environment.StackTrace}\n{e}", LogType.ERROR); + } + finally + { + // Ensure that we definitely release this lock + ReleaseLock(); + } + } + else { - //Check if we don't process - if (NetworkConfig != null - && NetworkConfig.NetworkingActive - && ((SystemFlags & EntitySystemFlags.HOST_SYSTEM) == 0 || !NetworkConfig.ProcessServerSystems) - && ((SystemFlags & EntitySystemFlags.CLIENT_SYSTEM) == 0 || !NetworkConfig.ProcessClientSystems)) - return; - ConcurrentQueue targetQueue = synchronous ? priorityInvokationQueue : invokationQueue; - AutoResetEvent synchronousWaitEvent = synchronous ? new AutoResetEvent(false) : null; + // If this is not a synchronous event, just queue the event + ConcurrentQueue targetQueue = invokationQueue; targetQueue.Enqueue(new InvokationAction(() => { - try - { - eventHandler(entity, (GComponent)component, (GEvent)signal); - } - catch (Exception e) - { - throw new Exception($"An exception occurred handling the signal type {typeof(GEvent)} registered on component {typeof(GComponent)} at system {GetType()}.", e); - } - finally - { - //If we were synchronous, indicate that the queuer can continue - if (synchronous) - { - synchronousWaitEvent.Set(); - } - } + eventHandler.Invoke(entity, (GComponent)component, (GEvent)signal); }, callingFile, callingMember, callingLine)); - if (isWaiting) - waitHandle.Set(); - //If this event is synchronous, wait for completion - if (synchronous) - synchronousWaitEvent.WaitOne(); - }; - lock (_linkedHandlers) - { - if (_linkedHandlers.ContainsKey((typeToRegister, eventHandler))) - throw new Exception("Attempting to register an event that is already registered."); - _linkedHandlers.Add((typeToRegister, eventHandler), createdEventHandler); + // Wake up the system if its sleeping + QueueProcessing(); } - RegisteredSystemSignalHandlers[eventComponentPair].Add(createdEventHandler); + }; + lock (_linkedHandlers) + { + if (_linkedHandlers.ContainsKey((typeToRegister, eventHandler))) + throw new Exception("Attempting to register an event that is already registered."); + _linkedHandlers.Add((typeToRegister, eventHandler), createdEventHandler); } + world.EntitySystemManager.RegisterSystemEventHandler(eventComponentPair, createdEventHandler); } } @@ -409,34 +470,39 @@ public void UnregisterLocalEvent(Action + /// Queues an action for execution, which will be executed on the correct thread for this system. + /// + /// + /// + /// + /// + public void QueueAction(Action action, + [CallerFilePath] string callingFile = "", + [CallerMemberName] string callingMember = "", + [CallerLineNumber] int callingLine = 0) + { + invokationQueue.Enqueue(new InvokationAction(action, callingFile, callingMember, callingLine)); + QueueProcessing(); + } + + public override string ToString() + { + return name; + } } } diff --git a/CorgEng.EntityComponentSystem/Systems/EntitySystemFlags.cs b/CorgEng.EntityComponentSystem/Systems/EntitySystemFlags.cs index 1433a814..df322815 100644 --- a/CorgEng.EntityComponentSystem/Systems/EntitySystemFlags.cs +++ b/CorgEng.EntityComponentSystem/Systems/EntitySystemFlags.cs @@ -12,5 +12,7 @@ public enum EntitySystemFlags HOST_SYSTEM = 1 << 0, //Does the entity system run while the client is running? CLIENT_SYSTEM = 1 << 1, + //Force asynchronous events to be multi-threaded even if we can execute them immediately + NO_IMMEDIATE_ASYNCHRONOUS_EVENTS = 1 << 2, } } diff --git a/CorgEng.EntityComponentSystem/Systems/EntitySystemManager.cs b/CorgEng.EntityComponentSystem/Systems/EntitySystemManager.cs new file mode 100644 index 00000000..e191e46a --- /dev/null +++ b/CorgEng.EntityComponentSystem/Systems/EntitySystemManager.cs @@ -0,0 +1,196 @@ +using CorgEng.Core.Modules; +using CorgEng.Core; +using CorgEng.GenericInterfaces.Logging; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using CorgEng.Core.Dependencies; +using CorgEng.EntityComponentSystem.Events; +using CorgEng.EntityComponentSystem.Events.Events; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using static CorgEng.EntityComponentSystem.Systems.EntitySystem; +using static CorgEng.GenericInterfaces.EntityComponentSystem.IEntitySystemManager; + +namespace CorgEng.EntityComponentSystem.Systems +{ + + internal class EntitySystemManager : IEntitySystemManager + { + + [UsingDependency] + protected static ILogger Logger; + + /// + /// A static list of all entity systems in use + /// + public ConcurrentDictionary EntitySystems { get; } + = new ConcurrentDictionary(); + + /// + /// has setup been completed? + /// + public bool SetupCompleted { get; private set; } = false; + + /// + /// Actions to run after setup + /// + public event Action postSetupAction; + + /// + /// Matches component type to types of registered events + /// + internal Dictionary> RegisteredEvents = new Dictionary>(); + + /// + /// Matches event and component types to registered signal handlers on systems + /// + internal Dictionary> RegisteredSystemSignalHandlers { get; } = new Dictionary>(); + + private EntitySystemThreadManager entitySystemThreadManager = new EntitySystemThreadManager(4); + + private IWorld world; + + public EntitySystemManager(IWorld world) + { + this.world = world; + } + + /// + /// Called when the attached world process is created. + /// Creates all System types and tracks the to prevent GC + /// + internal void CreateAllSystems() + { + Logger?.WriteLine($"Setting up systems...", LogType.LOG); + //Locate all system types using reflection. + //Note that we need all systems in all loaded modules + IEnumerable locatedSystems = CorgEngMain.LoadedAssemblyModules + .SelectMany(assembly => assembly.GetTypes() + .Where(type => typeof(EntitySystem).IsAssignableFrom(type) && !type.IsAbstract)); + int setupAmount = 0; + locatedSystems.Select((type) => + { + Logger?.WriteLine($"Initializing {type}...", LogType.LOG); + EntitySystem createdSystem = (EntitySystem)type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.HasThis, new Type[0], null).Invoke(new object[0]); + createdSystem.JoinWorld(world); + createdSystem.JoinEntitySystemManager(entitySystemThreadManager); + EntitySystems.TryAdd(createdSystem.GetType(), createdSystem); + return createdSystem; + }) + // Very important that we fully resolve the enumerator and don't evaluate it lazilly + .ToList() + // Now do the foreach after they have all been created + .ForEach(entitySystem => + { + setupAmount++; + entitySystem.SystemSetup(world); + }); + SetupCompleted = true; + // Run post-setup actions + postSetupAction?.Invoke(); + postSetupAction = null; + //Trigger the event when this is all done and loaded + CorgEngMain.OnReadyEvents += () => + { + new GameReadyEvent().RaiseGlobally(world); + }; + Logger?.WriteLine($"Successfully created and setup {setupAmount} systems!", LogType.LOG); + } + + /// + /// Gets a specific entity system + /// + /// + /// + public T GetSingleton() + { + return (T)(object)EntitySystems[typeof(T)]; + } + + /// + /// Shut down all subsystems that are running inside this entity system + /// manager. + /// + internal void TerminateSubsystems() + { + new GameClosedEvent().RaiseGlobally(world); + } + + public void RegisterEventType(Type componentType, Type eventType) + { + lock (RegisteredEvents) + { + if (!RegisteredEvents.ContainsKey(componentType)) + RegisteredEvents.Add(componentType, new List()); + if (!RegisteredEvents[componentType].Contains(eventType)) + RegisteredEvents[componentType].Add(eventType); + } + } + + //TODO: Does this work? + //TODO: Create unit tests for the expected behaviour of unregister event type. + public void UnregisterEventType(Type componentType, Type eventType) + { + lock (RegisteredEvents) + { + if (!RegisteredEvents.ContainsKey(componentType)) + throw new Exception("Attempted to unregister an event that was not present on the target entity system. (Component is not registered, are you using the right generic types?)"); + if (!RegisteredEvents[componentType].Contains(eventType)) + throw new Exception("Attempted to unregister an event that was not present on the target entity system. (Event was not registered, are you using the right generic types?)"); + } + } + + public IEnumerable GetRegisteredEventTypes(Type componentType) + { + lock (RegisteredEvents) + { + if (RegisteredEvents.ContainsKey(componentType)) + return RegisteredEvents[componentType]; + } + return Enumerable.Empty(); + } + + public void RegisterSystemEventHandler(EventComponentPair registeredEventComponentType, SystemEventHandlerDelegate eventAction) + { + lock (RegisteredSystemSignalHandlers) + { + if (!RegisteredSystemSignalHandlers.ContainsKey(registeredEventComponentType)) + RegisteredSystemSignalHandlers.Add(registeredEventComponentType, new List()); + RegisteredSystemSignalHandlers[registeredEventComponentType].Add(eventAction); + } + } + + public void UnregisterSystemEventHandler(EventComponentPair registeredEventComponentType, SystemEventHandlerDelegate eventAction) + { + lock (RegisteredSystemSignalHandlers) + { + RegisteredSystemSignalHandlers[registeredEventComponentType].Remove(eventAction); + if (RegisteredSystemSignalHandlers[registeredEventComponentType].Count == 0) + { + RegisteredSystemSignalHandlers.Remove(registeredEventComponentType); + } + } + } + + public List GetRegisteredSystemEventHandlers(EventComponentPair registeredEventComponentType) + { + lock (RegisteredSystemSignalHandlers) + { + if (!RegisteredSystemSignalHandlers.ContainsKey(registeredEventComponentType)) + return new List(); + return RegisteredSystemSignalHandlers[registeredEventComponentType]; + } + } + + public void Cleanup() + { + TerminateSubsystems(); + EntitySystems.Clear(); + entitySystemThreadManager.Cleanup(); + } + } +} diff --git a/CorgEng.EntityComponentSystem/Systems/EntitySystemThreadManager.cs b/CorgEng.EntityComponentSystem/Systems/EntitySystemThreadManager.cs new file mode 100644 index 00000000..99ccdc1c --- /dev/null +++ b/CorgEng.EntityComponentSystem/Systems/EntitySystemThreadManager.cs @@ -0,0 +1,201 @@ +using CorgEng.Core; +using CorgEng.Core.Dependencies; +using CorgEng.Core.Modules; +using CorgEng.GenericInterfaces.Logging; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CorgEng.EntityComponentSystem.Systems +{ + public enum ThreadManagerFlags + { + QUEUED_SYSTEM = 1 << 0, + LOCKED_LOW = 1 << 1, + REQUESTED_HIGH = 1 << 2, + LOCKED_HIGH = 1 << 3, + } + + public class EntitySystemThreadManager + { + + private static ConcurrentBag activeBags = new ConcurrentBag(); + + [UsingDependency] + private static ILogger Logger; + + private EntitySystemThread[] runningWorkers; + + internal bool working = true; + + internal ConcurrentQueue queuedSystems = new ConcurrentQueue(); + + internal ConcurrentQueue sleepingThreads = new ConcurrentQueue(); + + public EntitySystemThreadManager(int workerThreads) + { + if (workerThreads < 1) + { + workerThreads = 1; + } + List threadNumbers = new List(); + runningWorkers = new EntitySystemThread[workerThreads]; + for (int i = 0; i < workerThreads; i++) + { + runningWorkers[i] = new EntitySystemThread(this, i); + threadNumbers.Add("Thread: " + runningWorkers[i].thread.ManagedThreadId); + } + Logger.WriteLine($"Entity system thread manager created with {workerThreads} maximum threads. Thread IDs: {string.Join(", ", threadNumbers)}", LogType.DEBUG); + activeBags.Add(this); + } + + [ModuleTerminate] + public static void ShutdownEntitySystemManagers() + { + lock (activeBags) + { + foreach (EntitySystemThreadManager thread in activeBags) + { + thread.working = false; + } + activeBags.Clear(); + } + } + + public void Cleanup() + { + lock (activeBags) + { + working = false; + } + } + + /// + /// Enqueue a system for processing. + /// + /// + public void EnqueueSystemForProcessing(EntitySystem system) + { + queuedSystems.Enqueue(system); + // Wake up a single thread to handle this + if (sleepingThreads.TryDequeue(out EntitySystemThread result)) + { + result.WakeUp(); + } + } + + public void FireSystemIn(EntitySystem system, double fireTime) + { + CorgEngMain.ExecuteIn(() => { + Logger.WriteLine($"Attempting to queue system {system} for processing fire at {CorgEngMain.Time}", LogType.TEMP); + system.QueueProcessing(); + }, fireTime); + } + + } + + public class EntitySystemThread + { + + [UsingDependency] + private static ILogger Logger; + + public int identifier; + + private bool isRunning = false; + + private EntitySystemThreadManager threadManager; + + internal Thread thread; + + public EntitySystemThread(EntitySystemThreadManager threadManager, int identifier) + { + this.identifier = identifier; + this.threadManager = threadManager; + thread = new Thread(WorkerThread); + thread.Start(); + WakeUp(); + } + + /// + /// Attempt to wakeup the entity system thread. + /// + public void WakeUp() + { + lock (this) + { + if (isRunning) + return; + Monitor.Pulse(this); + isRunning = true; + //Logger.WriteLine($"Entity System Thread {identifier} waking up...", LogType.DEBUG); + } + } + + /// + /// The entity system worker thread. + /// Will process any subsystems that are not currently + /// owned by another process. + /// The thread will terminate once this is completed. + /// + private void WorkerThread() + { + try + { + while (threadManager.working) + { + // Begin working through the systems + while (threadManager.queuedSystems.TryDequeue(out EntitySystem entitySystem)) + { + lock (entitySystem) + { + entitySystem.threadManagerFlags &= ~ThreadManagerFlags.QUEUED_SYSTEM; + if (!entitySystem.TryAcquireLowPriorityLock()) + { + // We will be re-queued when the lock is released. + continue; + } + } + try + { + entitySystem.calledFromProcess = true; + if (!entitySystem.PerformRun(threadManager)) + { + // Requeue the system for further processing + entitySystem.QueueProcessing(); + } + } + // Release our low priority lock at the end without side effects. + finally + { + entitySystem.calledFromProcess = false; + entitySystem.ReleaseInternalLock(); + } + } + // Check if we were requested to wakeup while trying to sleep + if (threadManager.queuedSystems.IsEmpty) + { + lock (this) + { + // We can spin down now + isRunning = false; + threadManager.sleepingThreads.Enqueue(this); + // Wait until we are woken up again + //Logger.WriteLine($"Entity System Thread {identifier} entering sleep mode...", LogType.DEBUG); + Monitor.Wait(this); + } + } + } + } + catch (Exception e) + { + Logger.WriteLine($"FATAL EXCEPTION: THREAD MANAGER CRASHED\n{e}.", LogType.ERROR); + } + } + + } +} diff --git a/CorgEng.EntityComponentSystem/Systems/ProcessingSystem.cs b/CorgEng.EntityComponentSystem/Systems/ProcessingSystem.cs index 3c3b83bd..a3007db2 100644 --- a/CorgEng.EntityComponentSystem/Systems/ProcessingSystem.cs +++ b/CorgEng.EntityComponentSystem/Systems/ProcessingSystem.cs @@ -1,4 +1,6 @@ -using CorgEng.Core; +//#define PROCESSING_SYSTEM_DEBUG + +using CorgEng.Core; using CorgEng.Core.Dependencies; using CorgEng.EntityComponentSystem.Events.Events; using CorgEng.EntityComponentSystem.Implementations.Deletion; @@ -9,6 +11,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace CorgEng.EntityComponentSystem.Systems @@ -53,18 +56,66 @@ public abstract class ProcessingSystem : EntitySystem /// public static double DeltaTimeMultiplier { get; set; } = 1; - private ConcurrentQueue WorkingQueue - { - get => priorityInvokationQueue.Count > 0 ? priorityInvokationQueue : invokationQueue; - } + private int tickRunsRemaining = 0; + private bool isRunningProcesses = false; + + private double nextProcessTime; /// - /// Override initial behaviour to also be able to handle processing at regular intervals + /// Override initial behaviour to also be able to handle processing at regular intervals. + /// This works slightly differently as it needs to allow for adequate time to process + /// the things it needs to process while still letting signals pass. /// - protected override void SystemThread() + public override bool PerformRun(EntitySystemThreadManager threadManager) { - while (!CorgEngMain.Terminated && !assassinated) + if (!isRunningProcesses) + { + // Refresh the tick runs that we need to + if (tickRunsRemaining <= 0) + { + tickRunsRemaining = invokationQueue.Count; + } + // Run the processes that we need to + while (invokationQueue.Count > 0 && tickRunsRemaining-- > 0) + { + InvokationAction firstInvokation; + invokationQueue.TryDequeue(out firstInvokation); + if (firstInvokation != null) + { + try + { + //Invoke the provided action + firstInvokation.Action(); + } + catch (Exception e) + { + Logger?.WriteLine($"Event Called From: {firstInvokation.CallingMemberName}:{firstInvokation.CallingLineNumber} in {firstInvokation.CallingFile}\n{e}", LogType.ERROR); + } + } + // Check to see if someone else is doing something more important than us + if (CheckRelinquishControl()) + { + return false; + } + } + // Move to the next step (processes) + isRunningProcesses = true; + } + if (isRunningProcesses) { + // We are not ready to re-fire at this time + if (!continued && nextProcessTime > CorgEngMain.Time) + { + isRunningProcesses = false; +#if PROCESSING_SYSTEM_DEBUG + Logger.WriteLine($"{this} is not ready to fire, fire at: {nextProcessTime}, current time: {CorgEngMain.Time}", LogType.DEBUG); + if (calledFromProcess) + { + Logger.WriteLine($"wtf, we were claled from runningProcess::: {this}", LogType.TEMP); + } +#endif + return invokationQueue.IsEmpty; + } //If we aren't continued, fetch the new queue if (!continued) { @@ -73,6 +124,9 @@ protected override void SystemThread() deltaTime = CorgEngMain.Time - lastFireTime; //Mark the last fire time as now lastFireTime = CorgEngMain.Time; +#if PROCESSING_SYSTEM_DEBUG + Logger.WriteLine($"Firing {ToString()} processing system at {CorgEngMain.Time} (Delay: {ProcessDelay}ms)", LogType.DEBUG); +#endif } //Mark the system as being completed, unless we have to break out early continued = false; @@ -92,47 +146,34 @@ protected override void SystemThread() { Logger.WriteLine(e, LogType.ERROR); } - //Check to see if a new signal that needs to be handled has come in - if (WorkingQueue.Count > 0) + // Check to see if someone else is doing something more important than us + if (CheckRelinquishControl()) { - //We have an invokation to handle, we will continue processing on the next cycle round continued = true; - break; - } - } - //Allocate time to processing signal handles - InvokationAction current; - while (WorkingQueue.TryDequeue(out current)) - { - try - { - current.Action(); - } - catch (Exception e) - { - Logger?.WriteLine($"Event Called From: {current.CallingMemberName}:{current.CallingLineNumber} in {current.CallingFile}\n{e}", LogType.ERROR); + return false; } } - //If we completed processing, wait for the specified time, or until another signal handler comes in - //If a signal comes in when we loop round, we will go straight back to handling signals again - if (!continued) + // Move to the first step again + isRunningProcesses = false; + //Calculate how long we have to wait for, based on the last fire time, our current time + //and the system wait time. + //Stay in sync! if this value is less than 0, we are overtiming. + int waitTime = (int)Math.Max(1000 * (lastFireTime - CorgEngMain.Time) + ProcessDelay, 0); + nextProcessTime = CorgEngMain.Time + (waitTime * 0.001); +#if PROCESSING_SYSTEM_DEBUG + Logger.WriteLine($"{ToString()} queued to fire in {waitTime}ms. It should fire at {nextProcessTime}, it is currently {CorgEngMain.Time}", LogType.DEBUG); +#endif + //If we recieve a signal, loop straight round to processing signal handlers. + //Otherwise, we reached the next processor step, so do processing + if (waitTime > 0) { - isWaiting = true; - //Calculate how long we have to wait for, based on the last fire time, our current time - //and the system wait time. - //Stay in sync! if this value is less than 0, we are overtiming. - int waitTime = (int)Math.Max(1000 * (lastFireTime - CorgEngMain.Time) + ProcessDelay, 0); - //If we recieve a signal, loop straight round to processing signal handlers. - //Otherwise, we reached the next processor step, so do processing - if(waitTime > 0) - continued = waitHandle.WaitOne(waitTime); - else - continued = false; - isWaiting = false; + threadManager.FireSystemIn(this, waitTime); } + // We need to immediately re-fire to process the next tick + else + return false; } - //Terminated - Logger?.WriteLine($"Terminated EntitySystem thread: {this}", LogType.LOG); + return invokationQueue.IsEmpty; } private HashSet registeredDeletionHandlers = new HashSet(); diff --git a/CorgEng.EntityComponentSystem/WorldManager/World.cs b/CorgEng.EntityComponentSystem/WorldManager/World.cs new file mode 100644 index 00000000..d1810a89 --- /dev/null +++ b/CorgEng.EntityComponentSystem/WorldManager/World.cs @@ -0,0 +1,109 @@ +using CorgEng.Core; +using CorgEng.Core.Dependencies; +using CorgEng.EntityComponentSystem.Components; +using CorgEng.EntityComponentSystem.Entities; +using CorgEng.EntityComponentSystem.Systems; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using CorgEng.GenericInterfaces.Logging; +using CorgEng.GenericInterfaces.Networking.Networking.Client; +using CorgEng.GenericInterfaces.Networking.Networking.Server; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.EntityComponentSystem.WorldManager +{ + /// + /// Represents an entity group container. + /// Different worlds represent different processes + /// that do not directly interact with each other. + /// Primarilly used for unit testing. + /// + internal class World : IWorld + { + + [UsingDependency] + private static INetworkServerFactory NetworkServerFactory = null!; + + [UsingDependency] + private static INetworkClientFactory NetworkClientFactory = null!; + + [UsingDependency] + private static ILogger Logger = null!; + + /// + /// The entity manager associated with this world process + /// + public IEntityManager EntityManager { get; } + + /// + /// The entity system manager associated with this world process + /// + public IEntitySystemManager EntitySystemManager { get; } + + + /// + /// The server instance, if it doesn't exist for this world, one will be + /// created. + /// + private INetworkServer _serverInstance; + public INetworkServer ServerInstance + { + get + { + if (_serverInstance == null) + { + _serverInstance = NetworkServerFactory.CreateNetworkServer(this); + } + return _serverInstance; + } + } + + /// + /// The client instance, if one doesn't exist for this world, one will be + /// created. + /// + private INetworkClient _clientInstance; + public INetworkClient ClientInstance + { + get + { + if (_clientInstance == null) + _clientInstance = NetworkClientFactory.CreateNetworkClient(this); + return _clientInstance; + } + } + + public IComponentSignalInjector ComponentSignalInjector { get; } + + public World() + { + EntityManager = new EntityManager(this); + ComponentSignalInjector = new ComponentSignalInjector(this); + EntitySystemManager systemManager = new EntitySystemManager(this); + EntitySystemManager = systemManager; + // Do this last + systemManager.CreateAllSystems(); + CorgEngMain.WorldList.Add(this); + Logger.WriteLine("World created and initialised", LogType.LOG); + } + + /// + /// Cleanup the server and client instance when the world goes out of testing scope. + /// + ~World() + { + Cleanup(); + Logger.WriteLine("World went out of scope and was cleaned up.", LogType.DEBUG); + } + + public void Cleanup() + { + _serverInstance?.Cleanup(); + _clientInstance?.Cleanup(); + EntitySystemManager?.Cleanup(); + } + } +} diff --git a/CorgEng.EntityComponentSystem/WorldManager/WorldFactory.cs b/CorgEng.EntityComponentSystem/WorldManager/WorldFactory.cs new file mode 100644 index 00000000..c8ddbb1e --- /dev/null +++ b/CorgEng.EntityComponentSystem/WorldManager/WorldFactory.cs @@ -0,0 +1,21 @@ +using CorgEng.DependencyInjection.Dependencies; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.EntityComponentSystem.WorldManager +{ + [Dependency] + internal class WorldFactory : IWorldFactory + { + + public IWorld CreateWorld() + { + return new World(); + } + + } +} diff --git a/CorgEng.EntityQuery/EntityQuery.cs b/CorgEng.EntityQuery/EntityQuery.cs index 898818ed..ffc99898 100644 --- a/CorgEng.EntityQuery/EntityQuery.cs +++ b/CorgEng.EntityQuery/EntityQuery.cs @@ -3,6 +3,7 @@ using CorgEng.EntityComponentSystem.Entities; using CorgEng.EntityComponentSystem.Events.Events; using CorgEng.EntityComponentSystem.Systems; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Logging; using System; using System.Collections.Generic; @@ -13,10 +14,14 @@ namespace CorgEng.EntityQuery { - public class EntityQuery + public class EntityQuery : WorldObject { [UsingDependency] internal static ILogger Logger = default!; + + public EntityQuery(IWorld world) : base(world) + { + } } public class EntityQuery : EntityQuery @@ -41,7 +46,7 @@ public class EntityQuery : EntityQuery /// /// Fetch the query if it already exists /// - public EntityQuery() + public EntityQuery(IWorld world) : base(world) { // Lock the entity query lookup so that we don't have issues with threading. lock (EntityQueryLookup) @@ -57,25 +62,25 @@ public EntityQuery() { // The entity lookup doesn't exist, perform an expensive search over all entities #pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type. - components = EntityManager.GetEntityArrayUnsafe() + components = world.EntityManager.GetEntityArrayUnsafe() .Select(entity => entity?.FindComponent()) .Where(component => component != null) .ToHashSet(); #pragma warning restore CS8619 // Nullability of reference types in value doesn't match target type. if (components.Count > 0) { - EntityQuery.Logger.WriteLine($"Performed an entity query locating {components.Count} components. " + + Logger.WriteLine($"Performed an entity query locating {components.Count} components. " + $"It would be far more efficient to initialise the query at runtime.", LogType.WARNING); } // Force ourselves into the entity query system - EntitySystem.GetSingleton() + world.EntitySystemManager.GetSingleton() .RegisterLocalEvent((entity, component, signal) => { if (component != signal.Component) return; components.Add(component); }); - EntitySystem.GetSingleton() + world.EntitySystemManager.GetSingleton() .RegisterLocalEvent((entity, component, signal) => { if (component != signal.Component) @@ -83,10 +88,10 @@ public EntityQuery() components.Remove(component); }); }; - if (EntitySystem.SetupCompleted) + if (world.EntitySystemManager.SetupCompleted) setupFunction(); else - EntitySystem.postSetupAction += setupFunction; + world.EntitySystemManager.postSetupAction += setupFunction; } } diff --git a/CorgEng.EntityQuery/EntityQuerySystem.cs b/CorgEng.EntityQuery/EntityQuerySystem.cs index 40a26876..6854ba0e 100644 --- a/CorgEng.EntityQuery/EntityQuerySystem.cs +++ b/CorgEng.EntityQuery/EntityQuerySystem.cs @@ -1,5 +1,6 @@ using CorgEng.EntityComponentSystem.Events.Events; using CorgEng.EntityComponentSystem.Systems; +using CorgEng.GenericInterfaces.EntityComponentSystem; using System; using System.Collections.Generic; using System.Linq; @@ -13,7 +14,7 @@ public class EntityQuerySystem : EntitySystem public override EntitySystemFlags SystemFlags => EntitySystemFlags.CLIENT_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { } } } diff --git a/CorgEng.Example.Server/CorgEng.Example.Server.csproj b/CorgEng.Example.Server/CorgEng.Example.Server.csproj index 37461709..23abb752 100644 --- a/CorgEng.Example.Server/CorgEng.Example.Server.csproj +++ b/CorgEng.Example.Server/CorgEng.Example.Server.csproj @@ -31,7 +31,6 @@ - diff --git a/CorgEng.Example.Server/Program.cs b/CorgEng.Example.Server/Program.cs index e96c09e8..76d6b079 100644 --- a/CorgEng.Example.Server/Program.cs +++ b/CorgEng.Example.Server/Program.cs @@ -44,15 +44,9 @@ class Program [UsingDependency] private static ILogger Logger; - [UsingDependency] - private static INetworkingServer NetworkingServer; - [UsingDependency] private static IPrototypeManager PrototypeManager; - [UsingDependency] - private static IEntityFactory EntityFactory; - #if DEBUG_RENDERING [UsingDependency] private static IIsometricCameraFactory IsometricCameraFactory; @@ -61,10 +55,14 @@ class Program [UsingDependency] private static IIconFactory IconFactory; - [UsingDependency] private static INetworkConfig NetworkConfig; + [UsingDependency] + private static IWorldFactory WorldFactory; + + public static IWorld ServerWorld; + static void Main(string[] args) { //Load the application config @@ -76,9 +74,19 @@ static void Main(string[] args) #else CorgEngMain.Initialize(); - ExampleRenderCore erc = new ExampleRenderCore(); + // Create the program world + ServerWorld = WorldFactory.CreateWorld(); + CorgEngMain.PrimaryWorld = ServerWorld; + + //Start networking server + ServerWorld.ServerInstance.StartHosting(5000); + + //Debug + NetworkConfig.ProcessClientSystems = true; + + ExampleRenderCore erc = new ExampleRenderCore(ServerWorld); CorgEngMain.SetRenderCore(erc); - EntityFactory.CreateEmptyEntity(entity => { + ServerWorld.EntityManager.CreateEmptyEntity(entity => { IIsometricCamera camera = IsometricCameraFactory.CreateCamera(); camera.Width = 30; camera.Height = 30; @@ -90,7 +98,7 @@ static void Main(string[] args) }); // Create a lighting debugger - EntityFactory.CreateEmptyEntity(entity => { + ServerWorld.EntityManager.CreateEmptyEntity(entity => { entity.AddComponent(new TransformComponent()); entity.AddComponent(new FollowCursorComponent()); entity.AddComponent(new SpriteRenderComponent()); @@ -98,17 +106,12 @@ static void Main(string[] args) new SetSpriteEvent(IconFactory.CreateIcon("rock", 5, Constants.RenderingConstants.DEFAULT_RENDERER_PLANE)).Raise(entity); new SetSpriteRendererEvent(1).Raise(entity); }); - - //Debug - NetworkConfig.ProcessClientSystems = true; #endif BuildWorld(); //Set the default player prototype SetPlayerPrototype(); - //Start networking server - NetworkingServer.StartHosting(5000); #if DEBUG_RENDERING //Transfer control of the main thread to the CorgEng @@ -161,7 +164,7 @@ private static void BuildWorld() { for (int yv = Math.Min(start_y, end_y); yv <= Math.Max(start_y, end_y); yv++) { - EntityFactory.CreateEmptyEntity(testingEntity => { + ServerWorld.EntityManager.CreateEmptyEntity(testingEntity => { //Add components testingEntity.AddComponent(new NetworkTransformComponent()); testingEntity.AddComponent(new SpriteRenderComponent()); @@ -178,7 +181,7 @@ private static void BuildWorld() }); //Create a testing entity - EntityFactory.CreateEmptyEntity(testingEntity => { + ServerWorld.EntityManager.CreateEmptyEntity(testingEntity => { //Add components testingEntity.AddComponent(new NetworkTransformComponent()); testingEntity.AddComponent(new SpriteRenderComponent()); @@ -192,13 +195,13 @@ private static void BuildWorld() private static void SetPlayerPrototype() { - EntityFactory.CreateEmptyEntity(playerPrototype => { + ServerWorld.EntityManager.CreateEmptyEntity(playerPrototype => { playerPrototype.AddComponent(new ClientComponent()); playerPrototype.AddComponent(new NetworkTransformComponent()); playerPrototype.AddComponent(new SpriteRenderComponent() { Sprite = IconFactory.CreateIcon("human.ghost", 5, Constants.RenderingConstants.DEFAULT_RENDERER_PLANE), SpriteRendererIdentifier = 1 }); playerPrototype.AddComponent(new PlayerMovementComponent()); IPrototype prototype = PrototypeManager.GetPrototype(playerPrototype); - NetworkingServer.SetClientPrototype(prototype); + ServerWorld.ServerInstance.SetClientPrototype(prototype); new DeleteEntityEvent().Raise(playerPrototype); }); } diff --git a/CorgEng.Example.Shared/Common/Components/Camera/CameraSystem.cs b/CorgEng.Example.Shared/Common/Components/Camera/CameraSystem.cs index 978c9a7e..914c5a8a 100644 --- a/CorgEng.Example.Shared/Common/Components/Camera/CameraSystem.cs +++ b/CorgEng.Example.Shared/Common/Components/Camera/CameraSystem.cs @@ -23,7 +23,7 @@ public class CameraSystem : EntitySystem //Contains camera logic which only the client knows about public override EntitySystemFlags SystemFlags { get; } = EntitySystemFlags.CLIENT_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(OnCameraMoved); RegisterLocalEvent(OnComponentAdded); diff --git a/CorgEng.Example.Shared/Components/FollowMouseComponent/FollowCursorComponent.cs b/CorgEng.Example.Shared/Components/FollowMouseComponent/FollowCursorComponent.cs index afcb77de..cc56f010 100644 --- a/CorgEng.Example.Shared/Components/FollowMouseComponent/FollowCursorComponent.cs +++ b/CorgEng.Example.Shared/Components/FollowMouseComponent/FollowCursorComponent.cs @@ -5,6 +5,7 @@ using CorgEng.EntityComponentSystem.Implementations.Transform; using CorgEng.EntityComponentSystem.Systems; using CorgEng.EntityQuery; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.InputHandler; using CorgEng.GenericInterfaces.UtilityTypes; using CorgEng.GenericInterfaces.World; @@ -28,14 +29,15 @@ internal class FollowCursorSystem : EntitySystem private static IInputHandler InputHandler = null!; [UsingDependency] - private static IWorld World; + private static IEntityPositionTracker World; - private static EntityQuery CursorComponents = new EntityQuery(); + private static EntityQuery CursorComponents; public override EntitySystemFlags SystemFlags => EntitySystemFlags.CLIENT_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { + CursorComponents = new EntityQuery(world); InputHandler.AddMouseMoveBind("cursor_moved"); InputHandler.AddMouseMoveAction("cursor_moved", (deltaTime, mouseX, mouseY) => { Vector worldCoordinates = (Vector)CorgEngMain.MainCamera.ScreenToWorldCoordinates(mouseX * 2 - 1, mouseY * 2 - 1, CorgEngMain.GameWindow.Width, CorgEngMain.GameWindow.Height); diff --git a/CorgEng.Example.Shared/Components/Gravity/SandSystem.cs b/CorgEng.Example.Shared/Components/Gravity/SandSystem.cs index 8100dc3e..5c642898 100644 --- a/CorgEng.Example.Shared/Components/Gravity/SandSystem.cs +++ b/CorgEng.Example.Shared/Components/Gravity/SandSystem.cs @@ -19,7 +19,7 @@ public class SandSystem : ProcessingSystem { [UsingDependency] - private static IWorld WorldAccess = default!; + private static IEntityPositionTracker WorldAccess = default!; public override EntitySystemFlags SystemFlags => EntitySystemFlags.HOST_SYSTEM; @@ -30,7 +30,7 @@ public class SandSystem : ProcessingSystem /// private WorldPathCellQueryer SolidCellQuery = new WorldPathCellQueryer(); - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(OnSandInitialise); RegisterLocalEvent(OnSandDestroyed); @@ -49,7 +49,7 @@ private void OnSandDestroyed(IEntity entity, SandComponent sandComponent, Delete private void ProcessSand(IEntity sandEntity, SandComponent sandComponent, double deltaTime) { TransformComponent sandTransform = sandEntity.GetComponent(); - Vector nextPosition = new Vector(sandTransform.Position.X, sandTransform.Position.Y - 1); + Vector nextPosition = new Vector(sandTransform.Position.Value.X, sandTransform.Position.Value.Y - 1); if (SolidCellQuery.EnterPositionCost(nextPosition, GenericInterfaces.Core.Direction.SOUTH) > 0) { new SetPositionEvent(nextPosition).Raise(sandEntity); diff --git a/CorgEng.Example.Shared/Components/PlayerMovement/PlayerMovementSystem.cs b/CorgEng.Example.Shared/Components/PlayerMovement/PlayerMovementSystem.cs index e4911a50..2e0544a4 100644 --- a/CorgEng.Example.Shared/Components/PlayerMovement/PlayerMovementSystem.cs +++ b/CorgEng.Example.Shared/Components/PlayerMovement/PlayerMovementSystem.cs @@ -30,7 +30,7 @@ public class PlayerMovementSystem : EntitySystem //Logic executed on the server that moves players when requested. public override EntitySystemFlags SystemFlags { get; } = EntitySystemFlags.HOST_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(OnComponentAdded); RegisterLocalEvent(OnComponentRemoved); diff --git a/CorgEng.Example.Shared/Components/SandFactory/SandFactorySystem.cs b/CorgEng.Example.Shared/Components/SandFactory/SandFactorySystem.cs index 50d5874b..a871e448 100644 --- a/CorgEng.Example.Shared/Components/SandFactory/SandFactorySystem.cs +++ b/CorgEng.Example.Shared/Components/SandFactory/SandFactorySystem.cs @@ -10,6 +10,7 @@ using CorgEng.Lighting.Components; using CorgEng.Networking.Components; using CorgEng.Pathfinding.Components; +using CorgEng.Rendering.ComponentSystem.DepthParallax; using CorgEng.UtilityTypes.Colours; using CorgEng.UtilityTypes.Vectors; using System; @@ -23,9 +24,6 @@ namespace CorgEng.Example.Shared.Components.SandFactory internal class SandFactorySystem : ProcessingSystem { - [UsingDependency] - private static IEntityFactory EntityFactory = default!; - [UsingDependency] private static IIconFactory IconFactory = default!; @@ -35,23 +33,24 @@ internal class SandFactorySystem : ProcessingSystem protected override int ProcessDelay => 2000; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent((entity, component, signal) => { TransformComponent transform = entity.GetComponent(); RegisterProcess(entity, (entity, component, deltaTime) => { - EntityFactory.CreateEmptyEntity(entity => { + world.EntityManager.CreateEmptyEntity(entity => { //Add components entity.AddComponent(new NetworkTransformComponent()); - entity.AddComponent(new SpriteRenderComponent()); + entity.AddComponent(new DepthSpriteRenderComponent()); + //entity.AddComponent(new SpriteRenderComponent()); entity.AddComponent(new SandComponent()); entity.AddComponent(new SolidComponent()); entity.AddComponent(new LightingComponent() { Colour = new Colour(DebugRandom.NextSingle(), DebugRandom.NextSingle(), DebugRandom.NextSingle(), DebugRandom.NextSingle()), }); //Update the entity - new SetPositionEvent(new Vector(transform.Position.X, transform.Position.Y)).Raise(entity); - new SetSpriteEvent(IconFactory.CreateIcon("sand", 5, Constants.RenderingConstants.DEFAULT_RENDERER_PLANE)).Raise(entity); + new SetPositionEvent(new Vector(transform.Position.Value.X, transform.Position.Value.Y)).Raise(entity); + new SetSpriteEvent(IconFactory.CreateIcon("sand", DebugRandom.Next(10, 300) / 10.0f, Constants.RenderingConstants.DEFAULT_RENDERER_PLANE)).Raise(entity); new SetSpriteRendererEvent(1).Raise(entity); }); }); diff --git a/CorgEng.Example.Shared/CorgEng.Example.Shared.csproj b/CorgEng.Example.Shared/CorgEng.Example.Shared.csproj index 387df563..20252659 100644 --- a/CorgEng.Example.Shared/CorgEng.Example.Shared.csproj +++ b/CorgEng.Example.Shared/CorgEng.Example.Shared.csproj @@ -20,6 +20,7 @@ + diff --git a/CorgEng.Example.Shared/RenderCores/ExampleRenderCore.cs b/CorgEng.Example.Shared/RenderCores/ExampleRenderCore.cs index 5e618a2f..3a92d1db 100644 --- a/CorgEng.Example.Shared/RenderCores/ExampleRenderCore.cs +++ b/CorgEng.Example.Shared/RenderCores/ExampleRenderCore.cs @@ -2,6 +2,7 @@ using CorgEng.Core.Dependencies; using CorgEng.Core.Rendering; using CorgEng.EntityComponentSystem.Entities; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Font.Fonts; using CorgEng.GenericInterfaces.Rendering.Renderers.SpriteRendering; using CorgEng.GenericInterfaces.Rendering.RenderObjects.SpriteRendering; @@ -10,6 +11,7 @@ using CorgEng.GenericInterfaces.UserInterface.Components; using CorgEng.GenericInterfaces.UserInterface.Generators; using CorgEng.Lighting.RenderCores; +using CorgEng.Rendering.DepthParallax; using System; using System.Collections.Generic; using System.Linq; @@ -40,9 +42,16 @@ public class ExampleRenderCore : RenderCore [UsingDependency] public static ITextObjectFactory TextObjectFactory; - public override void Initialize() + private LightingRenderCore lightingRenderCore; + + private ParallaxLayerRenderCore parallaxLayerRenderCore; + + public ExampleRenderCore(IWorld world) : base(world) { + } + public override void Initialize() + { spriteRenderer = SpriteRendererFactory.CreateSpriteRenderer(1); spriteRenderer?.Initialize(); @@ -50,16 +59,20 @@ public override void Initialize() ITextObject textObject = TextObjectFactory.CreateTextObject(spriteRenderer, font, "CorgEng.Font"); textObject.StartRendering(); - LightingRenderCore.Resolve(); - + parallaxLayerRenderCore = new ParallaxLayerRenderCore(world, 1, 30); + parallaxLayerRenderCore?.Initialize(); + lightingRenderCore = new LightingRenderCore(world); + lightingRenderCore.Initialize(); } public override void PerformRender() { spriteRenderer?.Render(CorgEngMain.MainCamera); - LightingRenderCore.Singleton.DoRender(); - LightingRenderCore.Singleton.DrawToBuffer(FrameBufferUint, 0, 0, Width, Height); + parallaxLayerRenderCore.DoRender(); + parallaxLayerRenderCore.DrawToBuffer(FrameBufferUint, 0, 0, Width, Height); + //lightingRenderCore.DoRender(); + //lightingRenderCore.DrawToBuffer(FrameBufferUint, 0, 0, Width, Height); } } } diff --git a/CorgEng.Example/Modules/CameraScroll/CameraScrollSystem.cs b/CorgEng.Example/Modules/CameraScroll/CameraScrollSystem.cs index d4f04bf2..88ae8479 100644 --- a/CorgEng.Example/Modules/CameraScroll/CameraScrollSystem.cs +++ b/CorgEng.Example/Modules/CameraScroll/CameraScrollSystem.cs @@ -1,5 +1,6 @@ using CorgEng.Core; using CorgEng.EntityComponentSystem.Systems; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Rendering.Cameras.Isometric; using CorgEng.InputHandling.Events; using System; @@ -17,7 +18,7 @@ internal class CameraScrollSystem : EntitySystem public override EntitySystemFlags SystemFlags => EntitySystemFlags.CLIENT_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { //RegisterGlobalEvent(OnMouseScroll); } diff --git a/CorgEng.Example/Program.cs b/CorgEng.Example/Program.cs index a9c1686f..42b4e6fe 100644 --- a/CorgEng.Example/Program.cs +++ b/CorgEng.Example/Program.cs @@ -2,6 +2,7 @@ using CorgEng.Core.Dependencies; using CorgEng.Example.Modules.CameraScroll; using CorgEng.Example.Shared.RenderCores; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Networking.Networking.Client; using CorgEng.GenericInterfaces.Rendering.Cameras.Isometric; using System.Threading; @@ -13,10 +14,10 @@ class Program { [UsingDependency] - private static IIsometricCameraFactory isometricCameraFactory; + private static IIsometricCameraFactory isometricCameraFactory = null!; [UsingDependency] - private static INetworkingClient NetworkingClient; + private static IWorldFactory WorldFactory = null!; static void Main(string[] args) { @@ -27,6 +28,10 @@ static void Main(string[] args) //modules that are dependencies CorgEngMain.Initialize(); + // Create the world + IWorld world = WorldFactory.CreateWorld(); + CorgEngMain.PrimaryWorld = world; + //Set the render core // Prevent failures @@ -36,11 +41,11 @@ static void Main(string[] args) IIsometricCamera camera = isometricCameraFactory.CreateCamera(); CameraScrollSystem.IsometricCamera = camera; - ExampleRenderCore erc = new ExampleRenderCore(); + ExampleRenderCore erc = new ExampleRenderCore(world); CorgEngMain.SetRenderCore(erc); //Connect to our server - NetworkingClient.AttemptConnection("127.0.0.1", 5000); + world.ClientInstance.AttemptConnection("127.0.0.1", 5000); //Create the entity to hold and move the camera /*Entity mainCameraEntity = new Entity(); diff --git a/CorgEng.Functional/CorgEng.Functional.csproj b/CorgEng.Functional/CorgEng.Functional.csproj new file mode 100644 index 00000000..132c02c5 --- /dev/null +++ b/CorgEng.Functional/CorgEng.Functional.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/CorgEng.Functional/Monads/Result.cs b/CorgEng.Functional/Monads/Result.cs new file mode 100644 index 00000000..8af271b8 --- /dev/null +++ b/CorgEng.Functional/Monads/Result.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.Functional.Monads +{ + public class Result + { + + public virtual bool HasValue { get; } = true; + + private T value; + + public Result(T value) + { + this.value = value; + } + + public Result Fail(Action failureAction) + { + if (HasValue) + return this; + failureAction(); + return this; + } + + public Result Then(Action successAction) + { + if (!HasValue) + return this; + successAction(value); + return this; + } + + } + + public class Failure : Result + { + + public Failure() : base(default) + { } + + public override bool HasValue => false; + } +} diff --git a/CorgEng.GenericInterfaces/AiBehaviours/IBehaviourManagerFactory.cs b/CorgEng.GenericInterfaces/AiBehaviours/IBehaviourManagerFactory.cs index d7e8e8ed..ea91b3d3 100644 --- a/CorgEng.GenericInterfaces/AiBehaviours/IBehaviourManagerFactory.cs +++ b/CorgEng.GenericInterfaces/AiBehaviours/IBehaviourManagerFactory.cs @@ -15,7 +15,7 @@ public interface IBehaviourManagerFactory /// /// /// - IBehaviourManager CreateBehaviourManager(IEntity attachedPawn, params IBehaviourNode[] nodes); + IBehaviourManager CreateBehaviourManager(IWorld world, IEntity attachedPawn, params IBehaviourNode[] nodes); } } diff --git a/CorgEng.GenericInterfaces/ContentLoading/IEntityCreator.cs b/CorgEng.GenericInterfaces/ContentLoading/IEntityCreator.cs index 014d253c..e1daeead 100644 --- a/CorgEng.GenericInterfaces/ContentLoading/IEntityCreator.cs +++ b/CorgEng.GenericInterfaces/ContentLoading/IEntityCreator.cs @@ -18,14 +18,14 @@ public interface IEntityCreator /// /// /// Returns an uninitialised entity. - IEntity CreateEntity(string entityName, Action preInitialisationEvents); + IEntity CreateEntity(IWorld world, string entityName, Action preInitialisationEvents); /// /// Create an object with the specified identifier. /// /// The identifier of the object to create. /// Returns an object created based on the definition file. - object CreateObject(string objectIdentifier); + object CreateObject(IWorld world, string objectIdentifier); } } diff --git a/CorgEng.GenericInterfaces/ContentLoading/IEntityDefinition.cs b/CorgEng.GenericInterfaces/ContentLoading/IEntityDefinition.cs index 6e6fb0f8..3eb2a19f 100644 --- a/CorgEng.GenericInterfaces/ContentLoading/IEntityDefinition.cs +++ b/CorgEng.GenericInterfaces/ContentLoading/IEntityDefinition.cs @@ -14,7 +14,7 @@ public interface IEntityDefinition /// Create the entity /// /// - IEntity CreateEntity(Action setupAction); + IEntity CreateEntity(IWorld world, Action setupAction); } } diff --git a/CorgEng.GenericInterfaces/Core/IRenderCore.cs b/CorgEng.GenericInterfaces/Core/IRenderCore.cs index db9077a4..51020625 100644 --- a/CorgEng.GenericInterfaces/Core/IRenderCore.cs +++ b/CorgEng.GenericInterfaces/Core/IRenderCore.cs @@ -29,9 +29,11 @@ public interface IRenderCore void Initialize(); /// - /// Perform rendering + /// Render this render core to its render texture. /// - void DoRender(); + /// The depth texture of the thing that we are drawing on top of + /// + void DoRender(Action? preRenderAction = null); /// /// Draws the rendered frame buffer to the screen (Framebuffer 0) diff --git a/CorgEng.GenericInterfaces/DependencyInjection/NonDependencyAttribute.cs b/CorgEng.GenericInterfaces/DependencyInjection/NonDependencyAttribute.cs new file mode 100644 index 00000000..d427ee32 --- /dev/null +++ b/CorgEng.GenericInterfaces/DependencyInjection/NonDependencyAttribute.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.DependencyInjection.Dependencies +{ + /// + /// Indicdates that an interface should not be used + /// as a dependency and will cause dependency injection + /// to fail. This is used when an interface that was a dependency + /// before is no longer being used as a dependency. + /// + [AttributeUsage(AttributeTargets.Interface)] + internal class NonDependencyAttribute : Attribute + { + + public bool fail = true; + + public string message; + + public NonDependencyAttribute() + { + } + + public NonDependencyAttribute(string message) + { + this.message = message; + } + } +} diff --git a/CorgEng.EntityComponentSystem/Events/EventComponentPair.cs b/CorgEng.GenericInterfaces/EntityComponentSystem/EventComponentPair.cs similarity index 59% rename from CorgEng.EntityComponentSystem/Events/EventComponentPair.cs rename to CorgEng.GenericInterfaces/EntityComponentSystem/EventComponentPair.cs index 37d09918..c623544e 100644 --- a/CorgEng.EntityComponentSystem/Events/EventComponentPair.cs +++ b/CorgEng.GenericInterfaces/EntityComponentSystem/EventComponentPair.cs @@ -1,8 +1,12 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; -namespace CorgEng.EntityComponentSystem.Events +namespace CorgEng.GenericInterfaces.EntityComponentSystem { - internal struct EventComponentPair + public struct EventComponentPair { public Type EventType { get; } diff --git a/CorgEng.GenericInterfaces/EntityComponentSystem/IComponentSignalInjector.cs b/CorgEng.GenericInterfaces/EntityComponentSystem/IComponentSignalInjector.cs new file mode 100644 index 00000000..bd293991 --- /dev/null +++ b/CorgEng.GenericInterfaces/EntityComponentSystem/IComponentSignalInjector.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.GenericInterfaces.EntityComponentSystem +{ + public interface IComponentSignalInjector + { + + void OnComponentAdded(IComponent component, IEntity parent); + + void OnComponentRemoved(IComponent component, IEntity parent, bool silent); + + } +} diff --git a/CorgEng.GenericInterfaces/EntityComponentSystem/IEntity.cs b/CorgEng.GenericInterfaces/EntityComponentSystem/IEntity.cs index b289f29c..138d1e7a 100644 --- a/CorgEng.GenericInterfaces/EntityComponentSystem/IEntity.cs +++ b/CorgEng.GenericInterfaces/EntityComponentSystem/IEntity.cs @@ -11,6 +11,8 @@ namespace CorgEng.GenericInterfaces.EntityComponentSystem public interface IEntity { + public delegate void InternalSignalHandleDelegate(IEntity entity, IEvent signal, bool synchronous, string callingFile, string callingMember, int callingLine); + /// /// The name of the definition used to spawn this entity. /// Null if the entity was created without using EntityCreator. @@ -73,5 +75,20 @@ public interface IEntity /// bool HasComponent(); + /// + /// Add an event listener to a specific type + /// + /// + /// + void AddEventListener(Type eventType, InternalSignalHandleDelegate eventHandler); + + /// + /// Remove an event listener from a provided event type. + /// + /// + /// + /// + void RemoveEventListenter(Type eventType, InternalSignalHandleDelegate eventHandler); + } } diff --git a/CorgEng.GenericInterfaces/EntityComponentSystem/IEntityFactory.cs b/CorgEng.GenericInterfaces/EntityComponentSystem/IEntityFactory.cs deleted file mode 100644 index 07bd9da7..00000000 --- a/CorgEng.GenericInterfaces/EntityComponentSystem/IEntityFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CorgEng.GenericInterfaces.EntityComponentSystem -{ - public interface IEntityFactory - { - - /// - /// Creates an empty entity with no components - /// - /// - IEntity CreateEmptyEntity(Action preInitialisationEvents); - - /// - /// Creates an empty entity with no components - /// - /// - IEntity CreateUninitialisedEntity(); - - } -} diff --git a/CorgEng.GenericInterfaces/EntityComponentSystem/IEntityManager.cs b/CorgEng.GenericInterfaces/EntityComponentSystem/IEntityManager.cs new file mode 100644 index 00000000..829f3f12 --- /dev/null +++ b/CorgEng.GenericInterfaces/EntityComponentSystem/IEntityManager.cs @@ -0,0 +1,34 @@ +using CorgEng.EntityComponentSystem.Entities; +using CorgEng.GenericInterfaces.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CorgEng.GenericInterfaces.EntityComponentSystem +{ + public interface IEntityManager + { + + void RegisterEntity(IEntity entity); + + uint GetNewEntityId(); + + void RemoveEntity(IEntity entity); + + void InternallyDelete(IEntity entity); + + IEntity GetEntity(uint identifier); + + IEntity[] GetEntityArrayUnsafe(); + + IEntity CreateEmptyEntity(Action preInitialisationEvents); + + IEntity CreateUninitialisedEntity(); + + IEntity CreateUninitialisedEntity(uint entityIdentifier); + + } +} diff --git a/CorgEng.GenericInterfaces/EntityComponentSystem/IEntitySystemManager.cs b/CorgEng.GenericInterfaces/EntityComponentSystem/IEntitySystemManager.cs new file mode 100644 index 00000000..80e6043b --- /dev/null +++ b/CorgEng.GenericInterfaces/EntityComponentSystem/IEntitySystemManager.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.GenericInterfaces.EntityComponentSystem +{ + public interface IEntitySystemManager + { + + /// + /// The delegate used for handling system events. + /// + /// The entity target of the signal handler. + /// The component that is affected by the raised signal. + /// The signal that was raised. + /// Boolean value which represents if the event was raised synchronously. Synchronous events + /// will halt the curren threads execution until it is handled. + /// The name of the file that the event was called from. + /// The name of the member that raised the event. + /// The line number that the event was raised on. + public delegate void SystemEventHandlerDelegate(IEntity entity, IComponent component, IEvent signal, bool synchronous, string file, string member, int lineNumber); + + /// + /// A boolean value representing wether or not the entity systems have been + /// setup or not. + /// + bool SetupCompleted { get; } + + /// + /// An event that is raised once the entity systems are registered and have had + /// their SetupSystem function called. This will only ever be called once in normal + /// operations, once it has been called adding to this event will be pointless as + /// it will never be called again. + /// + event Action postSetupAction; + + /// + /// Get the singleton subsystem with the type T, + /// running within this world instance. + /// + /// + /// + T GetSingleton(); + + /// + /// Register an event with a specific type + /// + void RegisterEventType(Type componentType, Type eventType); + + /// + /// Unregister an event with a given type for a given component. + /// + /// + /// + void UnregisterEventType(Type componentType, Type eventType); + + /// + /// Returns the enumable set of types registered to the + /// provided component type. + /// + /// + /// + IEnumerable GetRegisteredEventTypes(Type componentType); + + /// + /// Register a system event handler to this system manager group. When an event + /// is raised on an object that contains a component of the types specified in the + /// EventComponentPair, then the provided SystemEventHandlerDelegate will be raised. + /// + /// + /// + void RegisterSystemEventHandler(EventComponentPair registeredEventComponentType, SystemEventHandlerDelegate eventAction); + + /// + /// Unregister a system event handler attached to this system group. See RegisterSystemEventHandler + /// for details. + /// + /// + /// + void UnregisterSystemEventHandler(EventComponentPair registeredEventComponentType, SystemEventHandlerDelegate eventAction); + + /// + /// Get an enumerable set of the registered system event handlers registered to a + /// specified event component pair. + /// + /// + List GetRegisteredSystemEventHandlers(EventComponentPair registeredEventComponentType); + + /** + * Shutdown and cleanup + */ + void Cleanup(); + + } +} diff --git a/CorgEng.GenericInterfaces/EntityComponentSystem/IWorld.cs b/CorgEng.GenericInterfaces/EntityComponentSystem/IWorld.cs new file mode 100644 index 00000000..35d72615 --- /dev/null +++ b/CorgEng.GenericInterfaces/EntityComponentSystem/IWorld.cs @@ -0,0 +1,27 @@ +using CorgEng.GenericInterfaces.Networking.Networking.Client; +using CorgEng.GenericInterfaces.Networking.Networking.Server; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.GenericInterfaces.EntityComponentSystem +{ + public interface IWorld + { + + IEntityManager EntityManager { get; } + + IEntitySystemManager EntitySystemManager { get; } + + IComponentSignalInjector ComponentSignalInjector { get; } + + INetworkServer ServerInstance { get; } + + INetworkClient ClientInstance { get; } + + void Cleanup(); + + } +} diff --git a/CorgEng.GenericInterfaces/EntityComponentSystem/IWorldFactory.cs b/CorgEng.GenericInterfaces/EntityComponentSystem/IWorldFactory.cs new file mode 100644 index 00000000..163c5750 --- /dev/null +++ b/CorgEng.GenericInterfaces/EntityComponentSystem/IWorldFactory.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.GenericInterfaces.EntityComponentSystem +{ + public interface IWorldFactory + { + + IWorld CreateWorld(); + + } +} diff --git a/CorgEng.GenericInterfaces/EntityComponentSystem/WorldObject.cs b/CorgEng.GenericInterfaces/EntityComponentSystem/WorldObject.cs new file mode 100644 index 00000000..4615939b --- /dev/null +++ b/CorgEng.GenericInterfaces/EntityComponentSystem/WorldObject.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.GenericInterfaces.EntityComponentSystem +{ + public class WorldObject + { + + public IWorld world { get; } + + public WorldObject(IWorld world) + { + this.world = world; + } + + } +} diff --git a/CorgEng.GenericInterfaces/World/IContentsHolder.cs b/CorgEng.GenericInterfaces/EntityPositionTracker/IContentsHolder.cs similarity index 100% rename from CorgEng.GenericInterfaces/World/IContentsHolder.cs rename to CorgEng.GenericInterfaces/EntityPositionTracker/IContentsHolder.cs diff --git a/CorgEng.GenericInterfaces/World/IWorld.cs b/CorgEng.GenericInterfaces/EntityPositionTracker/IEntityPositionTracker.cs similarity index 99% rename from CorgEng.GenericInterfaces/World/IWorld.cs rename to CorgEng.GenericInterfaces/EntityPositionTracker/IEntityPositionTracker.cs index fb428cfb..dbcb6174 100644 --- a/CorgEng.GenericInterfaces/World/IWorld.cs +++ b/CorgEng.GenericInterfaces/EntityPositionTracker/IEntityPositionTracker.cs @@ -9,7 +9,7 @@ namespace CorgEng.GenericInterfaces.World { - public interface IWorld + public interface IEntityPositionTracker { /// diff --git a/CorgEng.GenericInterfaces/World/IWorldTrackComponent.cs b/CorgEng.GenericInterfaces/EntityPositionTracker/IWorldTrackComponent.cs similarity index 100% rename from CorgEng.GenericInterfaces/World/IWorldTrackComponent.cs rename to CorgEng.GenericInterfaces/EntityPositionTracker/IWorldTrackComponent.cs diff --git a/CorgEng.GenericInterfaces/Networking/Networking/Client/INetworkingClient.cs b/CorgEng.GenericInterfaces/Networking/Networking/Client/INetworkClient.cs similarity index 97% rename from CorgEng.GenericInterfaces/Networking/Networking/Client/INetworkingClient.cs rename to CorgEng.GenericInterfaces/Networking/Networking/Client/INetworkClient.cs index a9e458cb..49a8aa85 100644 --- a/CorgEng.GenericInterfaces/Networking/Networking/Client/INetworkingClient.cs +++ b/CorgEng.GenericInterfaces/Networking/Networking/Client/INetworkClient.cs @@ -14,7 +14,7 @@ namespace CorgEng.GenericInterfaces.Networking.Networking.Client public delegate void ConnectionFailed(IPAddress ipAddress, DisconnectReason disconnectReason, string reasonText); - public interface INetworkingClient + public interface INetworkClient { /// diff --git a/CorgEng.GenericInterfaces/Networking/Networking/Client/INetworkClientFactory.cs b/CorgEng.GenericInterfaces/Networking/Networking/Client/INetworkClientFactory.cs new file mode 100644 index 00000000..93e4bbea --- /dev/null +++ b/CorgEng.GenericInterfaces/Networking/Networking/Client/INetworkClientFactory.cs @@ -0,0 +1,16 @@ +using CorgEng.GenericInterfaces.EntityComponentSystem; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.GenericInterfaces.Networking.Networking.Client +{ + public interface INetworkClientFactory + { + + INetworkClient CreateNetworkClient(IWorld world); + + } +} diff --git a/CorgEng.GenericInterfaces/Networking/Networking/Server/IEntityCommunicatorFactory.cs b/CorgEng.GenericInterfaces/Networking/Networking/Server/IEntityCommunicatorFactory.cs new file mode 100644 index 00000000..7e2054aa --- /dev/null +++ b/CorgEng.GenericInterfaces/Networking/Networking/Server/IEntityCommunicatorFactory.cs @@ -0,0 +1,16 @@ +using CorgEng.GenericInterfaces.EntityComponentSystem; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.GenericInterfaces.Networking.Networking.Server +{ + public interface IEntityCommunicatorFactory + { + + IEntityCommunicator CreateEntityCommunicator(IWorld world); + + } +} diff --git a/CorgEng.GenericInterfaces/Networking/Networking/Server/INetworkingServer.cs b/CorgEng.GenericInterfaces/Networking/Networking/Server/INetworkServer.cs similarity index 97% rename from CorgEng.GenericInterfaces/Networking/Networking/Server/INetworkingServer.cs rename to CorgEng.GenericInterfaces/Networking/Networking/Server/INetworkServer.cs index 226ddbc7..b2d07e57 100644 --- a/CorgEng.GenericInterfaces/Networking/Networking/Server/INetworkingServer.cs +++ b/CorgEng.GenericInterfaces/Networking/Networking/Server/INetworkServer.cs @@ -9,7 +9,7 @@ namespace CorgEng.GenericInterfaces.Networking.Networking.Server { - public interface INetworkingServer + public interface INetworkServer { /// diff --git a/CorgEng.GenericInterfaces/Networking/Networking/Server/INetworkServerFactory.cs b/CorgEng.GenericInterfaces/Networking/Networking/Server/INetworkServerFactory.cs new file mode 100644 index 00000000..4fbe4b6f --- /dev/null +++ b/CorgEng.GenericInterfaces/Networking/Networking/Server/INetworkServerFactory.cs @@ -0,0 +1,16 @@ +using CorgEng.GenericInterfaces.EntityComponentSystem; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.GenericInterfaces.Networking.Networking.Server +{ + public interface INetworkServerFactory + { + + INetworkServer CreateNetworkServer(IWorld world); + + } +} diff --git a/CorgEng.GenericInterfaces/Networking/Packets/PacketHeaders.cs b/CorgEng.GenericInterfaces/Networking/Packets/PacketHeaders.cs index 7153d982..d3168f9b 100644 --- a/CorgEng.GenericInterfaces/Networking/Packets/PacketHeaders.cs +++ b/CorgEng.GenericInterfaces/Networking/Packets/PacketHeaders.cs @@ -37,5 +37,7 @@ public enum PacketHeaders PING_RESPONSE, //Request information about an entity REQUEST_ENTITY, + //Update a netvar value + NETVAR_VALUE_UPDATED, } } diff --git a/CorgEng.GenericInterfaces/Networking/PrototypeManager/IPrototype.cs b/CorgEng.GenericInterfaces/Networking/PrototypeManager/IPrototype.cs index 92b94009..7c2889dd 100644 --- a/CorgEng.GenericInterfaces/Networking/PrototypeManager/IPrototype.cs +++ b/CorgEng.GenericInterfaces/Networking/PrototypeManager/IPrototype.cs @@ -28,13 +28,13 @@ public interface IPrototype /// Create a new entity from a prototype, with a new and unique identifier. /// /// - IEntity CreateEntityFromPrototype(); + IEntity CreateEntityFromPrototype(IWorld world); /// /// Create an existing entity based on the information in this prototype /// /// An instantiated entity based on the contents of this prototype. - IEntity CreateEntityFromPrototype(uint entityIdentifier); + IEntity CreateEntityFromPrototype(IWorld world, uint entityIdentifier); /// /// Serializes the prototype into a byte array, allowing it to be transmitted through the networking. diff --git a/CorgEng.GenericInterfaces/UserInterface/Components/IUserInterfaceComponent.cs b/CorgEng.GenericInterfaces/UserInterface/Components/IUserInterfaceComponent.cs index 99cf1e25..9da41708 100644 --- a/CorgEng.GenericInterfaces/UserInterface/Components/IUserInterfaceComponent.cs +++ b/CorgEng.GenericInterfaces/UserInterface/Components/IUserInterfaceComponent.cs @@ -71,6 +71,11 @@ public interface IUserInterfaceComponent /// double BottomOffset { get; } + /// + /// The world relevant to this user interface component + /// + IWorld World { get; } + /// /// Raw list of children /// diff --git a/CorgEng.GenericInterfaces/UserInterface/Components/IUserInterfaceComponentFactory.cs b/CorgEng.GenericInterfaces/UserInterface/Components/IUserInterfaceComponentFactory.cs index d274598c..98402cd5 100644 --- a/CorgEng.GenericInterfaces/UserInterface/Components/IUserInterfaceComponentFactory.cs +++ b/CorgEng.GenericInterfaces/UserInterface/Components/IUserInterfaceComponentFactory.cs @@ -1,4 +1,5 @@ -using CorgEng.GenericInterfaces.UserInterface.Anchors; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using CorgEng.GenericInterfaces.UserInterface.Anchors; using System; using System.Collections.Generic; using System.Linq; @@ -16,7 +17,7 @@ public interface IUserInterfaceComponentFactory /// The parent of this component. /// The anchor of this component. Defines where the user interface component will be rendered relative to the parent. /// The created blank user interface component. - IUserInterfaceComponent CreateGenericUserInterfaceComponent(IUserInterfaceComponent parent, IAnchor anchorDetails, Action preInitialiseAction); + IUserInterfaceComponent CreateGenericUserInterfaceComponent(IWorld world, IUserInterfaceComponent parent, IAnchor anchorDetails, Action preInitialiseAction); /// /// Creates a generic user interface component with no parent. @@ -24,7 +25,7 @@ public interface IUserInterfaceComponentFactory /// /// The anchor details of this component. /// The created blank user interface component. - IUserInterfaceComponent CreateGenericUserInterfaceComponent(IAnchor anchorDetails, Action preInitialiseAction); + IUserInterfaceComponent CreateGenericUserInterfaceComponent(IWorld world, IAnchor anchorDetails, Action preInitialiseAction); /// /// @@ -34,7 +35,7 @@ public interface IUserInterfaceComponentFactory /// /// /// - IUserInterfaceComponent CreateUserInterfaceComponent(string componentType, IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments, Action preInitialiseAction); + IUserInterfaceComponent CreateUserInterfaceComponent(IWorld world, string componentType, IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments, Action preInitialiseAction); /// /// @@ -43,7 +44,7 @@ public interface IUserInterfaceComponentFactory /// /// /// - IUserInterfaceComponent CreateUserInterfaceComponent(string componentType, IAnchor anchorDetails, IDictionary arguments, Action preInitialiseAction); + IUserInterfaceComponent CreateUserInterfaceComponent(IWorld world, string componentType, IAnchor anchorDetails, IDictionary arguments, Action preInitialiseAction); } } diff --git a/CorgEng.GenericInterfaces/UserInterface/Generators/IUserInterfaceXmlLoader.cs b/CorgEng.GenericInterfaces/UserInterface/Generators/IUserInterfaceXmlLoader.cs index fb395431..85f177b1 100644 --- a/CorgEng.GenericInterfaces/UserInterface/Generators/IUserInterfaceXmlLoader.cs +++ b/CorgEng.GenericInterfaces/UserInterface/Generators/IUserInterfaceXmlLoader.cs @@ -1,4 +1,5 @@ -using CorgEng.GenericInterfaces.UserInterface.Components; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using CorgEng.GenericInterfaces.UserInterface.Components; using System; using System.Collections.Generic; using System.Linq; @@ -15,7 +16,7 @@ public interface IUserInterfaceXmlLoader /// /// The path to the XML file to load the user interface from. /// Returns the root component of the user interface. - IUserInterfaceComponent LoadUserInterface(string filepath); + IUserInterfaceComponent LoadUserInterface(IWorld world, string filepath); } } diff --git a/CorgEng.GenericInterfaces/UserInterface/Popups/IPopupManager.cs b/CorgEng.GenericInterfaces/UserInterface/Popups/IPopupManager.cs index 7a763479..09f8fccd 100644 --- a/CorgEng.GenericInterfaces/UserInterface/Popups/IPopupManager.cs +++ b/CorgEng.GenericInterfaces/UserInterface/Popups/IPopupManager.cs @@ -1,4 +1,5 @@ -using CorgEng.GenericInterfaces.UserInterface.Components; +using CorgEng.DependencyInjection.Dependencies; +using CorgEng.GenericInterfaces.UserInterface.Components; using System; using System.Collections.Generic; using System.Linq; @@ -7,6 +8,7 @@ namespace CorgEng.GenericInterfaces.UserInterface.Popups { + [NonDependency("IPopupManager is no longer used as a dependency and should be accessed from IWorld instead.")] public interface IPopupManager { diff --git a/CorgEng.GenericInterfaces/UtilityTypes/IBindableProperty.cs b/CorgEng.GenericInterfaces/UtilityTypes/IBindableProperty.cs index a7e59c0d..7cdc1ad5 100644 --- a/CorgEng.GenericInterfaces/UtilityTypes/IBindableProperty.cs +++ b/CorgEng.GenericInterfaces/UtilityTypes/IBindableProperty.cs @@ -2,12 +2,9 @@ namespace CorgEng.GenericInterfaces.UtilityTypes { - public interface IBindableProperty + public interface IBindableProperty : IListenable { - //Callback for when the value is changed - event EventHandler ValueChanged; - //The actual value of the bindable property T Value { get; set; } diff --git a/CorgEng.GenericInterfaces/UtilityTypes/IListenable.cs b/CorgEng.GenericInterfaces/UtilityTypes/IListenable.cs new file mode 100644 index 00000000..f7002329 --- /dev/null +++ b/CorgEng.GenericInterfaces/UtilityTypes/IListenable.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.GenericInterfaces.UtilityTypes +{ + public interface IListenable + { + + //Callback for when the value is changed + event EventHandler ValueChanged; + + } +} diff --git a/CorgEng.GenericInterfaces/UtilityTypes/IVector.cs b/CorgEng.GenericInterfaces/UtilityTypes/IVector.cs index a01f2243..64110ef9 100644 --- a/CorgEng.GenericInterfaces/UtilityTypes/IVector.cs +++ b/CorgEng.GenericInterfaces/UtilityTypes/IVector.cs @@ -6,11 +6,9 @@ namespace CorgEng.GenericInterfaces.UtilityTypes { - public interface IVector + public interface IVector : IListenable { - event EventHandler OnChange; - T this[int x] { get; set; } T X { get; set; } diff --git a/CorgEng.IconSmoothing/CorgEng.IconSmoothing.csproj b/CorgEng.IconSmoothing/CorgEng.IconSmoothing.csproj index 757ddd99..daef2c60 100644 --- a/CorgEng.IconSmoothing/CorgEng.IconSmoothing.csproj +++ b/CorgEng.IconSmoothing/CorgEng.IconSmoothing.csproj @@ -8,6 +8,7 @@ + diff --git a/CorgEng.IconSmoothing/Systems/SmoothIconSystem.cs b/CorgEng.IconSmoothing/Systems/SmoothIconSystem.cs index e7d57e17..15aa125f 100644 --- a/CorgEng.IconSmoothing/Systems/SmoothIconSystem.cs +++ b/CorgEng.IconSmoothing/Systems/SmoothIconSystem.cs @@ -23,11 +23,11 @@ internal class SmoothIconSystem : EntitySystem { [UsingDependency] - private static IWorld WorldAccess; + private static IEntityPositionTracker WorldAccess; public override EntitySystemFlags SystemFlags => EntitySystemFlags.HOST_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(OnEntityCreated); RegisterLocalEvent(OnEntityMoved); @@ -39,7 +39,7 @@ private void SmoothEntity(IEntity entity, SmoothIconComponent smoothIconComponen { if ((entity.EntityFlags & EntityComponentSystem.Entities.EntityFlags.DESTROYED) != 0) return; - IVector position = WorldAccess.GetGridPosition(smoothIconComponent.TransformComponent.Position); + IVector position = WorldAccess.GetGridPosition(smoothIconComponent.TransformComponent.Position.Value); IVector above = new Vector(position.X, position.Y + 1); IVector below = new Vector(position.X, position.Y - 1); IVector right = new Vector(position.X + 1, position.Y); @@ -102,7 +102,7 @@ private void OnComponentRemoved(IEntity entity, SmoothIconComponent trackCompone private void SmoothAround(SmoothIconComponent smoothIconComponent) { - IVector position = WorldAccess.GetGridPosition(smoothIconComponent.TransformComponent.Position); + IVector position = WorldAccess.GetGridPosition(smoothIconComponent.TransformComponent.Position.Value); IVector above = new Vector(position.X, position.Y + 1); IVector below = new Vector(position.X, position.Y - 1); IVector right = new Vector(position.X + 1, position.Y); diff --git a/CorgEng.InputHandling/ClickHandler/SelectableSystem.cs b/CorgEng.InputHandling/ClickHandler/SelectableSystem.cs index d15a37c6..01031db9 100644 --- a/CorgEng.InputHandling/ClickHandler/SelectableSystem.cs +++ b/CorgEng.InputHandling/ClickHandler/SelectableSystem.cs @@ -28,14 +28,11 @@ public class SelectableSystem : EntitySystem { [UsingDependency] - private static IWorld World = default!; + private static IEntityPositionTracker World = default!; [UsingDependency] private static IIconFactory IconFactory = default!; - [UsingDependency] - private static IEntityFactory EntityFactory = default!; - [UsingDependency] private static IInputHandler InputHandler = default!; @@ -50,7 +47,7 @@ public class SelectableSystem : EntitySystem private static SelectedComponent currentSelectedComponent; public static IEntity SelectedEntity { get; private set; } - public override void SystemSetup() + public override void SystemSetup(IWorld world) { selectorOverlay = IconFactory.CreateIcon("selector", 100, 1); RegisterLocalEvent(OnComponentAdded); diff --git a/CorgEng.InputHandling/CorgEng.InputHandling.csproj b/CorgEng.InputHandling/CorgEng.InputHandling.csproj index 2a7bc99c..a917bbf4 100644 --- a/CorgEng.InputHandling/CorgEng.InputHandling.csproj +++ b/CorgEng.InputHandling/CorgEng.InputHandling.csproj @@ -31,6 +31,7 @@ + diff --git a/CorgEng.InputHandling/InputHandler.cs b/CorgEng.InputHandling/InputHandler.cs index a26cb73d..352c7e7a 100644 --- a/CorgEng.InputHandling/InputHandler.cs +++ b/CorgEng.InputHandling/InputHandler.cs @@ -76,7 +76,7 @@ private void HandleScroll(IntPtr window, double x, double y) { //Synchronous to prevent subsystem overloading, will not render the //next frame until this is handled. - new MouseScrollEvent(y).RaiseGlobally(synchronous: true); + new MouseScrollEvent(y).RaiseGlobally(CorgEngMain.PrimaryWorld, synchronous: true); //Trigger the new actions foreach (string actionName in boundScrollActions) { @@ -96,7 +96,7 @@ private void HandleCursorMove(IntPtr window, double x, double y) { //Synchronous to prevent subsystem overloading, will not render the //next frame until this is handled. - new MouseMoveEvent(x / CorgEngMain.GameWindow.Width, y / CorgEngMain.GameWindow.Height).RaiseGlobally(synchronous: true); + new MouseMoveEvent(x / CorgEngMain.GameWindow.Width, y / CorgEngMain.GameWindow.Height).RaiseGlobally(CorgEngMain.PrimaryWorld, synchronous: true); //Trigger the new actions foreach (string actionName in boundMouseMoveActions) { @@ -134,7 +134,7 @@ private void HandleMousePress(IntPtr window, MouseButton button, InputState stat { return; } - mousePressEvent.RaiseGlobally(); + mousePressEvent.RaiseGlobally(CorgEngMain.PrimaryWorld); mouseDownAt = CorgEngMain.Time; //Trigger the action if (!boundMouseActions.TryGetValue(button, out action)) @@ -153,7 +153,7 @@ private void HandleMousePress(IntPtr window, MouseButton button, InputState stat MouseReleaseEvent mouseReleaseEvent = new MouseReleaseEvent(x / width, y / height, button, modifiers); mouseReleaseEvent.HeldTime = CorgEngMain.Time - mouseDownAt; //Raise synchronously, so we can determine if the event was handled - mouseReleaseEvent.RaiseGlobally(true); + mouseReleaseEvent.RaiseGlobally(CorgEngMain.PrimaryWorld, true); //Handle world clicks if (!mouseReleaseEvent.Handled && mouseReleaseEvent.MouseButton == MouseButton.Left) { @@ -192,7 +192,7 @@ private void HandleKeyboardPress(IntPtr window, Keys key, int scanCode, InputSta { case InputState.Press: KeyPressEvent keyPressEvent = new KeyPressEvent(key, mods); - keyPressEvent.RaiseGlobally(); + keyPressEvent.RaiseGlobally(CorgEngMain.PrimaryWorld); lock (heldKeysDownAt) { heldKeysDownAt.Add(key, CorgEngMain.Time); @@ -214,7 +214,7 @@ private void HandleKeyboardPress(IntPtr window, Keys key, int scanCode, InputSta try { KeyReleaseEvent keyReleaseEvent = new KeyReleaseEvent(key, mods); - keyReleaseEvent.RaiseGlobally(); + keyReleaseEvent.RaiseGlobally(CorgEngMain.PrimaryWorld); //Trigger the action if (!boundKeyActions.TryGetValue(key, out action)) return; @@ -270,57 +270,84 @@ public void WindowUpdate(Window targetWindow) public void AddKeybind(string action, Keys key) { - boundKeyActions.Add(key, action); + lock (boundKeyActions) + { + boundKeyActions.Add(key, action); + } } public void AddMouseButtonbind(string action, MouseButton key) { - boundMouseActions.Add(key, action); + lock (boundMouseActions) + { + boundMouseActions.Add(key, action); + } } public void AddMouseMoveBind(string action) { - boundMouseMoveActions.Add(action); + lock (boundMouseMoveActions) + { + boundMouseMoveActions.Add(action); + } } public void AddMouseScrollBind(string action) { - boundScrollActions.Add(action); + lock (boundScrollActions) + { + boundScrollActions.Add(action); + } } public void AddButtonPressAction(string actionKey, Func actionDelegate, int priority) { - if (!buttonDownActions.ContainsKey(actionKey)) - buttonDownActions.Add(actionKey, new SortedList>()); - buttonDownActions[actionKey].Add(priority, actionDelegate); + lock (buttonDownActions) + { + if (!buttonDownActions.ContainsKey(actionKey)) + buttonDownActions.Add(actionKey, new SortedList>()); + buttonDownActions[actionKey].Add(priority, actionDelegate); + } } public void AddButtonReleaseAction(string actionKey, Func actionDelegate, int priority) { - if (!buttonUpActions.ContainsKey(actionKey)) - buttonUpActions.Add(actionKey, new SortedList>()); - buttonUpActions[actionKey].Add(priority, actionDelegate); + lock (buttonUpActions) + { + if (!buttonUpActions.ContainsKey(actionKey)) + buttonUpActions.Add(actionKey, new SortedList>()); + buttonUpActions[actionKey].Add(priority, actionDelegate); + } } public void AddButtonHoldAction(string actionKey, Func actionDelegate, int priority) { - if (!buttonHoldActions.ContainsKey(actionKey)) - buttonHoldActions.Add(actionKey, new SortedList>()); - buttonHoldActions[actionKey].Add(priority, actionDelegate); + lock (buttonHoldActions) + { + if (!buttonHoldActions.ContainsKey(actionKey)) + buttonHoldActions.Add(actionKey, new SortedList>()); + buttonHoldActions[actionKey].Add(priority, actionDelegate); + } } public void AddMouseMoveAction(string actionKey, Func actionDelegate, int priority) { - if (!mouseMoveActions.ContainsKey(actionKey)) - mouseMoveActions.Add(actionKey, new SortedList>()); - mouseMoveActions[actionKey].Add(priority, actionDelegate); + lock (mouseMoveActions) + { + if (!mouseMoveActions.ContainsKey(actionKey)) + mouseMoveActions.Add(actionKey, new SortedList>()); + mouseMoveActions[actionKey].Add(priority, actionDelegate); + } } public void AddMouseScrollAction(string actionKey, Func actionDelegate, int priority) { - if (!scrollActions.ContainsKey(actionKey)) - scrollActions.Add(actionKey, new SortedList>()); - scrollActions[actionKey].Add(priority, actionDelegate); + lock (scrollActions) + { + if (!scrollActions.ContainsKey(actionKey)) + scrollActions.Add(actionKey, new SortedList>()); + scrollActions[actionKey].Add(priority, actionDelegate); + } } public void GetCursorPosition(out double x, out double y) diff --git a/CorgEng.Lighting/Components/LightingComponent.cs b/CorgEng.Lighting/Components/LightingComponent.cs index 0a9ccde5..35dedb5f 100644 --- a/CorgEng.Lighting/Components/LightingComponent.cs +++ b/CorgEng.Lighting/Components/LightingComponent.cs @@ -1,5 +1,4 @@ using CorgEng.EntityComponentSystem.Components; -using CorgEng.EntityComponentSystem.Implementations.Rendering.SpriteRendering; using CorgEng.GenericInterfaces.UtilityTypes; using CorgEng.UtilityTypes.Colours; using System; diff --git a/CorgEng.Lighting/CorgEng.Lighting.csproj b/CorgEng.Lighting/CorgEng.Lighting.csproj index 5cc306df..1179ac2c 100644 --- a/CorgEng.Lighting/CorgEng.Lighting.csproj +++ b/CorgEng.Lighting/CorgEng.Lighting.csproj @@ -9,6 +9,7 @@ + diff --git a/CorgEng.Lighting/RenderCores/LightingRenderCore.cs b/CorgEng.Lighting/RenderCores/LightingRenderCore.cs index e0599351..7f609cc2 100644 --- a/CorgEng.Lighting/RenderCores/LightingRenderCore.cs +++ b/CorgEng.Lighting/RenderCores/LightingRenderCore.cs @@ -1,6 +1,7 @@ using CorgEng.Core; using CorgEng.Core.Dependencies; using CorgEng.Core.Rendering; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Rendering.Renderers.SpriteRendering; using CorgEng.GenericInterfaces.UtilityTypes; using static OpenGL.Gl; @@ -15,28 +16,6 @@ public class LightingRenderCore : RenderCore [UsingDependency] private static ISpriteRendererFactory SpriteRendererFactory = default!; - private static LightingRenderCore? _singleton; - public static LightingRenderCore Singleton - { - get { - if (_singleton == null) - { - _singleton = new LightingRenderCore(); - _singleton.Initialize(); - } - return _singleton; - } - } - - public static void Resolve() - { - if (_singleton == null) - { - _singleton = new LightingRenderCore(); - _singleton.Initialize(); - } - } - public override RenderModes DrawMode => RenderModes.MULTIPLY; public override RenderModes BlendMode => RenderModes.ADDITIVE; @@ -45,6 +24,10 @@ public static void Resolve() public ISpriteRenderer lightRenderer = null!; + public LightingRenderCore(IWorld world) : base(world) + { + } + public override IColour BackColour => ColourFactory.GetColour(0.4f, 0.4f, 0.4f, 1); public override void Initialize() diff --git a/CorgEng.Lighting/Systems/LightingSystem.cs b/CorgEng.Lighting/Systems/LightingSystem.cs index 11a78ea3..aeb5b166 100644 --- a/CorgEng.Lighting/Systems/LightingSystem.cs +++ b/CorgEng.Lighting/Systems/LightingSystem.cs @@ -4,6 +4,7 @@ using CorgEng.EntityComponentSystem.Implementations.Rendering.SpriteRendering; using CorgEng.EntityComponentSystem.Systems; using CorgEng.GenericInterfaces.ContentLoading; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Rendering.Icons; using CorgEng.Lighting.Components; using CorgEng.Lighting.RenderCores; @@ -23,12 +24,9 @@ internal class LightingSystem : EntitySystem [UsingDependency] private static IIconFactory IconFactory = default!; - [UsingDependency] - private static IEntityCreator EntityCreator = default!; - public override EntitySystemFlags SystemFlags => EntitySystemFlags.CLIENT_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { //When the lighting object initialises, set it to start rendering on the specified system RegisterLocalEvent((entity, lightingComponent, signal) => { diff --git a/CorgEng.Networking/Components/ComponentExtensions.cs b/CorgEng.Networking/Components/ComponentExtensions.cs index c0a7dadf..4bc55479 100644 --- a/CorgEng.Networking/Components/ComponentExtensions.cs +++ b/CorgEng.Networking/Components/ComponentExtensions.cs @@ -2,6 +2,7 @@ using CorgEng.Core.Dependencies; using CorgEng.Core.Modules; using CorgEng.EntityComponentSystem.Components; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Logging; using CorgEng.GenericInterfaces.Networking.Attributes; using CorgEng.GenericInterfaces.Networking.Serialisation; @@ -48,7 +49,7 @@ public static void LoadPropertyInfoCache() //Locate all component types IEnumerable locatedComponentTypes = CorgEngMain.LoadedAssemblyModules .SelectMany(x => x.GetTypes()) - .Where(type => typeof(Component).IsAssignableFrom(type) && !type.IsAbstract); + .Where(type => typeof(IComponent).IsAssignableFrom(type) && !type.IsAbstract); //Populate the property info cache foreach (Type componentType in locatedComponentTypes) { diff --git a/CorgEng.Networking/Components/NetworkTransformComponent.cs b/CorgEng.Networking/Components/NetworkTransformComponent.cs index f09d11e2..d743e313 100644 --- a/CorgEng.Networking/Components/NetworkTransformComponent.cs +++ b/CorgEng.Networking/Components/NetworkTransformComponent.cs @@ -1,4 +1,7 @@ -using CorgEng.EntityComponentSystem.Implementations.Transform; +using CorgEng.Core.Dependencies; +using CorgEng.EntityComponentSystem.Implementations.Transform; +using CorgEng.GenericInterfaces.Networking.Clients; +using CorgEng.GenericInterfaces.Networking.Config; using System; using System.Collections.Generic; using System.Linq; @@ -13,5 +16,45 @@ namespace CorgEng.Networking.Components /// public sealed class NetworkTransformComponent : TransformComponent { + + [UsingDependency] + private static INetworkConfig NetworkConfig; + + /// + /// Automatically assign ownership on creation if we are the server. + /// + private bool _hasLocalOwnership = !NetworkConfig.NetworkingActive || NetworkConfig.ProcessServerSystems; + + /// + /// Are we the management of ownership? + /// + private bool _isOwnershipManager = !NetworkConfig.NetworkingActive || NetworkConfig.ProcessServerSystems; + + /// + /// The current owner of this transform component as determined by the ownership manager. + /// + internal IClient currentOwner; + + /// + /// Returns true if the local application has ownership of this network transform. + /// + public bool HasOwnership + { + get + { + return _hasLocalOwnership; + } + } + + /// + /// Are we the set ownership manager? + /// The ownership manager will be the server, they are the one that + /// verifies any attempted modifications of the entity. + /// + public bool IsOwnershipManager + { + get { return _isOwnershipManager; } + } + } } diff --git a/CorgEng.Networking/EntitySystems/ClientSystem.cs b/CorgEng.Networking/EntitySystems/ClientSystem.cs index 7e45ab61..f9a7bc2d 100644 --- a/CorgEng.Networking/EntitySystems/ClientSystem.cs +++ b/CorgEng.Networking/EntitySystems/ClientSystem.cs @@ -11,6 +11,7 @@ using CorgEng.GenericInterfaces.World; using CorgEng.Networking.Components; using CorgEng.Networking.Events; +using CorgEng.Networking.Networking; using System; using System.Collections.Generic; using System.IO; @@ -31,7 +32,9 @@ internal class ClientSystem : EntitySystem private static ILogger Logger; [UsingDependency] - private static IEntityCommunicator EntityCommunicator; + private static IEntityCommunicatorFactory EntityCommunicatorFactory; + + private IEntityCommunicator EntityCommunicator; [UsingDependency] private static IServerCommunicator ServerCommunicator; @@ -40,12 +43,13 @@ internal class ClientSystem : EntitySystem private static INetworkMessageFactory NetworkMessageFactory; [UsingDependency] - private static IWorld WorldAccess; + private static IEntityPositionTracker WorldAccess; public override EntitySystemFlags SystemFlags { get; } = EntitySystemFlags.HOST_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { + EntityCommunicator = EntityCommunicatorFactory.CreateEntityCommunicator(world); RegisterLocalEvent(OnClientConnected); RegisterLocalEvent(OnClientMoved); RegisterLocalEvent(OnEntityDeleted); diff --git a/CorgEng.Networking/EntitySystems/DelayedEventSystem.cs b/CorgEng.Networking/EntitySystems/DelayedEventSystem.cs index 4d65c1a7..a57d0ffb 100644 --- a/CorgEng.Networking/EntitySystems/DelayedEventSystem.cs +++ b/CorgEng.Networking/EntitySystems/DelayedEventSystem.cs @@ -26,7 +26,7 @@ internal static void AddDelayedEvent(uint identifier, Action callbackAc } } - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent((entity, component, signal) => { List> actionsToPerform; diff --git a/CorgEng.Networking/EntitySystems/NetworkSystem.cs b/CorgEng.Networking/EntitySystems/NetworkSystem.cs index 3cea5d11..68f0bb94 100644 --- a/CorgEng.Networking/EntitySystems/NetworkSystem.cs +++ b/CorgEng.Networking/EntitySystems/NetworkSystem.cs @@ -12,6 +12,7 @@ using CorgEng.GenericInterfaces.Networking.Networking.Server; using CorgEng.GenericInterfaces.Networking.Packets; using CorgEng.Networking.Components; +using CorgEng.Networking.Networking; using CorgEng.Networking.Networking.Server; using CorgEng.Networking.VersionSync; using CorgEng.UtilityTypes.Vectors; @@ -40,19 +41,19 @@ public class NetworkSystem : EntitySystem private static INetworkMessageFactory NetworkMessageFactory; [UsingDependency] - private static IEntityCommunicator EntityCommunicator; + private static IEntityCommunicatorFactory EntityCommunicatorFactory; - [UsingDependency] - private static INetworkingServer NetworkingServer; + private IEntityCommunicator EntityCommunicator; public override EntitySystemFlags SystemFlags { get; } = EntitySystemFlags.HOST_SYSTEM | EntitySystemFlags.CLIENT_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { + EntityCommunicator = EntityCommunicatorFactory.CreateEntityCommunicator(world); RegisterLocalEvent(OnComponentAdded); RegisterLocalEvent(OnNetworkedEventRaised); RegisterLocalEvent((e, c, s) => { - foreach (IClient client in ((NetworkingServer)NetworkingServer).connectedClients.Values) + foreach (IClient client in ((NetworkServer)world.ServerInstance).connectedClients.Values) EntityCommunicator.CommunicateEntity(e, client); }); RegisterGlobalEvent(OnGlobalNetworkedEventRaised); @@ -100,7 +101,7 @@ private void OnNetworkedEventRaised(IEntity entity, NetworkTransformComponent tr { ServerCommunicator?.SendToReleventClients( NetworkMessageFactory.CreateMessage(PacketHeaders.LOCAL_EVENT_RAISED, InjectEventCode(networkedEventRaisedEvent.RaisedEvent, entity)), - transformComponent.Position, + transformComponent.Position.Value, new Vector(1, 1, 1) ); } diff --git a/CorgEng.Networking/Networking/Client/ClientCommunicator.cs b/CorgEng.Networking/Networking/Client/ClientCommunicator.cs index 4488aafd..a0c897dd 100644 --- a/CorgEng.Networking/Networking/Client/ClientCommunicator.cs +++ b/CorgEng.Networking/Networking/Client/ClientCommunicator.cs @@ -1,4 +1,6 @@ -using CorgEng.DependencyInjection.Dependencies; +using CorgEng.Core.Dependencies; +using CorgEng.DependencyInjection.Dependencies; +using CorgEng.GenericInterfaces.Logging; using CorgEng.GenericInterfaces.Networking.Networking.Client; using CorgEng.GenericInterfaces.Networking.Packets; using System; @@ -13,10 +15,18 @@ namespace CorgEng.Networking.Networking.Client internal class ClientCommunicator : IClientCommunicator { - public static NetworkingClient client; + [UsingDependency] + private static ILogger Logger; + + public static NetworkClient client; public void SendToServer(INetworkMessage networkMessage) { + if (client == null) + { + Logger.WriteLine("Attempted to send a message to the server while not connected.", LogType.WARNING); + return; + } client.QueueMessage(networkMessage); } diff --git a/CorgEng.Networking/Networking/Client/NetworkingClient.cs b/CorgEng.Networking/Networking/Client/NetworkClient.cs similarity index 95% rename from CorgEng.Networking/Networking/Client/NetworkingClient.cs rename to CorgEng.Networking/Networking/Client/NetworkClient.cs index c446db1e..4888a860 100644 --- a/CorgEng.Networking/Networking/Client/NetworkingClient.cs +++ b/CorgEng.Networking/Networking/Client/NetworkClient.cs @@ -39,8 +39,7 @@ namespace CorgEng.Networking.Networking.Client /// This is absolutely awful and I hate it, will probably rewrite it in the future /// to iron out some of the oversights. /// - [Dependency] - internal class NetworkingClient : NetworkCommunicator, INetworkingClient + public class NetworkClient : NetworkCommunicator, INetworkClient { [UsingDependency] @@ -56,7 +55,9 @@ internal class NetworkingClient : NetworkCommunicator, INetworkingClient private static INetworkConfig NetworkConfig; [UsingDependency] - private static IEntityCommunicator EntityCommunicator; + private static IEntityCommunicatorFactory EntityCommunicatorFactory; + + private IEntityCommunicator EntityCommunicator; [UsingDependency] private static IPrototypeManager PrototypeManager; @@ -68,6 +69,14 @@ internal class NetworkingClient : NetworkCommunicator, INetworkingClient public event ConnectionFailed OnConnectionFailed; + private IWorld attachedWorld; + + public NetworkClient(IWorld world) + { + attachedWorld = world; + EntityCommunicator = EntityCommunicatorFactory.CreateEntityCommunicator(world); + } + /// /// Attempt connection to a network address /// @@ -262,7 +271,7 @@ protected override void HandleMessage(IPEndPoint sender, PacketHeaders header, b ushort networkedIdentifier = reader.ReadUInt16(); //Read the target entity uint entityIdentifier = reader.ReadUInt32(); - IEntity entityTarget = EntityManager.GetEntity(entityIdentifier); + IEntity entityTarget = attachedWorld.EntityManager.GetEntity(entityIdentifier); //Get the event that was raised INetworkedEvent raisedEvent = VersionGenerator.CreateTypeFromIdentifier(networkedIdentifier); //Deserialize the event @@ -301,7 +310,7 @@ protected override void HandleMessage(IPEndPoint sender, PacketHeaders header, b Logger.WriteMetric("networked_global_event", raisedEvent.GetType().ToString()); //Deserialize the event raisedEvent.Deserialise(reader); - raisedEvent.RaiseGlobally(false); + raisedEvent.RaiseGlobally(CorgEngMain.PrimaryWorld, false); } } return; @@ -313,7 +322,7 @@ protected override void HandleMessage(IPEndPoint sender, PacketHeaders header, b Task.Run(async () => { IEntity createdEntity = await EntityCommunicator.DeserialiseEntity(data.Skip(start).Take(length).ToArray()); - EntityManager.RegisterEntity(createdEntity); + attachedWorld.EntityManager.RegisterEntity(createdEntity); }); return; case PacketHeaders.UPDATE_CLIENT_VIEW: @@ -332,7 +341,7 @@ protected override void HandleMessage(IPEndPoint sender, PacketHeaders header, b double viewOffsetHeight = reader.ReadDouble(); //Update our view //Send a signal - new ModifyIsometricView(viewX + viewOffsetX, viewY + viewOffsetY, viewZ, viewOffsetWidth, viewOffsetHeight).RaiseGlobally(); + new ModifyIsometricView(viewX + viewOffsetX, viewY + viewOffsetY, viewZ, viewOffsetWidth, viewOffsetHeight).RaiseGlobally(CorgEngMain.PrimaryWorld); } } return; diff --git a/CorgEng.Networking/Networking/Client/NetworkClientFactory.cs b/CorgEng.Networking/Networking/Client/NetworkClientFactory.cs new file mode 100644 index 00000000..80c6d0c9 --- /dev/null +++ b/CorgEng.Networking/Networking/Client/NetworkClientFactory.cs @@ -0,0 +1,23 @@ +using CorgEng.Core.Dependencies; +using CorgEng.DependencyInjection.Dependencies; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using CorgEng.GenericInterfaces.Networking.Networking.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.Networking.Networking.Client +{ + [Dependency] + internal class NetworkClientFactory : INetworkClientFactory + { + + public INetworkClient CreateNetworkClient(IWorld world) + { + return new NetworkClient(world); + } + + } +} diff --git a/CorgEng.Networking/Networking/Server/EntityCommunicator.cs b/CorgEng.Networking/Networking/EntityCommunicator.cs similarity index 95% rename from CorgEng.Networking/Networking/Server/EntityCommunicator.cs rename to CorgEng.Networking/Networking/EntityCommunicator.cs index fc637e7a..8c3dfc11 100644 --- a/CorgEng.Networking/Networking/Server/EntityCommunicator.cs +++ b/CorgEng.Networking/Networking/EntityCommunicator.cs @@ -23,9 +23,8 @@ using System.Text; using System.Threading.Tasks; -namespace CorgEng.Networking.Networking.Server +namespace CorgEng.Networking.Networking { - [Dependency] internal class EntityCommunicator : IEntityCommunicator { @@ -44,6 +43,13 @@ internal class EntityCommunicator : IEntityCommunicator [UsingDependency] private static INetworkMessageFactory NetworkMessageFactory; + internal IWorld World { get; } + + public EntityCommunicator(IWorld world) + { + World = world; + } + /// /// Communicate information about an entity to a client. /// We need to include: @@ -71,14 +77,14 @@ public async Task DeserialiseEntity(byte[] data) uint entityIdentifier = binaryReader.ReadUInt32(); uint prototypeIdentifier = binaryReader.ReadUInt32(); //Create or locate the entity - IEntity entity = EntityManager.GetEntity(entityIdentifier); + IEntity entity = World.EntityManager.GetEntity(entityIdentifier); if (entity == null) { //We need to create the entity //Create it based on the prototype we have, which we need to request from the server if we don't have it already IPrototype locatedPrototype = await PrototypeManager.GetPrototype(prototypeIdentifier); //Create the entity with a specific identifier - entity = locatedPrototype.CreateEntityFromPrototype(entityIdentifier); + entity = locatedPrototype.CreateEntityFromPrototype(World, entityIdentifier); } else { diff --git a/CorgEng.Networking/Networking/EntityCommunicatorFactory.cs b/CorgEng.Networking/Networking/EntityCommunicatorFactory.cs new file mode 100644 index 00000000..e1072672 --- /dev/null +++ b/CorgEng.Networking/Networking/EntityCommunicatorFactory.cs @@ -0,0 +1,20 @@ +using CorgEng.DependencyInjection.Dependencies; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using CorgEng.GenericInterfaces.Networking.Networking.Server; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.Networking.Networking +{ + [Dependency] + internal class EntityCommunicatorFactory : IEntityCommunicatorFactory + { + public IEntityCommunicator CreateEntityCommunicator(IWorld world) + { + return new EntityCommunicator(world); + } + } +} diff --git a/CorgEng.Networking/Networking/NetworkCommunicator.cs b/CorgEng.Networking/Networking/NetworkCommunicator.cs index 73fd38a1..846cd9cc 100644 --- a/CorgEng.Networking/Networking/NetworkCommunicator.cs +++ b/CorgEng.Networking/Networking/NetworkCommunicator.cs @@ -2,6 +2,7 @@ using CorgEng.Core.Dependencies; using CorgEng.Core.Modules; using CorgEng.DependencyInjection.Dependencies; +using CorgEng.EntityComponentSystem.Components.ComponentVariables.Networking; using CorgEng.EntityComponentSystem.Entities; using CorgEng.EntityComponentSystem.Events; using CorgEng.EntityComponentSystem.Events.Events; @@ -16,6 +17,7 @@ using CorgEng.GenericInterfaces.Networking.Packets.PacketQueues; using CorgEng.GenericInterfaces.Networking.PrototypeManager; using CorgEng.GenericInterfaces.Rendering; +using CorgEng.GenericInterfaces.Serialization; using CorgEng.Networking.Components; using CorgEng.Networking.EntitySystems; using CorgEng.Networking.Events; @@ -36,7 +38,7 @@ namespace CorgEng.Networking.Networking { - internal abstract class NetworkCommunicator + public abstract class NetworkCommunicator { [UsingDependency] @@ -51,6 +53,9 @@ internal abstract class NetworkCommunicator [UsingDependency] private static IQueuedPacketFactory QueuedPacketFactory; + [UsingDependency] + private static IAutoSerialiser AutoSerialiser; + public IClientAddressingTable ClientAddressingTable { get; private set; } protected IPacketQueue PacketQueue; @@ -133,6 +138,8 @@ protected void NetworkSenderThread(UdpClient client) { //Create a stopwatch to get the current time stopwatch.Restart(); + //Create messages if needed + UpdateDirtyNetvars(); //Transmit packets while (PacketQueue.AcquireLockIfHasMessages()) { @@ -304,6 +311,27 @@ private void ProcessPacket(IPEndPoint sender, byte[] data) } } + private void UpdateDirtyNetvars() + { + lock (NetVar.DirtyNetvars) + { + // Get the values and packetise them + foreach (INetVar netVar in NetVar.DirtyNetvars) + { + object value = netVar.GetValue(); + byte[] serialisedValue = new byte[AutoSerialiser.SerialisationLength(netVar.GetStoredType(), value)]; + using (BinaryWriter binaryWriter = new BinaryWriter(new MemoryStream(serialisedValue))) + { + AutoSerialiser.SerializeInto(netVar.GetStoredType(), value, binaryWriter); + } + INetworkMessage networkMessage = NetworkMessageFactory.CreateMessage(PacketHeaders.NETVAR_VALUE_UPDATED, serialisedValue); + PacketQueue.QueueMessage(ClientAddressingTable?.GetEveryone() ?? null, networkMessage); + } + // Reset the list + NetVar.DirtyNetvars.Clear(); + } + } + /// /// Is the provided address the address of someone connected / what we are connected to? /// diff --git a/CorgEng.Networking/Networking/Server/NetworkingServer.cs b/CorgEng.Networking/Networking/Server/NetworkServer.cs similarity index 93% rename from CorgEng.Networking/Networking/Server/NetworkingServer.cs rename to CorgEng.Networking/Networking/Server/NetworkServer.cs index e3910538..69dbe6ef 100644 --- a/CorgEng.Networking/Networking/Server/NetworkingServer.cs +++ b/CorgEng.Networking/Networking/Server/NetworkServer.cs @@ -33,36 +33,31 @@ namespace CorgEng.Networking.Networking.Server { - - [Dependency] - internal class NetworkingServer : NetworkCommunicator, INetworkingServer + public class NetworkServer : NetworkCommunicator, INetworkServer { [UsingDependency] - private static ILogger Logger; + private static ILogger Logger = null!; [UsingDependency] - private static IClientFactory ClientFactory; + private static IClientFactory ClientFactory = null!; [UsingDependency] - private static IClientAddressingTableFactory ClientAddressingTableFactory; + private static IClientAddressingTableFactory ClientAddressingTableFactory = null!; public IClientAddressingTable ClientAddressingTable { get; private set; } [UsingDependency] - private static INetworkMessageFactory NetworkMessageFactory; - - [UsingDependency] - private static IPacketQueueFactory PacketQueueFactory; + private static INetworkMessageFactory NetworkMessageFactory = null!; [UsingDependency] - private static INetworkConfig NetworkConfig; + private static IPacketQueueFactory PacketQueueFactory = null!; [UsingDependency] - private static IPrototypeManager PrototypeManager; + private static INetworkConfig NetworkConfig = null!; [UsingDependency] - private static IEntityFactory EntityFactory; + private static IPrototypeManager PrototypeManager = null!; private static IPrototype DefaultEntityPrototype; @@ -73,11 +68,17 @@ internal class NetworkingServer : NetworkCommunicator, INetworkingServer private double lastPingAt = 0; - [ModuleLoad] + private IWorld world; + + public NetworkServer(IWorld world) + { + this.world = world; + } + public void LoadDefaultPrototype() { //Set the default prototype - EntityFactory.CreateEmptyEntity(sampleEntity => { + world.EntityManager.CreateEmptyEntity(sampleEntity => { sampleEntity.AddComponent(new NetworkTransformComponent()); sampleEntity.AddComponent(new ClientComponent()); //Get the prototype @@ -110,6 +111,13 @@ public virtual void StartHosting(int port) } //Start networking NetworkConfig.NetworkingActive = true; + NetworkConfig.ProcessServerSystems = true; + // Load the default player prototype if it hasn't been set. + // Must be done after we activate the server system processing flag + if (DefaultEntityPrototype == null) + { + LoadDefaultPrototype(); + } //Log Logger?.WriteLine($"Starting UDP server on port {port}.", LogType.MESSAGE); //Set the listening port @@ -134,7 +142,6 @@ public virtual void StartHosting(int port) shutdownCountdown.Reset(); started = true; ServerCommunicator.server = this; - NetworkConfig.ProcessServerSystems = true; } protected override void HandleMessage(IPEndPoint sender, PacketHeaders header, byte[] data, int start, int length) @@ -182,7 +189,7 @@ protected override void HandleMessage(IPEndPoint sender, PacketHeaders header, b Logger.WriteMetric("networked_global_event", raisedEvent.ToString()); //Deserialize the event raisedEvent.Deserialise(reader); - raisedEvent.RaiseGlobally(false); + raisedEvent.RaiseGlobally(world, false); } } return; @@ -253,7 +260,7 @@ private void HandleConnectionRequest(IPEndPoint sender, byte[] data, int start, ClientAddressingTable.AddClient(createdClient), NetworkMessageFactory.CreateMessage(PacketHeaders.CONNECTION_ACCEPT, new byte[0])); //Create a new client entity and add what we need - IEntity createdEntity = DefaultEntityPrototype.CreateEntityFromPrototype(); + IEntity createdEntity = DefaultEntityPrototype.CreateEntityFromPrototype(world); //Send a connection event new ClientConnectedEvent(createdClient).Raise(createdEntity); //Initialise the entity diff --git a/CorgEng.Networking/Networking/Server/NetworkServerFactory.cs b/CorgEng.Networking/Networking/Server/NetworkServerFactory.cs new file mode 100644 index 00000000..bb414de9 --- /dev/null +++ b/CorgEng.Networking/Networking/Server/NetworkServerFactory.cs @@ -0,0 +1,22 @@ +using CorgEng.DependencyInjection.Dependencies; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using CorgEng.GenericInterfaces.Networking.Networking.Server; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.Networking.Networking.Server +{ + [Dependency] + internal class NetworkServerFactory : INetworkServerFactory + { + + public INetworkServer CreateNetworkServer(IWorld world) + { + return new NetworkServer(world); + } + + } +} diff --git a/CorgEng.Networking/Networking/Server/ServerCommunicator.cs b/CorgEng.Networking/Networking/Server/ServerCommunicator.cs index 727d4529..d9e1b66f 100644 --- a/CorgEng.Networking/Networking/Server/ServerCommunicator.cs +++ b/CorgEng.Networking/Networking/Server/ServerCommunicator.cs @@ -19,7 +19,7 @@ internal class ServerCommunicator : IServerCommunicator [UsingDependency] private static IClientAddressFactory ClientAddressFactory; - public static NetworkingServer server; + public static NetworkServer server; public bool IsServer => server != null; diff --git a/CorgEng.Networking/Prototypes/Prototype.cs b/CorgEng.Networking/Prototypes/Prototype.cs index 45c32e15..66b445e6 100644 --- a/CorgEng.Networking/Prototypes/Prototype.cs +++ b/CorgEng.Networking/Prototypes/Prototype.cs @@ -1,5 +1,7 @@ using CorgEng.Core.Dependencies; using CorgEng.EntityComponentSystem.Entities; +using CorgEng.EntityComponentSystem.Events; +using CorgEng.EntityComponentSystem.Events.Events; using CorgEng.EntityComponentSystem.Implementations.Deletion; using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Logging; @@ -62,14 +64,14 @@ public void GenerateFromEntity(IEntity entity) } } - public IEntity CreateEntityFromPrototype() + public IEntity CreateEntityFromPrototype(IWorld world) { - return CreateEntityFromPrototype(EntityManager.GetNewEntityId()); + return CreateEntityFromPrototype(world, world.EntityManager.GetNewEntityId()); } - public IEntity CreateEntityFromPrototype(uint entityIdentifier) + public IEntity CreateEntityFromPrototype(IWorld world, uint entityIdentifier) { - IEntity createdEntity = new Entity(entityIdentifier); + IEntity createdEntity = world.EntityManager.CreateUninitialisedEntity(entityIdentifier); //Add components foreach (Type type in prototypeComponents.Keys) { @@ -83,6 +85,8 @@ public IEntity CreateEntityFromPrototype(uint entityIdentifier) //Add the component createdEntity.AddComponent(createdComponent); } + //Raise the initialise event + new InitialiseEvent().Raise(createdEntity, true); return createdEntity; } diff --git a/CorgEng.Networking/Serialization/AutoSerialiser.cs b/CorgEng.Networking/Serialization/AutoSerialiser.cs index cdb97cd6..5e816298 100644 --- a/CorgEng.Networking/Serialization/AutoSerialiser.cs +++ b/CorgEng.Networking/Serialization/AutoSerialiser.cs @@ -16,18 +16,31 @@ namespace CorgEng.Networking.Serialization [Dependency] internal class AutoSerialiser : IAutoSerialiser { + + private Type ConstructGenericType(Type startingType, BinaryReader binaryReader) + { + Type nextType = VersionGenerator.GetTypeFromNetworkedIdentifier(binaryReader.ReadUInt16()); + if (nextType.IsGenericType) + { + Type nextGenericType = ConstructGenericType(nextType, binaryReader); + nextType = nextType.MakeGenericType(nextGenericType); + } + return nextType; + } + public object Deserialize(Type deserialisationType, BinaryReader binaryReader) { //Set the value based on the property type if (typeof(ICustomSerialisationBehaviour).IsAssignableFrom(deserialisationType)) { + // Wait, why do we need to do any of this if we already know the type we are deserialising?? + // ^ Because subtypes can be assigned to variables of a parent type. Type type = VersionGenerator.GetTypeFromNetworkedIdentifier(binaryReader.ReadUInt16()); //Generic types need additional handling in order to determine the contained generic type if (type.IsGenericType) { - //Doesn't allow for embedded generic types, would be easy to implement with a recursive function however - Type genericType = VersionGenerator.GetTypeFromNetworkedIdentifier(binaryReader.ReadUInt16()); ; - type = type.MakeGenericType(genericType); + Type createdGenericType = ConstructGenericType(type, binaryReader); + type = type.MakeGenericType(createdGenericType); } ICustomSerialisationBehaviour createdObject = (ICustomSerialisationBehaviour)FormatterServices.GetUninitializedObject(type); createdObject.DeserialiseFrom(binaryReader); @@ -79,7 +92,13 @@ public int SerialisationLength(Type type, object value) } if (value is ICustomSerialisationBehaviour serialisationBehaviour) { - int genericSize = value.GetType().IsGenericType ? sizeof(ushort) : 0; + int genericSize = 0; + Type current = value.GetType(); + while (current.IsGenericType) + { + genericSize += sizeof(ushort); + current = current.GetGenericArguments()[0]; + } return sizeof(ushort) + genericSize + serialisationBehaviour.GetSerialisationLength(); } if (type.IsPrimitive) @@ -97,10 +116,12 @@ public void SerializeInto(Type type, object value, BinaryWriter binaryWriter) if (typeof(ICustomSerialisationBehaviour).IsAssignableFrom(objectType)) { binaryWriter.Write(VersionGenerator.GetNetworkedIdentifier(value.GetType())); - if (value.GetType().IsGenericType) + Type current = value.GetType(); + while (current.IsGenericType) { //TODO: Add embedded recursive types and multiple recursive type arguments. - binaryWriter.Write(VersionGenerator.GetNetworkedIdentifier(value.GetType().GenericTypeArguments[0])); + current = current.GenericTypeArguments[0]; + binaryWriter.Write(VersionGenerator.GetNetworkedIdentifier(current)); } ((ICustomSerialisationBehaviour)value).SerialiseInto(binaryWriter); } diff --git a/CorgEng.NetworkingPrimitives/CorgEng.NetworkingPrimitives.csproj b/CorgEng.NetworkingPrimitives/CorgEng.NetworkingPrimitives.csproj new file mode 100644 index 00000000..132c02c5 --- /dev/null +++ b/CorgEng.NetworkingPrimitives/CorgEng.NetworkingPrimitives.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/CorgEng.NetworkingPrimitives/Networking/INetVar.cs b/CorgEng.NetworkingPrimitives/Networking/INetVar.cs new file mode 100644 index 00000000..78c84268 --- /dev/null +++ b/CorgEng.NetworkingPrimitives/Networking/INetVar.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.NetworkingPrimitives.Variables +{ + internal interface INetVar + { + + Type GetStoredType(); + + object GetValue(); + + } +} diff --git a/CorgEng.NetworkingPrimitives/Networking/NetCVar.cs b/CorgEng.NetworkingPrimitives/Networking/NetCVar.cs new file mode 100644 index 00000000..ad124319 --- /dev/null +++ b/CorgEng.NetworkingPrimitives/Networking/NetCVar.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.NetworkingPrimitives.Variables +{ + + internal static class NetVar + { + internal static HashSet DirtyNetvars = new HashSet(); + } + + /** + * Networked version of a CVar + */ + public class NetCVar : CVar, INetVar + { + + private static ulong _netVarCount = 0; + + public ulong NetVarID { get; private set; } + + public bool PrototypeSerialised { get; set; } + + public NetCVar() + { + // Set the NetVarID + NetVarID = _netVarCount++; + } + + public override void TriggerChanged() + { + base.TriggerChanged(); + MarkDirty(); + } + + public void MarkDirty() + { + lock (NetVar.DirtyNetvars) + { + if (NetVar.DirtyNetvars.Contains(this)) + return; + NetVar.DirtyNetvars.Add(this); + } + } + + public NetCVar SetPrototypeSerialised(bool value) + { + PrototypeSerialised = value; + return this; + } + + public object GetValue() + { + return Value; + } + + public Type GetStoredType() + { + return typeof(T); + } + } +} diff --git a/CorgEng.Pathfinding/Queryers/WorldPathCellQueryer.cs b/CorgEng.Pathfinding/Queryers/WorldPathCellQueryer.cs index 7eaee559..2193b5ec 100644 --- a/CorgEng.Pathfinding/Queryers/WorldPathCellQueryer.cs +++ b/CorgEng.Pathfinding/Queryers/WorldPathCellQueryer.cs @@ -16,7 +16,7 @@ public class WorldPathCellQueryer : IPathCellQueryer { [UsingDependency] - private static IWorld WorldAccess; + private static IEntityPositionTracker WorldAccess; public int EnterPositionCost(IVector position, Direction enterDirection) { diff --git a/CorgEng.Pathfinding/Systems/SolidSystem.cs b/CorgEng.Pathfinding/Systems/SolidSystem.cs index f5b02964..a3d1b0eb 100644 --- a/CorgEng.Pathfinding/Systems/SolidSystem.cs +++ b/CorgEng.Pathfinding/Systems/SolidSystem.cs @@ -19,7 +19,7 @@ internal class SolidSystem : EntitySystem { [UsingDependency] - private static IWorld WorldAccess; + private static IEntityPositionTracker WorldAccess; /// /// The world layers @@ -28,7 +28,7 @@ internal class SolidSystem : EntitySystem public override EntitySystemFlags SystemFlags => EntitySystemFlags.HOST_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(OnComponentAdded); RegisterLocalEvent(OnComponentRemoved); @@ -49,7 +49,7 @@ private void OnComponentAdded(IEntity entity, SolidComponent solidComponent, Com WorldLayers.Add(0, new WorldGrid()); } //Get the position - IVector gridPosition = WorldAccess.GetGridPosition(attachedTransformComponent.Position); + IVector gridPosition = WorldAccess.GetGridPosition(attachedTransformComponent.Position.Value); int positionX = gridPosition.X; int positionY = gridPosition.Y; WorldLayers[0].AddElement(positionX, positionY); @@ -69,7 +69,7 @@ private void OnComponentRemoved(IEntity entity, SolidComponent solidComponent, C return; } //Get the position - IVector gridPosition = WorldAccess.GetGridPosition(attachedTransformComponent.Position); + IVector gridPosition = WorldAccess.GetGridPosition(attachedTransformComponent.Position.Value); int positionX = gridPosition.X; int positionY = gridPosition.Y; WorldLayers[0].RemoveElement(positionX, positionY); diff --git a/CorgEng.Rendering/ComponentSystem/DepthParallax/DepthSpriteRenderComponent.cs b/CorgEng.Rendering/ComponentSystem/DepthParallax/DepthSpriteRenderComponent.cs new file mode 100644 index 00000000..535d24ce --- /dev/null +++ b/CorgEng.Rendering/ComponentSystem/DepthParallax/DepthSpriteRenderComponent.cs @@ -0,0 +1,34 @@ +using CorgEng.EntityComponentSystem.Implementations.Rendering.SpriteRendering; +using CorgEng.GenericInterfaces.Networking.Attributes; +using CorgEng.GenericInterfaces.Rendering.Renderers.SpriteRendering; +using CorgEng.Rendering.DepthParallax; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.Rendering.ComponentSystem.DepthParallax +{ + public class DepthSpriteRenderComponent : SpriteRenderComponent + { + + public override ISpriteRenderer SpriteRenderer + { + get + { + if (SpriteRendererIdentifier == 0) + return null; + if (_spriteRenderer == null || cachedSpriteRendererIdentifier != SpriteRendererIdentifier) + { + if (!ParallaxLayerRenderCore.parallaxRenderCores.ContainsKey((int)SpriteRendererIdentifier)) + return null; + _spriteRenderer = ParallaxLayerRenderCore.parallaxRenderCores[(int)SpriteRendererIdentifier][(int)Sprite.Layer-1].renderer; + cachedSpriteRendererIdentifier = SpriteRendererIdentifier; + } + return _spriteRenderer; + } + } + + } +} diff --git a/CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/AddOverlayEvent.cs b/CorgEng.Rendering/ComponentSystem/SpriteRendering/AddOverlayEvent.cs similarity index 100% rename from CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/AddOverlayEvent.cs rename to CorgEng.Rendering/ComponentSystem/SpriteRendering/AddOverlayEvent.cs diff --git a/CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/RemoveOverlayEvent.cs b/CorgEng.Rendering/ComponentSystem/SpriteRendering/RemoveOverlayEvent.cs similarity index 100% rename from CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/RemoveOverlayEvent.cs rename to CorgEng.Rendering/ComponentSystem/SpriteRendering/RemoveOverlayEvent.cs diff --git a/CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/SetDirectionEvent.cs b/CorgEng.Rendering/ComponentSystem/SpriteRendering/SetDirectionEvent.cs similarity index 100% rename from CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/SetDirectionEvent.cs rename to CorgEng.Rendering/ComponentSystem/SpriteRendering/SetDirectionEvent.cs diff --git a/CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/SetSpriteEvent.cs b/CorgEng.Rendering/ComponentSystem/SpriteRendering/SetSpriteEvent.cs similarity index 100% rename from CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/SetSpriteEvent.cs rename to CorgEng.Rendering/ComponentSystem/SpriteRendering/SetSpriteEvent.cs diff --git a/CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/SetSpriteRendererEvent.cs b/CorgEng.Rendering/ComponentSystem/SpriteRendering/SetSpriteRendererEvent.cs similarity index 100% rename from CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/SetSpriteRendererEvent.cs rename to CorgEng.Rendering/ComponentSystem/SpriteRendering/SetSpriteRendererEvent.cs diff --git a/CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/SpriteRenderComponent.cs b/CorgEng.Rendering/ComponentSystem/SpriteRendering/SpriteRenderComponent.cs similarity index 94% rename from CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/SpriteRenderComponent.cs rename to CorgEng.Rendering/ComponentSystem/SpriteRendering/SpriteRenderComponent.cs index 75614292..dff5d587 100644 --- a/CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/SpriteRenderComponent.cs +++ b/CorgEng.Rendering/ComponentSystem/SpriteRendering/SpriteRenderComponent.cs @@ -58,11 +58,11 @@ public class SpriteRenderComponent : Component, IInstantiatable internal bool WantsToRender { get; set; } = true; - private uint cachedSpriteRendererIdentifier = 0; + protected uint cachedSpriteRendererIdentifier = 0; - private ISpriteRenderer _spriteRenderer; + protected ISpriteRenderer _spriteRenderer; - public ISpriteRenderer SpriteRenderer + public virtual ISpriteRenderer SpriteRenderer { get { diff --git a/CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/SpriteRenderSystem.cs b/CorgEng.Rendering/ComponentSystem/SpriteRendering/SpriteRenderSystem.cs similarity index 98% rename from CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/SpriteRenderSystem.cs rename to CorgEng.Rendering/ComponentSystem/SpriteRendering/SpriteRenderSystem.cs index bb2c94ce..68823767 100644 --- a/CorgEng.EntityComponentSystem/Implementations/Rendering/SpriteRendering/SpriteRenderSystem.cs +++ b/CorgEng.Rendering/ComponentSystem/SpriteRendering/SpriteRenderSystem.cs @@ -1,5 +1,6 @@ using CorgEng.Core; using CorgEng.Core.Dependencies; +using CorgEng.EntityComponentSystem.Components; using CorgEng.EntityComponentSystem.Entities; using CorgEng.EntityComponentSystem.Events.Events; using CorgEng.EntityComponentSystem.Implementations.Transform; @@ -38,13 +39,14 @@ public class SpriteRenderSystem : EntitySystem //Runs only on the client, contains no server-side logic. public override EntitySystemFlags SystemFlags { get; } = EntitySystemFlags.CLIENT_SYSTEM | EntitySystemFlags.HOST_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(OnSetSprite); RegisterLocalEvent(OnSetRenderer); RegisterLocalEvent(OnEntityDestroyed); RegisterLocalEvent(OnComponentAdded); RegisterLocalEvent(OnEntityMoved); + //Component.GetTemplate().Position RegisterLocalEvent(OnEntityRotated); RegisterLocalEvent(OnInitialise); RegisterLocalEvent(AddOverlay); @@ -60,7 +62,7 @@ private void OnInitialise(IEntity entity, SpriteRenderComponent spriteRenderComp if (!CorgEngMain.IsRendering) return; //Take the position - spriteRenderComponent.CachedPosition = entity.GetComponent().Position.Copy(); + spriteRenderComponent.CachedPosition = entity.GetComponent().Position.Value.Copy(); //Update the sprite UpdateSprite(spriteRenderComponent); //Apply initial overlays diff --git a/CorgEng.Rendering/Content/Shaders/SpriteShader/SpriteShader.frag b/CorgEng.Rendering/Content/Shaders/SpriteShader/SpriteShader.frag index b89ed638..32fc8994 100644 --- a/CorgEng.Rendering/Content/Shaders/SpriteShader/SpriteShader.frag +++ b/CorgEng.Rendering/Content/Shaders/SpriteShader/SpriteShader.frag @@ -22,6 +22,7 @@ void main() transformedUV *= vec2(fragTextureData[2], fragTextureData[3]); transformedUV += vec2(fragTextureData[0], 1.0 - fragTextureData[1] - fragTextureData[3]); result = texture(renderTexture, transformedUV) * fragColour; + //result.rgb = vec3(gl_FragCoord.z, gl_FragCoord.z, gl_FragCoord.z); //result = vec4(1, 0, 0, 1); //result = vec4(vec3(gl_FragCoord.z), 1.0); if (result.a == 0) diff --git a/CorgEng.Rendering/CorgEng.Rendering.csproj b/CorgEng.Rendering/CorgEng.Rendering.csproj index 0e786021..25614f64 100644 --- a/CorgEng.Rendering/CorgEng.Rendering.csproj +++ b/CorgEng.Rendering/CorgEng.Rendering.csproj @@ -28,6 +28,7 @@ + diff --git a/CorgEng.Rendering/DepthParallax/ParallaxLayerRenderCore.cs b/CorgEng.Rendering/DepthParallax/ParallaxLayerRenderCore.cs new file mode 100644 index 00000000..176e82bc --- /dev/null +++ b/CorgEng.Rendering/DepthParallax/ParallaxLayerRenderCore.cs @@ -0,0 +1,80 @@ +using CorgEng.Core; +using CorgEng.Core.Dependencies; +using CorgEng.Core.Rendering; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using CorgEng.GenericInterfaces.Rendering.Renderers; +using CorgEng.GenericInterfaces.Rendering.Renderers.SpriteRendering; +using CorgEng.Rendering.SpriteRendering; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static OpenGL.Gl; + +namespace CorgEng.Rendering.DepthParallax +{ + public class ParallaxLayerRenderCore : RenderCore + { + + [UsingDependency] + private static ISpriteRendererFactory SpriteRendererFactory = null!; + + /// + /// The layer that this parallax renderer is on + /// + public int LayerValue { get; } + + /// + /// The next layer in the sequence + /// + private ParallaxLayerRenderCore? next; + + /// + /// The renderer + /// + internal ISpriteRenderer renderer; + + /// + /// Parallax render cores that are active in the world + /// + public static Dictionary parallaxRenderCores = new Dictionary(); + + public override float DepthAdd => 0.01f; + + public ParallaxLayerRenderCore(IWorld world, int identifier, int layer) : base(world) + { + if (!parallaxRenderCores.ContainsKey(identifier)) + parallaxRenderCores.Add(identifier, new ParallaxLayerRenderCore[layer]); + parallaxRenderCores[identifier][layer - 1] = this; + LayerValue = layer; + // Keep going until we reach layer 1 + if (layer == 1) + return; + next = new ParallaxLayerRenderCore(world, identifier, layer - 1); + } + + public override void Initialize() + { + renderer = SpriteRendererFactory.CreateSpriteRenderer(0); + renderer.Initialize(); + next?.Initialize(); + } + + public override void PerformRender() + { + // Render our current layer then pass that on to the next render core to draw + renderer.Render(CorgEngMain.MainCamera); + // Now render the layer below us + if (next != null) + { + next.DoRender(() => { + DrawToBuffer(next.FrameBufferUint, 0, 0, Width, Height); + }); + // Render the layer below us onto our layer + next.DrawToBuffer(FrameBufferUint, 1, 1, Width, Height); + } + } + + } +} diff --git a/CorgEng.Rendering/DepthParallax/README.md b/CorgEng.Rendering/DepthParallax/README.md new file mode 100644 index 00000000..2311cce0 --- /dev/null +++ b/CorgEng.Rendering/DepthParallax/README.md @@ -0,0 +1,4 @@ + +# Parallax Depth + +Objects are rendered from front to back, with each layer taking the previous layer and then stacking new elements on top of it. diff --git a/CorgEng.Rendering/Icons/Icon.cs b/CorgEng.Rendering/Icons/Icon.cs index 89dd4b11..9529ea89 100644 --- a/CorgEng.Rendering/Icons/Icon.cs +++ b/CorgEng.Rendering/Icons/Icon.cs @@ -109,7 +109,7 @@ public Icon(string iconName, float layer, uint plane) IconName = iconName; Layer = layer; Plane = plane; - Colour.OnChange += (e, args) => + Colour.ValueChanged += (e, args) => { ValueChanged?.Invoke(); }; diff --git a/CorgEng.Rendering/InstancedRenderer.cs b/CorgEng.Rendering/InstancedRenderer.cs index 02ec444a..c0558792 100644 --- a/CorgEng.Rendering/InstancedRenderer.cs +++ b/CorgEng.Rendering/InstancedRenderer.cs @@ -79,6 +79,8 @@ public void Initialize() public void Render(ICamera camera) { + if (ShaderSet == null) + throw new Exception("Attempting to use a renderer before it has been initialised. Please call Initialize() first."); //Start using our program //Shaders were loaded during init glUseProgram(programUint); diff --git a/CorgEng.Tests/ContentLoadingTests/ContentLoadingTests.cs b/CorgEng.Tests/ContentLoadingTests/ContentLoadingTests.cs index 25568c36..ecdc705a 100644 --- a/CorgEng.Tests/ContentLoadingTests/ContentLoadingTests.cs +++ b/CorgEng.Tests/ContentLoadingTests/ContentLoadingTests.cs @@ -1,6 +1,7 @@ using CorgEng.ContentLoading; using CorgEng.Core.Dependencies; using CorgEng.GenericInterfaces.ContentLoading; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Logging; using CorgEng.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -13,7 +14,7 @@ namespace CorgEng.Tests.ContentLoadingTests { [TestClass] - public class ContentLoadingTests + public class ContentLoadingTests : TestBase { [UsingDependency] @@ -22,16 +23,20 @@ public class ContentLoadingTests [UsingDependency] private static ILogger Logger; + [UsingDependency] + private static IWorldFactory WorldFactory; + [TestMethod] public void TestContentLoading() { + IWorld testWorld = WorldFactory.CreateWorld(); ConsoleLogger.ExceptionCount = 0; EntityLoader.LoadEntities(); Assert.AreEqual(0, ConsoleLogger.ExceptionCount, "An exception occured during entity loading. See logs for details."); //Spawn everything to test spawning foreach (string name in EntityLoader.LoadedDefinitionNames) { - EntityCreator.CreateObject(name); + EntityCreator.CreateObject(testWorld, name); Logger.WriteLine($"Successfully created an instant of {name}.", LogType.LOG); Assert.AreEqual(0, ConsoleLogger.ExceptionCount, $"Failed to spawn object with ID '{name}'."); } diff --git a/CorgEng.Tests/DependencyInjection/DependencyTests.cs b/CorgEng.Tests/DependencyInjection/DependencyTests.cs index 455927d2..d8d0f264 100644 --- a/CorgEng.Tests/DependencyInjection/DependencyTests.cs +++ b/CorgEng.Tests/DependencyInjection/DependencyTests.cs @@ -10,7 +10,7 @@ namespace CorgEng.Tests.DependencyInjection /// Dependency injection overiding is required for these tests to run, so no point in testing it. /// [TestClass] - public class DependencyTests + public class DependencyTests : TestBase { private interface IDependency diff --git a/CorgEng.Tests/EntityComponentSystem/ProcessingSystemTest.cs b/CorgEng.Tests/EntityComponentSystem/ProcessingSystemTest.cs index 9445cff1..fe4cc63e 100644 --- a/CorgEng.Tests/EntityComponentSystem/ProcessingSystemTest.cs +++ b/CorgEng.Tests/EntityComponentSystem/ProcessingSystemTest.cs @@ -1,10 +1,12 @@ -using CorgEng.Core.Dependencies; +using CorgEng.Core; +using CorgEng.Core.Dependencies; using CorgEng.EntityComponentSystem.Components; using CorgEng.EntityComponentSystem.Entities; using CorgEng.EntityComponentSystem.Events; using CorgEng.EntityComponentSystem.Systems; using CorgEng.GenericInterfaces.ContentLoading; using CorgEng.GenericInterfaces.EntityComponentSystem; +using CorgEng.GenericInterfaces.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; @@ -12,15 +14,19 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using static CorgEng.Tests.EntityComponentSystem.TestEntitySystems; namespace CorgEng.Tests.EntityComponentSystem { [TestClass] - public class ProcessingSystemTest + public class ProcessingSystemTest : TestBase { [UsingDependency] - private static IEntityFactory EntityFactory; + private static IWorldFactory WorldFactory; + + [UsingDependency] + private static ILogger Logger; private class TestComponent : Component { @@ -34,11 +40,11 @@ private class TestSystem : ProcessingSystem public override EntitySystemFlags SystemFlags => EntitySystemFlags.HOST_SYSTEM; - protected override int ProcessDelay => 99; + protected override int ProcessDelay => 100; public volatile int timesSignalled = 0; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent((e, t, ev) => { timesSignalled++; @@ -51,28 +57,31 @@ public override void SystemSetup() [TestMethod] public void TestProcessingSystems() { - //Start the test system - TestSystem testSystem = new TestSystem(); - testSystem.SystemSetup(); + IWorld world = WorldFactory.CreateWorld(); + CorgEngMain.PrimaryWorld = world; //Create an entity to process - IEntity testEntity = EntityFactory.CreateEmptyEntity(null); + IEntity testEntity = world.EntityManager.CreateEmptyEntity(null); TestComponent testComponent = new TestComponent(); testEntity.AddComponent(testComponent); + TestSystem testSystem = world.EntitySystemManager.GetSingleton(); //Start processing testSystem.RegisterProcess(testEntity, (e, t, d) => { t.timesProcessed++; }); + Logger.WriteLine(testSystem, LogType.TEMP); //Send some signals to check new TestEvent().Raise(testEntity); new TestEvent().Raise(testEntity); new TestEvent().Raise(testEntity); //Wait 1 second - Thread.Sleep(1000); + Thread.Sleep(5000); //Kill the test system off testSystem.Kill(); //Assertions Assert.AreEqual(3, testSystem.timesSignalled); - Assert.IsTrue(testComponent.timesProcessed > 0); + // Close enough + Assert.IsTrue(testComponent.timesProcessed > 45 && testComponent.timesProcessed <= 51, $"TestComponent was processed {testComponent.timesProcessed} times."); + Console.WriteLine($"TestComponent was processed {testComponent.timesProcessed} times."); } } diff --git a/CorgEng.Tests/EntityComponentSystem/SignalTests.cs b/CorgEng.Tests/EntityComponentSystem/SignalTests.cs index b53b2053..fbf303ec 100644 --- a/CorgEng.Tests/EntityComponentSystem/SignalTests.cs +++ b/CorgEng.Tests/EntityComponentSystem/SignalTests.cs @@ -46,7 +46,7 @@ internal class TestEntitySystem : EntitySystem //Just run on everything public override EntitySystemFlags SystemFlags { get; } = EntitySystemFlags.HOST_SYSTEM | EntitySystemFlags.CLIENT_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(HandleTestEvent); RegisterLocalEvent(HandleSecondaryTestEvent); @@ -107,11 +107,11 @@ private void HandleGlobalEvent(TestEvent globalEvent) } [TestClass] - public class SignalTests + public class SignalTests : TestBase { [UsingDependency] - private static IEntityFactory EntityFactory; + private static IWorldFactory WorldFactory; /// /// DONT USE DEPENDNCY INJECTION TO FORCE THIS @@ -124,6 +124,8 @@ public class SignalTests internal volatile static bool passedGlobalTest = false; + private volatile static IWorld world; + private volatile static TestEntitySystem entitySystem; internal volatile static int currentTestId = 0; @@ -133,8 +135,8 @@ public SignalTests() if (entitySystem == null) { Logger?.WriteLine("Setting up test", LogType.LOG); - entitySystem = new TestEntitySystem(); - entitySystem.SystemSetup(); + world = WorldFactory.CreateWorld(); + entitySystem = world.EntitySystemManager.GetSingleton(); } } @@ -154,7 +156,7 @@ public void TestComponentRemoval() Assert.AreEqual(0, handlesReceieved, "INCORRECT TEST CONFIGURATION"); Logger?.WriteLine($"Current thread: {Thread.CurrentThread.ManagedThreadId}. TestID: {currentTestId}", LogType.LOG); //Create a test entity - IEntity testEntity = EntityFactory.CreateEmptyEntity(null); + IEntity testEntity = world.EntityManager.CreateEmptyEntity(null); //Add a test component TestComponent testComponent = new TestComponent(); testEntity.AddComponent(testComponent); @@ -175,7 +177,7 @@ public void TestComponentRemovalIsolation() Assert.AreEqual(0, secondaryHandlesReceieved, "INCORRECT TEST CONFIGURATION"); Logger?.WriteLine($"Current thread: {Thread.CurrentThread.ManagedThreadId}. TestID: {currentTestId}", LogType.LOG); //Create a test entity - IEntity testEntity = EntityFactory.CreateEmptyEntity(null); + IEntity testEntity = world.EntityManager.CreateEmptyEntity(null); //Add a test component TestComponent testComponent = new TestComponent(); testEntity.AddComponent(testComponent); @@ -200,7 +202,7 @@ public void TestSignalHandling() Assert.AreEqual(0, handlesReceieved, "INCORRECT TEST CONFIGURATION"); Logger?.WriteLine($"Current thread: {Thread.CurrentThread.ManagedThreadId}. TestID: {currentTestId}", LogType.LOG); //Create a test entity - IEntity testEntity = EntityFactory.CreateEmptyEntity(null); + IEntity testEntity = world.EntityManager.CreateEmptyEntity(null); //Add a test component TestComponent testComponent = new TestComponent(); testEntity.AddComponent(testComponent); @@ -230,10 +232,10 @@ public void TestGlobalSignalHandling() { Assert.IsFalse(passedGlobalTest, "INCORRECT TEST CONFIGURATION"); Logger?.WriteLine($"Current thread: {Thread.CurrentThread.ManagedThreadId}. TestID: {currentTestId}", LogType.LOG); - new OtherEvent().RaiseGlobally(); + new OtherEvent().RaiseGlobally(world); Thread.Sleep(50); Assert.IsFalse(passedGlobalTest); - new TestEvent().RaiseGlobally(); + new TestEvent().RaiseGlobally(world); while (!passedGlobalTest) Thread.Sleep(1); } @@ -245,7 +247,7 @@ public void TestMultipleSignalHandling() Assert.AreEqual(0, handlesReceieved, "INCORRECT TEST CONFIGURATION"); Logger?.WriteLine($"Current thread: {Thread.CurrentThread.ManagedThreadId}. TestID: {currentTestId}", LogType.LOG); //Create a test entity - IEntity testEntity = EntityFactory.CreateEmptyEntity(null); + IEntity testEntity = world.EntityManager.CreateEmptyEntity(null); //Add a test component TestComponent testComponent = new TestComponent(); testEntity.AddComponent(testComponent); @@ -266,7 +268,7 @@ public void TestSynchronousSignalHandling() Assert.AreEqual(0, handlesReceieved, "INCORRECT TEST CONFIGURATION"); Logger?.WriteLine($"Current thread: {Thread.CurrentThread.ManagedThreadId}. TestID: {currentTestId}", LogType.LOG); //Create a test entity - IEntity testEntity = EntityFactory.CreateEmptyEntity(null); + IEntity testEntity = world.EntityManager.CreateEmptyEntity(null); //Add a test component TestComponent testComponent = new TestComponent(); testEntity.AddComponent(testComponent); @@ -290,7 +292,7 @@ public void TestUnregisteringSignals() Assert.AreEqual(0, handlesReceieved, "INCORRECT TEST CONFIGURATION"); Logger?.WriteLine($"Current thread: {Thread.CurrentThread.ManagedThreadId}. TestID: {currentTestId}", LogType.LOG); //Create a test entity - IEntity testEntity = EntityFactory.CreateEmptyEntity(null); + IEntity testEntity = world.EntityManager.CreateEmptyEntity(null); //Add a test component TestComponent testComponent = new TestComponent(); testEntity.AddComponent(testComponent); @@ -300,8 +302,8 @@ public void TestUnregisteringSignals() new UnregisterEvent().Raise(testEntity, true); new UnregisterEvent().Raise(testEntity, true); Assert.AreEqual(1, handlesReceieved, "Should have receieved 1 handle."); - new UnregisterEvent().RaiseGlobally(true); - new UnregisterEvent().RaiseGlobally(true); + new UnregisterEvent().RaiseGlobally(world, true); + new UnregisterEvent().RaiseGlobally(world, true); Assert.AreEqual(2, handlesReceieved, "Should have receieved 2 handles."); } diff --git a/CorgEng.Tests/EntityComponentSystem/TestEntitySystems.cs b/CorgEng.Tests/EntityComponentSystem/TestEntitySystems.cs new file mode 100644 index 00000000..9a98973b --- /dev/null +++ b/CorgEng.Tests/EntityComponentSystem/TestEntitySystems.cs @@ -0,0 +1,98 @@ +using CorgEng.Core.Dependencies; +using CorgEng.EntityComponentSystem.Components; +using CorgEng.EntityComponentSystem.Events; +using CorgEng.EntityComponentSystem.Systems; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CorgEng.Tests.EntityComponentSystem +{ + [TestClass] + public class TestEntitySystems : TestBase + { + + [UsingDependency] + private static IWorldFactory WorldFactory = null!; + + public class TestSystem : EntitySystem + { + + public bool testCompleted = false; + + public override EntitySystemFlags SystemFlags => EntitySystemFlags.HOST_SYSTEM | EntitySystemFlags.CLIENT_SYSTEM; + + public override void SystemSetup(IWorld world) + { + RegisterLocalEvent((e, c, s) => { + testCompleted = true; + }); + } + } + + public class TestComponent : Component + { + + } + + public class TestEvent : IEvent + { + + } + + [TestMethod] + public void TestEntitySystem() + { + IWorld world = WorldFactory.CreateWorld(); + IEntity entity = world.EntityManager.CreateEmptyEntity(e => { + e.AddComponent(new TestComponent()); + }); + TestSystem system = world.EntitySystemManager.GetSingleton(); + // Verify + Assert.IsFalse(system.testCompleted); + // Send the signal + new TestEvent().Raise(entity); + // Assertion + Assert.IsTrue(system.testCompleted); + } + + [TestMethod] + [Timeout(200)] + public void TestEntitySystemThreadProcessing() + { + IWorld world = WorldFactory.CreateWorld(); + IEntity entity = world.EntityManager.CreateEmptyEntity(e => { + e.AddComponent(new TestComponent()); + }); + TestSystem system = world.EntitySystemManager.GetSingleton(); + // Verify + Assert.IsFalse(system.testCompleted); + // Send the signal + system.AcquireHighPriorityLock(); + new TestEvent().Raise(entity); + system.ReleaseLock(); + while (!system.testCompleted) + Thread.Sleep(10); + } + + [TestMethod] + public void TestSystemIsolation() + { + IWorld world = WorldFactory.CreateWorld(); + IWorld backgroundWorld = WorldFactory.CreateWorld(); + IEntity entity = world.EntityManager.CreateEmptyEntity(e => { + e.AddComponent(new TestComponent()); + }); + TestSystem backgroundSystem = backgroundWorld.EntitySystemManager.GetSingleton(); + Assert.IsFalse(backgroundSystem.testCompleted); + new TestEvent().Raise(entity); + Assert.IsFalse(backgroundSystem.testCompleted); + } + + } +} diff --git a/CorgEng.Tests/ExperimentationTests.cs b/CorgEng.Tests/ExperimentationTests.cs index 754355f2..165323ef 100644 --- a/CorgEng.Tests/ExperimentationTests.cs +++ b/CorgEng.Tests/ExperimentationTests.cs @@ -12,7 +12,7 @@ namespace CorgEng.Tests /// Class for experimenting with C# features /// [TestClass] - public class ExperimentationTests + public class ExperimentationTests : TestBase { [TestMethod] diff --git a/CorgEng.Tests/FontTests/FontTests.cs b/CorgEng.Tests/FontTests/FontTests.cs index 7dee6d8b..0704906c 100644 --- a/CorgEng.Tests/FontTests/FontTests.cs +++ b/CorgEng.Tests/FontTests/FontTests.cs @@ -11,7 +11,7 @@ namespace CorgEng.Tests.FontTests { [TestClass] - public class FontTests + public class FontTests : TestBase { [UsingDependency] diff --git a/CorgEng.Tests/NetworkingTests/ClientAddressTests.cs b/CorgEng.Tests/NetworkingTests/ClientAddressTests.cs index afb28834..b6accdb3 100644 --- a/CorgEng.Tests/NetworkingTests/ClientAddressTests.cs +++ b/CorgEng.Tests/NetworkingTests/ClientAddressTests.cs @@ -11,7 +11,7 @@ namespace CorgEng.Tests.NetworkingTests { [TestClass] - public class ClientAddressTests + public class ClientAddressTests : TestBase { [UsingDependency] diff --git a/CorgEng.Tests/NetworkingTests/EntityCommunicatorTests.cs b/CorgEng.Tests/NetworkingTests/EntityCommunicatorTests.cs index f7ac4934..ea5319f8 100644 --- a/CorgEng.Tests/NetworkingTests/EntityCommunicatorTests.cs +++ b/CorgEng.Tests/NetworkingTests/EntityCommunicatorTests.cs @@ -18,38 +18,26 @@ namespace CorgEng.Tests.NetworkingTests { [TestClass] - public class EntityCommunicatorTests + public class EntityCommunicatorTests : TestBase { [UsingDependency] - private static IEntityCommunicator EntityCommunicator; - - [UsingDependency] - private static INetworkingServer Server; - - [UsingDependency] - private static INetworkingClient Client; + private static IEntityCommunicatorFactory EntityCommunicatorFactory; [UsingDependency] private static ILogger Logger; [UsingDependency] - private static IEntityFactory EntityFactory; - - [TestCleanup] - public void AfterTest() - { - Server.Cleanup(); - Client.Cleanup(); - Logger?.WriteLine("TEST COMPLETED", LogType.DEBUG); - } + private static IWorldFactory WorldFactory; [TestMethod] [Timeout(1000)] public void TestEntityCommunication() { + IWorld world = WorldFactory.CreateWorld(); + IEntityCommunicator EntityCommunicator = EntityCommunicatorFactory.CreateEntityCommunicator(world); //Alright, connection established. Lets communicate an entity - IEntity testEntity = EntityFactory.CreateEmptyEntity(null); + IEntity testEntity = world.EntityManager.CreateEmptyEntity(null); TestComponent testComponent = new TestComponent(); testComponent.Text = "test"; testComponent.DontSerialise = 5.31262; @@ -59,7 +47,7 @@ public void TestEntityCommunication() //Communicate the entity byte[] serialisedEntity = EntityCommunicator.SerializeEntity(testEntity); //Clear entity manager, so it doesn't know about it - EntityManager.RemoveEntity(testEntity); + world.EntityManager.RemoveEntity(testEntity); //Deserialise the entity IEntity deserialisedEntity = EntityCommunicator.DeserialiseEntity(serialisedEntity).Result; //Perform tests diff --git a/CorgEng.Tests/NetworkingTests/NetworkingTest.cs b/CorgEng.Tests/NetworkingTests/NetworkingTest.cs index fe0f4bc7..2027e885 100644 --- a/CorgEng.Tests/NetworkingTests/NetworkingTest.cs +++ b/CorgEng.Tests/NetworkingTests/NetworkingTest.cs @@ -1,4 +1,5 @@ using CorgEng.Core.Dependencies; +using CorgEng.DependencyInjection.Injection; using CorgEng.EntityComponentSystem.Components; using CorgEng.EntityComponentSystem.Entities; using CorgEng.EntityComponentSystem.Events; @@ -6,11 +7,14 @@ using CorgEng.GenericInterfaces.ContentLoading; using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Logging; +using CorgEng.GenericInterfaces.Networking.Clients; using CorgEng.GenericInterfaces.Networking.Networking; using CorgEng.GenericInterfaces.Networking.Networking.Client; using CorgEng.GenericInterfaces.Networking.Networking.Server; using CorgEng.GenericInterfaces.Networking.Packets; using CorgEng.Networking.EntitySystems; +using CorgEng.Networking.Networking.Client; +using CorgEng.Networking.Networking.Server; using CorgEng.Networking.VersionSync; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -30,46 +34,27 @@ public class NetworkingTest { [UsingDependency] - private static INetworkingServer Server; + private static INetworkMessageFactory MessageFactory; [UsingDependency] - private static INetworkingClient Client; - - [UsingDependency] - private static INetworkMessageFactory MessageFactory; + private static IWorldFactory WorldFactory; [UsingDependency] private static ILogger Logger; - private static bool TestRunning = false; - - [TestCleanup] - public void AfterTest() - { - Server.Cleanup(); - Client.Cleanup(); - Logger?.WriteLine("TEST COMPLETED", LogType.DEBUG); - TestRunning = false; - } - - [TestInitialize] - public void SetupServer() - { - Server.GetType().GetMethod("LoadDefaultPrototype").Invoke(Server, new object[0]); - } - [TestMethod] [Timeout(5000)] public void TestNetworkConnection() { - if (TestRunning) - Assert.Fail("Attempted to test while testing."); - TestRunning = true; bool success = false; - Server.StartHosting(5000); - Client.OnConnectionSuccess += (IPAddress ipAddress) => { success = true; }; - Client.OnConnectionFailed += (IPAddress ipAddress, DisconnectReason disconnectReason, string reasonText) => { Assert.Fail("Connection failed, server rejected connection."); }; - Client.AttemptConnection("127.0.0.1", 5000, 1000); + + IWorld clientWorld = WorldFactory.CreateWorld(); + IWorld serverWorld = WorldFactory.CreateWorld(); + + serverWorld.ServerInstance.StartHosting(5000); + clientWorld.ClientInstance.OnConnectionSuccess += (IPAddress ipAddress) => { success = true; }; + clientWorld.ClientInstance.OnConnectionFailed += (IPAddress ipAddress, DisconnectReason disconnectReason, string reasonText) => { Assert.Fail("Connection failed, server rejected connection."); }; + clientWorld.ClientInstance.AttemptConnection("127.0.0.1", 5000, 1000); while (!success) Thread.Sleep(0); @@ -80,16 +65,17 @@ public void TestNetworkConnection() [Timeout(3000)] public void TestSendingToServer() { - if (TestRunning) - Assert.Fail("Attempted to test while testing."); - TestRunning = true; bool connected = false; bool success = false; - Server.StartHosting(5001); - Client.OnConnectionFailed += (IPAddress ipAddress, DisconnectReason disconnectReason, string reasonText) => { Assert.Inconclusive("Connection failed, server rejected connection."); }; - Client.OnConnectionSuccess += (IPAddress ipAddress) => { connected = true; }; - Server.NetworkMessageReceived += (PacketHeaders packetHeader, byte[] message, int start, int end) => + IWorld clientWorld = WorldFactory.CreateWorld(); + IWorld serverWorld = WorldFactory.CreateWorld(); + + serverWorld.ServerInstance.StartHosting(5001); + clientWorld.ClientInstance.OnConnectionFailed += (IPAddress ipAddress, DisconnectReason disconnectReason, string reasonText) => { Assert.Inconclusive("Connection failed, server rejected connection."); }; + clientWorld.ClientInstance.OnConnectionSuccess += (IPAddress ipAddress) => { connected = true; }; + + serverWorld.ServerInstance.NetworkMessageReceived += (PacketHeaders packetHeader, byte[] message, int start, int end) => { if (packetHeader == PacketHeaders.NETWORKING_TEST) { @@ -100,14 +86,14 @@ public void TestSendingToServer() } }; - Client.AttemptConnection("127.0.0.1", 5001, 1000); + clientWorld.ClientInstance.AttemptConnection("127.0.0.1", 5001, 1000); //Await connection to the server while (!connected) Thread.Sleep(0); //Send a server message to the client - Client.QueueMessage( + clientWorld.ClientInstance.QueueMessage( MessageFactory.CreateMessage(PacketHeaders.NETWORKING_TEST, Encoding.ASCII.GetBytes("CORRECT")) ); @@ -120,15 +106,16 @@ public void TestSendingToServer() [Timeout(3000)] public void TestSendingToClient() { - if (TestRunning) - Assert.Fail("Attempted to test while testing."); - TestRunning = true; bool connected = false; bool success = false; - Server.StartHosting(5002); - Client.OnConnectionFailed += (IPAddress ipAddress, DisconnectReason disconnectReason, string reasonText) => { Assert.Inconclusive("Connection failed, server rejected connection."); }; - Client.OnConnectionSuccess += (IPAddress ipAddress) => { connected = true; }; - Client.NetworkMessageReceived += (PacketHeaders packetHeader, byte[] message, int start, int end) => + + IWorld clientWorld = WorldFactory.CreateWorld(); + IWorld serverWorld = WorldFactory.CreateWorld(); + + serverWorld.ServerInstance.StartHosting(5002); + clientWorld.ClientInstance.OnConnectionFailed += (IPAddress ipAddress, DisconnectReason disconnectReason, string reasonText) => { Assert.Inconclusive("Connection failed, server rejected connection."); }; + clientWorld.ClientInstance.OnConnectionSuccess += (IPAddress ipAddress) => { connected = true; }; + clientWorld.ClientInstance.NetworkMessageReceived += (PacketHeaders packetHeader, byte[] message, int start, int end) => { if (packetHeader == PacketHeaders.NETWORKING_TEST) { @@ -138,15 +125,15 @@ public void TestSendingToClient() success = true; } }; - Client.AttemptConnection("127.0.0.1", 5002, 1000); + clientWorld.ClientInstance.AttemptConnection("127.0.0.1", 5002, 1000); //Await connection to the server while (!connected) Thread.Sleep(0); //Send a server message to the client - Server.QueueMessage( - Server.ClientAddressingTable.GetEveryone(), + serverWorld.ServerInstance.QueueMessage( + serverWorld.ServerInstance.ClientAddressingTable.GetEveryone(), MessageFactory.CreateMessage(PacketHeaders.NETWORKING_TEST, Encoding.ASCII.GetBytes("CORRECT")) ); @@ -158,9 +145,6 @@ public void TestSendingToClient() [Timeout(5000)] public void TestClientKick() { - if (TestRunning) - Assert.Fail("Attempted to test while testing."); - TestRunning = true; Assert.Inconclusive("Test isn't implemented"); } @@ -168,9 +152,6 @@ public void TestClientKick() [Timeout(5000)] public void TestBanning() { - if (TestRunning) - Assert.Fail("Attempted to test while testing."); - TestRunning = true; Assert.Inconclusive("Test isn't implemented"); } @@ -208,7 +189,7 @@ private class NetworkedTestEntitySystem : EntitySystem public override EntitySystemFlags SystemFlags { get; } = EntitySystemFlags.HOST_SYSTEM | EntitySystemFlags.CLIENT_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterGlobalEvent(AcceptTestEvent); } @@ -231,23 +212,23 @@ private void AcceptTestEvent(NetworkedTestEvent networkedTestEvent) [Timeout(3000)] public void TestGlobalNetworkedEvent() { - if (TestRunning) - Assert.Fail("Attempted to test while testing."); - TestRunning = true; - //Set up a test entity system - NetworkedTestEntitySystem networkedTestEntitySystem = new NetworkedTestEntitySystem(); - networkedTestEntitySystem.SystemSetup(); + // Currently broken + Assert.Inconclusive("This test currently doesn't work."); + IWorld clientWorld = WorldFactory.CreateWorld(); + IWorld serverWorld = WorldFactory.CreateWorld(); + + //Set up a test entity system + NetworkedTestEntitySystem networkedTestEntitySystem = clientWorld.EntitySystemManager.GetSingleton(); //Start a networking system - NetworkSystem networkSystem = new NetworkSystem(); - networkSystem.SystemSetup(); + NetworkSystem networkSystem = clientWorld.EntitySystemManager.GetSingleton(); //Connect to the server bool success = false; - Server.StartHosting(5003); - Client.OnConnectionSuccess += (IPAddress ipAddress) => { success = true; }; - Client.OnConnectionFailed += (IPAddress ipAddress, DisconnectReason disconnectReason, string reasonText) => { Assert.Fail("Connection failed, server rejected connection."); }; - Client.AttemptConnection("127.0.0.1", 5003, 1000); + serverWorld.ServerInstance.StartHosting(5003); + clientWorld.ClientInstance.OnConnectionSuccess += (IPAddress ipAddress) => { success = true; }; + clientWorld.ClientInstance.OnConnectionFailed += (IPAddress ipAddress, DisconnectReason disconnectReason, string reasonText) => { Assert.Fail("Connection failed, server rejected connection."); }; + clientWorld.ClientInstance.AttemptConnection("127.0.0.1", 5003, 1000); while (!success) Thread.Sleep(0); @@ -255,7 +236,7 @@ public void TestGlobalNetworkedEvent() //Raise a global event NetworkedTestEvent testEvent = new NetworkedTestEvent(); testEvent.testNumber = 142; - testEvent.RaiseGlobally(); + testEvent.RaiseGlobally(serverWorld); Logger?.WriteLine($"Test event raised globally, ID: {testEvent.GetNetworkedIdentifier()}", LogType.DEBUG); diff --git a/CorgEng.Tests/NetworkingTests/PacketQueueTests.cs b/CorgEng.Tests/NetworkingTests/PacketQueueTests.cs index 43149b10..f45b921f 100644 --- a/CorgEng.Tests/NetworkingTests/PacketQueueTests.cs +++ b/CorgEng.Tests/NetworkingTests/PacketQueueTests.cs @@ -12,7 +12,7 @@ namespace CorgEng.Tests.NetworkingTests { [TestClass] - public class PacketQueueTests + public class PacketQueueTests : TestBase { [UsingDependency] diff --git a/CorgEng.Tests/NetworkingTests/PrototypeTests.cs b/CorgEng.Tests/NetworkingTests/PrototypeTests.cs index 4a524c88..464a45b7 100644 --- a/CorgEng.Tests/NetworkingTests/PrototypeTests.cs +++ b/CorgEng.Tests/NetworkingTests/PrototypeTests.cs @@ -39,46 +39,37 @@ public class TestComponent : Component } [TestClass] - public class PrototypeTests + public class PrototypeTests : TestBase { [UsingDependency] - private static IEntityFactory EntityFactory; + private static IWorldFactory WorldFactory; [UsingDependency] private static IPrototypeManager PrototypeManager; - [UsingDependency] - private static INetworkingServer Server; - - [UsingDependency] - private static INetworkingClient Client; - [UsingDependency] private static ILogger Logger; - [TestCleanup] - public void AfterTest() - { - Server.Cleanup(); - Client.Cleanup(); - } - [TestMethod] [Timeout(10000)] public void TestPrototypes() { bool success = false; - Server.StartHosting(5000); - Client.OnConnectionSuccess += (IPAddress ipAddress) => { success = true; }; - Client.OnConnectionFailed += (IPAddress ipAddress, DisconnectReason disconnectReason, string reasonText) => { Assert.Fail("Connection failed, server rejected connection."); }; - Client.AttemptConnection("127.0.0.1", 5000, 1000); + + IWorld clientWorld = WorldFactory.CreateWorld(); + IWorld serverWorld = WorldFactory.CreateWorld(); + + serverWorld.ServerInstance.StartHosting(5000); + clientWorld.ClientInstance.OnConnectionSuccess += (IPAddress ipAddress) => { success = true; }; + clientWorld.ClientInstance.OnConnectionFailed += (IPAddress ipAddress, DisconnectReason disconnectReason, string reasonText) => { Assert.Fail("Connection failed, server rejected connection."); }; + clientWorld.ClientInstance.AttemptConnection("127.0.0.1", 5000, 1000); while (!success) Thread.Sleep(0); //Create an entity - IEntity entity = EntityFactory.CreateEmptyEntity(null); + IEntity entity = serverWorld.EntityManager.CreateEmptyEntity(null); TestComponent testComponent = new TestComponent(); testComponent.Integer = 59; testComponent.Text = "Hello World!"; @@ -95,7 +86,7 @@ public void TestPrototypes() //Deserialize the entity's prototype IPrototype deserialisedPrototype = PrototypeManager.GetPrototype(serialisedPrototype); - IEntity createdEntity = deserialisedPrototype.CreateEntityFromPrototype(); //We don't care about the identifier for this test + IEntity createdEntity = deserialisedPrototype.CreateEntityFromPrototype(clientWorld); //We don't care about the identifier for this test //Verify the entity is correct Assert.AreEqual(entity.Components.Count, createdEntity.Components.Count); TestComponent deserializedComponent = (TestComponent)createdEntity.Components[1]; diff --git a/CorgEng.Tests/NetworkingTests/SerializationTest.cs b/CorgEng.Tests/NetworkingTests/SerializationTest.cs index e2f85c69..85cfdfba 100644 --- a/CorgEng.Tests/NetworkingTests/SerializationTest.cs +++ b/CorgEng.Tests/NetworkingTests/SerializationTest.cs @@ -20,7 +20,7 @@ namespace CorgEng.Tests.NetworkingTests { [TestClass] - public class SerializationTest + public class SerializationTest : TestBase { [UsingDependency] diff --git a/CorgEng.Tests/NetworkingTests/TestCVars.cs b/CorgEng.Tests/NetworkingTests/TestCVars.cs new file mode 100644 index 00000000..0da7caf4 --- /dev/null +++ b/CorgEng.Tests/NetworkingTests/TestCVars.cs @@ -0,0 +1,93 @@ +using CorgEng.Core.Dependencies; +using CorgEng.EntityComponentSystem.Components; +using CorgEng.EntityComponentSystem.Components.ComponentVariables.Networking; +using CorgEng.EntityComponentSystem.Entities; +using CorgEng.GenericInterfaces.ContentLoading; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using CorgEng.GenericInterfaces.Logging; +using CorgEng.GenericInterfaces.Networking.Clients; +using CorgEng.GenericInterfaces.Networking.Networking; +using CorgEng.GenericInterfaces.Networking.Networking.Client; +using CorgEng.GenericInterfaces.Networking.Networking.Server; +using CorgEng.GenericInterfaces.Networking.Packets; +using CorgEng.Networking.Components; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CorgEng.Tests.NetworkingTests +{ + + public class ExampleComponent : Component + { + + public NetCVar exampleNetvar = new NetCVar(5); + + } + + + [TestClass] + public class TestCVars : TestBase + { + + [UsingDependency] + private static IWorldFactory WorldFactory; + + [UsingDependency] + private static INetworkMessageFactory MessageFactory; + + [UsingDependency] + private static ILogger Logger; + + private IWorld serverWorld; + private IWorld clientWorld; + + [TestInitialize] + public void TestStart() + { + serverWorld = WorldFactory.CreateWorld(); + clientWorld = WorldFactory.CreateWorld(); + + bool connected = false; + bool success = false; + serverWorld.ServerInstance.StartHosting(5001); + clientWorld.ClientInstance.OnConnectionFailed += (IPAddress ipAddress, DisconnectReason disconnectReason, string reasonText) => { Assert.Inconclusive("Connection failed, server rejected connection."); }; + clientWorld.ClientInstance.OnConnectionSuccess += (IPAddress ipAddress) => { connected = true; }; + + serverWorld.ServerInstance.NetworkMessageReceived += (PacketHeaders packetHeader, byte[] message, int start, int end) => + { + if (packetHeader == PacketHeaders.NETWORKING_TEST) + { + string gotMessage = Encoding.ASCII.GetString(message.Skip(start).Take(end).ToArray()); + if (gotMessage != "CORRECT") + Assert.Fail($"Recieved message did not contain correct content, content recieved: '{gotMessage}'"); + success = true; + } + }; + + clientWorld.ClientInstance.AttemptConnection("127.0.0.1", 5001, 1000); + + //Await connection to the server + while (!connected) + Thread.Sleep(0); + } + + [TestMethod] + public void TestNetVarModification() + { + // Create the entity + IEntity createdEntity = serverWorld.EntityManager.CreateEmptyEntity(entity => { + entity.AddComponent(new NetworkTransformComponent()); + entity.AddComponent(new ExampleComponent()); + }); + // + Assert.Inconclusive("I haven't made this"); + } + + } +} diff --git a/CorgEng.Tests/Pathfinding/PathfindingTests.cs b/CorgEng.Tests/Pathfinding/PathfindingTests.cs index 3c8e1779..1b229450 100644 --- a/CorgEng.Tests/Pathfinding/PathfindingTests.cs +++ b/CorgEng.Tests/Pathfinding/PathfindingTests.cs @@ -16,7 +16,7 @@ namespace CorgEng.Tests.Pathfinding { [TestClass] - public class PathfindingTests + public class PathfindingTests : TestBase { private class PathfindingTestQueryer : IPathCellQueryer diff --git a/CorgEng.Tests/Performance/EventProcessing.cs b/CorgEng.Tests/Performance/EventProcessing.cs index f7d035bb..f665e8c8 100644 --- a/CorgEng.Tests/Performance/EventProcessing.cs +++ b/CorgEng.Tests/Performance/EventProcessing.cs @@ -1,5 +1,6 @@ //#define PERFORMANCE_TEST +using CorgEng.Core.Dependencies; using CorgEng.EntityComponentSystem.Events; using CorgEng.EntityComponentSystem.Systems; using CorgEng.GenericInterfaces.EntityComponentSystem; @@ -24,6 +25,9 @@ namespace CorgEng.Tests.Performance public class EventProcessing { + [UsingDependency] + private static IWorldFactory WorldFactory; + private static int EventsHandled = 0; private static int LateHandles = 0; private static bool Running = false; @@ -35,7 +39,7 @@ private class TestEntitySystem : EntitySystem { public override EntitySystemFlags SystemFlags => EntitySystemFlags.HOST_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterGlobalEvent(HandleEvent); } @@ -59,10 +63,12 @@ public void TestAsyncEventPerformance() #if !PERFORMANCE_TEST Assert.Inconclusive("Test not executed. Please enable PERFORMANCE_TEST define in order to test performance."); #endif + + IWorld world = WorldFactory.CreateWorld(); + EventsHandled = 0; LateHandles = 0; - TestEntitySystem testEntitySystem = new TestEntitySystem(); - testEntitySystem.SystemSetup(); + TestEntitySystem testEntitySystem = world.EntitySystemManager.GetSingleton(); //Perform the tests Running = true; @@ -71,7 +77,7 @@ public void TestAsyncEventPerformance() while (Running) { //Perform a run - new TestEvent().RaiseGlobally(); + new TestEvent().RaiseGlobally(world); //Run completed runs++; } @@ -91,10 +97,11 @@ public void TestSynchronousEventPerformance() #if !PERFORMANCE_TEST Assert.Inconclusive("Test not executed. Please enable PERFORMANCE_TEST define in order to test performance."); #endif + IWorld world = WorldFactory.CreateWorld(); + EventsHandled = 0; LateHandles = 0; - TestEntitySystem testEntitySystem = new TestEntitySystem(); - testEntitySystem.SystemSetup(); + TestEntitySystem testEntitySystem = world.EntitySystemManager.GetSingleton(); //Perform the tests Running = true; @@ -103,7 +110,7 @@ public void TestSynchronousEventPerformance() while (Running) { //Perform a run - new TestEvent().RaiseGlobally(true); + new TestEvent().RaiseGlobally(world, true); //Run completed runs++; } @@ -123,10 +130,11 @@ public void TestMethodCallingPerformance() #if !PERFORMANCE_TEST Assert.Inconclusive("Test not executed. Please enable PERFORMANCE_TEST define in order to test performance."); #endif + IWorld world = WorldFactory.CreateWorld(); + EventsHandled = 0; LateHandles = 0; - TestEntitySystem testEntitySystem = new TestEntitySystem(); - testEntitySystem.SystemSetup(); + TestEntitySystem testEntitySystem = world.EntitySystemManager.GetSingleton(); //Perform the tests Running = true; diff --git a/CorgEng.Tests/Performance/SerialisationPerformance.cs b/CorgEng.Tests/Performance/SerialisationPerformance.cs index d29dc3f0..c1e75ec6 100644 --- a/CorgEng.Tests/Performance/SerialisationPerformance.cs +++ b/CorgEng.Tests/Performance/SerialisationPerformance.cs @@ -16,14 +16,14 @@ namespace CorgEng.Tests.Performance { [TestClass] - public class SerialisationPerformance + public class SerialisationPerformance : TestBase { [UsingDependency] - private static IEntityFactory EntityFactory; + private static IPrototypeManager PrototypeManager; [UsingDependency] - private static IPrototypeManager PrototypeManager; + private static IWorldFactory WorldFactory; private const int TEST_TIME = 5000; @@ -33,8 +33,9 @@ public void TestEntityToPrototypePerformance() #if !PERFORMANCE_TEST Assert.Inconclusive("Test not executed. Please enable PERFORMANCE_TEST define in order to test performance."); #endif + IWorld testWorld = WorldFactory.CreateWorld(); //Create an entity - IEntity entity = EntityFactory.CreateEmptyEntity(null); + IEntity entity = testWorld.EntityManager.CreateEmptyEntity(null); TestComponent testComponent = new TestComponent(); testComponent.Integer = 59; testComponent.Text = "Hello World!"; @@ -66,8 +67,9 @@ public void TestPrototypeToEntityPerformance() #if !PERFORMANCE_TEST Assert.Inconclusive("Test not executed. Please enable PERFORMANCE_TEST define in order to test performance."); #endif + IWorld testWorld = WorldFactory.CreateWorld(); //Create an entity - IEntity entity = EntityFactory.CreateEmptyEntity(null); + IEntity entity = testWorld.EntityManager.CreateEmptyEntity(null); TestComponent testComponent = new TestComponent(); testComponent.Integer = 59; testComponent.Text = "Hello World!"; @@ -83,7 +85,7 @@ public void TestPrototypeToEntityPerformance() while (running) { //Perform a run, don't care about the identifier - collectedPrototype.CreateEntityFromPrototype(); + collectedPrototype.CreateEntityFromPrototype(testWorld); //Run completed runs++; } @@ -101,9 +103,10 @@ public void TestDeserialisationPerformance() #if !PERFORMANCE_TEST Assert.Inconclusive("Test not executed. Please enable PERFORMANCE_TEST define in order to test performance."); #endif + IWorld testWorld = WorldFactory.CreateWorld(); //Create an entity - IEntity entity = EntityFactory.CreateEmptyEntity(null); + IEntity entity = testWorld.EntityManager.CreateEmptyEntity(null); TestComponent testComponent = new TestComponent(); testComponent.Integer = 59; testComponent.Text = "Hello World!"; @@ -138,9 +141,10 @@ public void TestSerialisationPerformance() #if !PERFORMANCE_TEST Assert.Inconclusive("Test not executed. Please enable PERFORMANCE_TEST define in order to test performance."); #endif + IWorld testWorld = WorldFactory.CreateWorld(); //Create an entity - IEntity entity = EntityFactory.CreateEmptyEntity(null); + IEntity entity = testWorld.EntityManager.CreateEmptyEntity(null); TestComponent testComponent = new TestComponent(); testComponent.Integer = 59; testComponent.Text = "Hello World!"; diff --git a/CorgEng.Tests/Performance/ThreadTest.cs b/CorgEng.Tests/Performance/ThreadTest.cs index e66bb066..e766a61b 100644 --- a/CorgEng.Tests/Performance/ThreadTest.cs +++ b/CorgEng.Tests/Performance/ThreadTest.cs @@ -15,7 +15,7 @@ namespace CorgEng.Tests.Performance { [TestClass] - public class ThreadTest + public class ThreadTest : TestBase { [UsingDependency] diff --git a/CorgEng.Tests/Performance/UnsortedTests.cs b/CorgEng.Tests/Performance/UnsortedTests.cs new file mode 100644 index 00000000..82bc61fd --- /dev/null +++ b/CorgEng.Tests/Performance/UnsortedTests.cs @@ -0,0 +1,127 @@ +//#define PERFORMANCE_TEST + +using CorgEng.EntityComponentSystem.Components; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CorgEng.Tests.Performance +{ + [TestClass] + [DoNotParallelize] + public class UnsortedTests + { + + private class Test + { + + } + + private class TestA : Test + { } + private class TestB : Test + { } + private class TestC : Test + { } + private class TestD : Test + { } + + private const int TEST_TIME = 5000; + + private Dictionary testDictionaries = new Dictionary() { + { typeof(TestA), new TestA() }, + { typeof(TestB), new TestB() }, + { typeof(TestC), new TestC() }, + { typeof(TestD), new TestD() }, + }; + + private sealed class TestLookup + where T : Test + { + internal static T Tester = Activator.CreateInstance(); + } + + [TestMethod] + public void DictionaryLookup() + { +#if !PERFORMANCE_TEST + Assert.Inconclusive("Test not executed. Please enable PERFORMANCE_TEST define in order to test performance."); +#endif + + //Perform the tests + bool running = true; + int runs = 0; + Thread thread = new Thread(() => { + while (running) + { + switch (runs % 4) + { + case 0: + Test thing = testDictionaries[typeof(TestA)]; + break; + case 1: + Test thing1 = testDictionaries[typeof(TestB)]; + break; + case 2: + Test thing2 = testDictionaries[typeof(TestC)]; + break; + case 3: + Test thing3 = testDictionaries[typeof(TestD)]; + break; + } + //Run completed + runs++; + } + }); + thread.Start(); + Thread.Sleep(TEST_TIME); + running = false; + //Process results + Assert.Inconclusive($"Performed {runs} prototype gets in {TEST_TIME}ms at a rate of {runs / (TEST_TIME / 1000f)}/s"); + } + + [TestMethod] + public void TypeLookup() + { +#if !PERFORMANCE_TEST + Assert.Inconclusive("Test not executed. Please enable PERFORMANCE_TEST define in order to test performance."); +#endif + + //Perform the tests + bool running = true; + int runs = 0; + Thread thread = new Thread(() => { + while (running) + { + switch (runs % 4) + { + case 0: + Test thing = TestLookup.Tester; + break; + case 1: + Test thing1 = TestLookup.Tester; + break; + case 2: + Test thing2 = TestLookup.Tester; + break; + case 3: + Test thing3 = TestLookup.Tester; + break; + } + //Run completed + runs++; + } + }); + thread.Start(); + Thread.Sleep(TEST_TIME); + running = false; + //Process results + Assert.Inconclusive($"Performed {runs} prototype gets in {TEST_TIME}ms at a rate of {runs / (TEST_TIME / 1000f)}/s"); + } + + } +} diff --git a/CorgEng.Tests/RenderingTests/BindedBatchTest.cs b/CorgEng.Tests/RenderingTests/BindedBatchTest.cs index 2cbcb2b7..1774ee1f 100644 --- a/CorgEng.Tests/RenderingTests/BindedBatchTest.cs +++ b/CorgEng.Tests/RenderingTests/BindedBatchTest.cs @@ -13,7 +13,7 @@ namespace CorgEng.Tests.RenderingTests { [TestClass] - public class BindedBatchTest + public class BindedBatchTest : TestBase { private class TestBatch : Batch diff --git a/CorgEng.Tests/RenderingTests/TransparencyLoadingTests.cs b/CorgEng.Tests/RenderingTests/TransparencyLoadingTests.cs index f4fbdddc..24b42e80 100644 --- a/CorgEng.Tests/RenderingTests/TransparencyLoadingTests.cs +++ b/CorgEng.Tests/RenderingTests/TransparencyLoadingTests.cs @@ -11,7 +11,7 @@ namespace CorgEng.Tests.RenderingTests { [TestClass] - public class TransparencyLoadingTests + public class TransparencyLoadingTests : TestBase { [UsingDependency] diff --git a/CorgEng.Tests/Stubs/RenderCoreStub.cs b/CorgEng.Tests/Stubs/RenderCoreStub.cs index fee30874..72f55bc5 100644 --- a/CorgEng.Tests/Stubs/RenderCoreStub.cs +++ b/CorgEng.Tests/Stubs/RenderCoreStub.cs @@ -17,7 +17,7 @@ internal class RenderCoreStub : IRenderCore public int Height => 0; - public void DoRender() + public void DoRender(Action preRenderAction = null) { return; } diff --git a/CorgEng.Tests/TestBase.cs b/CorgEng.Tests/TestBase.cs new file mode 100644 index 00000000..74141a29 --- /dev/null +++ b/CorgEng.Tests/TestBase.cs @@ -0,0 +1,20 @@ +using CorgEng.Core; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.Tests +{ + [TestClass] + public class TestBase + { + [TestCleanup] + public void TestCleanup() + { + CorgEngMain.Cleanup(); + } + } +} diff --git a/CorgEng.Tests/TestSetup.cs b/CorgEng.Tests/TestSetup.cs index 2f3775c8..52368333 100644 --- a/CorgEng.Tests/TestSetup.cs +++ b/CorgEng.Tests/TestSetup.cs @@ -18,7 +18,7 @@ namespace CorgEng.Tests { [TestClass] - public class TestSetup + public class TestSetup : TestBase { [AssemblyInitialize] diff --git a/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceButtonTests.cs b/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceButtonTests.cs index 33379a30..bc3b3c0f 100644 --- a/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceButtonTests.cs +++ b/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceButtonTests.cs @@ -1,5 +1,6 @@ using CorgEng.Core.Dependencies; using CorgEng.EntityComponentSystem.Events; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.UserInterface.Components; using CorgEng.GenericInterfaces.UserInterface.Generators; using CorgEng.UserInterface.Attributes; @@ -17,12 +18,17 @@ namespace CorgEng.Tests.UserInterfaceTests.UnitTests { [TestClass] - public class UserInterfaceButtonTests + public class UserInterfaceButtonTests : TestBase { [UsingDependency] private static IUserInterfaceXmlLoader UserInterfaceXmlLoader; + [UsingDependency] + private static IWorldFactory WorldFactory; + + private static IWorld world; + private static bool TestPassed = false; [UserInterfaceCodeCallback("PassTest")] @@ -39,8 +45,7 @@ public void SetupTests() if (!setup) { setup = true; - UserInterfaceClickSystem clickSystem = new UserInterfaceClickSystem(); - clickSystem.SystemSetup(); + world = WorldFactory.CreateWorld(); } } @@ -54,7 +59,7 @@ public void TestUserInterfaceButton() if (UserInterfaceXmlLoader == null) Assert.Inconclusive("User interface XML loader dependency was not injected."); //Check for loading errors - IUserInterfaceComponent Root = UserInterfaceXmlLoader.LoadUserInterface("UserInterfaceTests/UnitTests/Content/UserInterfaceButton.xml"); + IUserInterfaceComponent Root = UserInterfaceXmlLoader.LoadUserInterface(world, "UserInterfaceTests/UnitTests/Content/UserInterfaceButton.xml"); IUserInterfaceComponent button = Root.GetChildren().First(); new UserInterfaceClickEvent().Raise(button.ComponentHolder); while (!TestPassed) diff --git a/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceComponentTests.cs b/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceComponentTests.cs index 3c440ece..0348b87a 100644 --- a/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceComponentTests.cs +++ b/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceComponentTests.cs @@ -1,5 +1,6 @@ using CorgEng.Core.Dependencies; using CorgEng.DependencyInjection.Injection; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.UserInterface.Anchors; using CorgEng.GenericInterfaces.UserInterface.Components; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -12,7 +13,7 @@ namespace CorgEng.Tests.UserInterfaceTests.UnitTests { [TestClass] - public class UserInterfaceComponentTests + public class UserInterfaceComponentTests : TestBase { [UsingDependency] @@ -24,6 +25,9 @@ public class UserInterfaceComponentTests [UsingDependency] private static IAnchorDetailFactory AnchorDetailFactory; + [UsingDependency] + private static IWorldFactory WorldFactory; + /// /// This test verifies that the dependencies have been implemented. /// @@ -48,10 +52,12 @@ public void TestParentInterfaceExpansionPercentages() Assert.Inconclusive("Anchor factory not located."); if (UserInterfaceComponentFactory == null) Assert.Inconclusive("User interface factory not located."); + IWorld world = WorldFactory.CreateWorld(); //Create a root component IUserInterfaceComponent expandingComponent = null; IUserInterfaceComponent bigComponent = null; IUserInterfaceComponent parentUserInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0), AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.RIGHT, AnchorUnits.PIXELS, 0), @@ -61,6 +67,7 @@ public void TestParentInterfaceExpansionPercentages() createdComponent => { //Add a child component which can scale but starts with no height expandingComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, createdComponent, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PERCENTAGE, 10, true), @@ -71,6 +78,7 @@ public void TestParentInterfaceExpansionPercentages() subCreatedComponent => { //Add a child component to that which has a huge scale bigComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, subCreatedComponent, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0, true), @@ -117,8 +125,10 @@ public void TestParentInterfaceExpansion() Assert.Inconclusive("Anchor factory not located."); if (UserInterfaceComponentFactory == null) Assert.Inconclusive("User interface factory not located."); + IWorld world = WorldFactory.CreateWorld(); //Create a root component IUserInterfaceComponent parentUserInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0), AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.RIGHT, AnchorUnits.PIXELS, 0), @@ -130,6 +140,7 @@ public void TestParentInterfaceExpansion() parentUserInterfaceComponent.SetWidth(1000, 1000); //Add a child component which can scale but starts with no height IUserInterfaceComponent expandingComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, parentUserInterfaceComponent, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 100, true), @@ -141,6 +152,7 @@ public void TestParentInterfaceExpansion() ); //Add a child component to that which has a huge scale IUserInterfaceComponent bigComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, expandingComponent, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0, true), @@ -183,8 +195,10 @@ public void TestExpansionWithMargin() Assert.Inconclusive("Anchor factory not located."); if (UserInterfaceComponentFactory == null) Assert.Inconclusive("User interface factory not located."); + IWorld world = WorldFactory.CreateWorld(); //Create a root component IUserInterfaceComponent parentUserInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0), AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.RIGHT, AnchorUnits.PIXELS, 0), @@ -196,6 +210,7 @@ public void TestExpansionWithMargin() parentUserInterfaceComponent.SetWidth(1000, 1000); //Add a child component which can scale but starts with no height IUserInterfaceComponent expandingComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, parentUserInterfaceComponent, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 100, true), @@ -207,6 +222,7 @@ public void TestExpansionWithMargin() ); //Add a child component to that which has a huge scale IUserInterfaceComponent bigComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, expandingComponent, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0, true), @@ -238,8 +254,10 @@ public void TestMinimumScale() Assert.Inconclusive("Anchor factory not located."); if (UserInterfaceComponentFactory == null) Assert.Inconclusive("User interface factory not located."); + IWorld world = WorldFactory.CreateWorld(); //Create a root component IUserInterfaceComponent parentUserInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0), AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.RIGHT, AnchorUnits.PIXELS, 0), @@ -251,6 +269,7 @@ public void TestMinimumScale() parentUserInterfaceComponent.SetWidth(1000, 1000); //Add a child component which can scale but starts with no height IUserInterfaceComponent expandingComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, parentUserInterfaceComponent, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0), @@ -262,6 +281,7 @@ public void TestMinimumScale() ); //Add a child component to that which has a huge scale IUserInterfaceComponent bigComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, expandingComponent, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0), @@ -296,8 +316,10 @@ public void TestStrictExpansion() Assert.Inconclusive("Anchor factory not located."); if (UserInterfaceComponentFactory == null) Assert.Inconclusive("User interface factory not located."); + IWorld world = WorldFactory.CreateWorld(); //Create a root component IUserInterfaceComponent parentUserInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0), AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.RIGHT, AnchorUnits.PIXELS, 0), @@ -309,6 +331,7 @@ public void TestStrictExpansion() parentUserInterfaceComponent.SetWidth(1000, 1000); //This element should neve rexpand past 500 height IUserInterfaceComponent expandingComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, parentUserInterfaceComponent, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0), @@ -320,6 +343,7 @@ public void TestStrictExpansion() ); //Add a child component to that which has a huge scale IUserInterfaceComponent bigComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, expandingComponent, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0), @@ -354,8 +378,10 @@ public void TestNonStrictExpansion() Assert.Inconclusive("Anchor factory not located."); if (UserInterfaceComponentFactory == null) Assert.Inconclusive("User interface factory not located."); + IWorld world = WorldFactory.CreateWorld(); //Create a root component IUserInterfaceComponent parentUserInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0), AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.RIGHT, AnchorUnits.PIXELS, 0), @@ -367,6 +393,7 @@ public void TestNonStrictExpansion() parentUserInterfaceComponent.SetWidth(1000, 1000); //Add a child component which can scale but starts with no height IUserInterfaceComponent expandingComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, parentUserInterfaceComponent, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0), @@ -378,6 +405,7 @@ public void TestNonStrictExpansion() ); //Add a child component to that which has a huge scale IUserInterfaceComponent bigComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, expandingComponent, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0), @@ -426,6 +454,7 @@ public void TestChildScaleCorrectness( Assert.Inconclusive("Anchor factory not located."); if (UserInterfaceComponentFactory == null) Assert.Inconclusive("User interface factory not located."); + IWorld world = WorldFactory.CreateWorld(); //Setup the parent component //Create the parent anchor details IAnchorDetails parentTopAnchorDetails = AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.TOP, AnchorUnits.PIXELS, 0); @@ -435,7 +464,7 @@ public void TestChildScaleCorrectness( //Create the parent anchor IAnchor parentAnchor = AnchorFactory.CreateAnchor(parentLeftAnchorDetails, parentRightAnchorDetails, parentTopAnchorDetails, parentBottomAnchorDetails); //Create the parent component - IUserInterfaceComponent parentUserInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent(parentAnchor, null); + IUserInterfaceComponent parentUserInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent(world, parentAnchor, null); parentUserInterfaceComponent.SetWidth(parentWidth, 1000); //Setup the child component //Create the anchor details @@ -446,7 +475,7 @@ public void TestChildScaleCorrectness( //Create the anchor IAnchor childAnchor = AnchorFactory.CreateAnchor(childLeftAnchorDetails, childRightAnchorDetails, childTopAnchorDetails, childBottomAnchorDetails); //Create the child component - IUserInterfaceComponent childUserInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent(parentUserInterfaceComponent, childAnchor, null); + IUserInterfaceComponent childUserInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent(world, parentUserInterfaceComponent, childAnchor, null); //Verify child width Assert.AreEqual(800, childUserInterfaceComponent.PixelHeight); Assert.AreEqual(expectedChildPixelWidth, childUserInterfaceComponent.PixelWidth); @@ -478,6 +507,7 @@ public void TestUserInterfacePixelScaling( Assert.Inconclusive("Anchor factory not located."); if (UserInterfaceComponentFactory == null) Assert.Inconclusive("User interface factory not located."); + IWorld world = WorldFactory.CreateWorld(); //Create the anchor details IAnchorDetails topAnchorDetails = AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.TOP, AnchorUnits.PIXELS, 100); IAnchorDetails bottomAnchorDetails = AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.BOTTOM, AnchorUnits.PIXELS, 100); @@ -486,7 +516,7 @@ public void TestUserInterfacePixelScaling( //Craete the anchor IAnchor anchor = AnchorFactory.CreateAnchor(leftAnchorDetails, rightAnchorDetails, topAnchorDetails, bottomAnchorDetails); //Create a generic user interface component - IUserInterfaceComponent userInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent(anchor, null); + IUserInterfaceComponent userInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent(world, anchor, null); //Ensure user interface minimum scale correctness Assert.AreEqual(0, userInterfaceComponent.MinimumPixelHeight); Assert.AreEqual(expectedWidth, userInterfaceComponent.MinimumPixelWidth); @@ -520,6 +550,7 @@ public void TestUserInterfaceParentPixelScaling( Assert.Inconclusive("Anchor factory not located."); if (UserInterfaceComponentFactory == null) Assert.Inconclusive("User interface factory not located."); + IWorld world = WorldFactory.CreateWorld(); //Create the parent anchor details IAnchorDetails parentTopAnchorDetails = AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.TOP, AnchorUnits.PIXELS, 0); IAnchorDetails parentBottomAnchorDetails = AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.BOTTOM, AnchorUnits.PIXELS, 0); @@ -535,9 +566,9 @@ public void TestUserInterfaceParentPixelScaling( //Craete the anchor IAnchor childAnchor = AnchorFactory.CreateAnchor(childLeftAnchorDetails, childRightAnchorDetails, childTopAnchorDetails, childBottomAnchorDetails); //Create a generic user interface component - IUserInterfaceComponent parentUserInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent(parentAnchor, null); + IUserInterfaceComponent parentUserInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent(world, parentAnchor, null); //Create another generic user interface component - UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent(parentUserInterfaceComponent, childAnchor, null); + UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent(world, parentUserInterfaceComponent, childAnchor, null); //Check parent minimum width Assert.AreEqual(200, parentUserInterfaceComponent.MinimumPixelHeight); //Should be 200 as child component requires 100 space above, and 100 space below Assert.AreEqual(expectedParentWidth, parentUserInterfaceComponent.MinimumPixelWidth); //Should be 250 as child component requires 250 space left and none right. diff --git a/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceRaycastTests.cs b/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceRaycastTests.cs index 9d0620d8..05751c88 100644 --- a/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceRaycastTests.cs +++ b/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceRaycastTests.cs @@ -1,4 +1,5 @@ using CorgEng.Core.Dependencies; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.UserInterface.Anchors; using CorgEng.GenericInterfaces.UserInterface.Components; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -11,7 +12,7 @@ namespace CorgEng.Tests.UserInterfaceTests.UnitTests { [TestClass] - public class UserInterfaceRaycastTests + public class UserInterfaceRaycastTests : TestBase { [UsingDependency] @@ -23,6 +24,9 @@ public class UserInterfaceRaycastTests [UsingDependency] private static IAnchorDetailFactory AnchorDetailFactory; + [UsingDependency] + private static IWorldFactory WorldFactory; + [DataTestMethod] [DataRow(0, 0, 0)] [DataRow(0, 50, 50)] @@ -40,9 +44,11 @@ public void TestRaycast(int target, int x, int y) Assert.Inconclusive("Anchor factory not located."); if (UserInterfaceComponentFactory == null) Assert.Inconclusive("User interface factory not located."); + IWorld world = WorldFactory.CreateWorld(); //Create a root component //(0, 0) -> (1000, 1000) IUserInterfaceComponent parentUserInterfaceComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, null, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0), @@ -56,6 +62,7 @@ public void TestRaycast(int target, int x, int y) //Add a child component which can scale but starts with no height //(100, 100) -> (900, 900) IUserInterfaceComponent expandingComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, parentUserInterfaceComponent, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PERCENTAGE, 10, true), @@ -68,6 +75,7 @@ public void TestRaycast(int target, int x, int y) //Add a child component to that which has a huge scale //(100, 400) -> (900, 900) IUserInterfaceComponent bigComponent = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, expandingComponent, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0, true), diff --git a/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceXmlLoaderTests.cs b/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceXmlLoaderTests.cs index bf2effb3..b88963d1 100644 --- a/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceXmlLoaderTests.cs +++ b/CorgEng.Tests/UserInterfaceTests/UnitTests/UserInterfaceXmlLoaderTests.cs @@ -1,4 +1,5 @@ using CorgEng.Core.Dependencies; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.UserInterface.Anchors; using CorgEng.GenericInterfaces.UserInterface.Components; using CorgEng.GenericInterfaces.UserInterface.Generators; @@ -13,12 +14,15 @@ namespace CorgEng.Tests.UserInterfaceTests.UnitTests { [TestClass] - public class UserInterfaceXmlLoaderTests + public class UserInterfaceXmlLoaderTests : TestBase { [UsingDependency] private static IUserInterfaceXmlLoader UserInterfaceXmlLoader; + [UsingDependency] + private static IWorldFactory WorldFactory; + [TestMethod] public void TestDependencyImplementation() { @@ -38,7 +42,7 @@ public void TestFirstCorrectSide(string filepath, AnchorDirections left, AnchorD if (UserInterfaceXmlLoader == null) Assert.Inconclusive("User interface XML loader dependency was not injected."); //Check for loading errors - IUserInterfaceComponent Root = UserInterfaceXmlLoader.LoadUserInterface(filepath); + IUserInterfaceComponent Root = UserInterfaceXmlLoader.LoadUserInterface(WorldFactory.CreateWorld(), filepath); //If the returned value if null, fail Assert.IsNotNull(Root, "User interface loader returned null."); //Execute test @@ -62,7 +66,7 @@ public void TestFirstCorrectStrictness(string filepath, bool leftStrict, bool ri if (UserInterfaceXmlLoader == null) Assert.Inconclusive("User interface XML loader dependency was not injected."); //Check for loading errors - IUserInterfaceComponent Root = UserInterfaceXmlLoader.LoadUserInterface(filepath); + IUserInterfaceComponent Root = UserInterfaceXmlLoader.LoadUserInterface(WorldFactory.CreateWorld(), filepath); //If the returned value if null, fail Assert.IsNotNull(Root, "User interface loader returned null."); //Execute test @@ -86,7 +90,7 @@ public void TestFirstCorrectAnchorUnits(string filepath, AnchorUnits leftAnchorU if (UserInterfaceXmlLoader == null) Assert.Inconclusive("User interface XML loader dependency was not injected."); //Check for loading errors - IUserInterfaceComponent Root = UserInterfaceXmlLoader.LoadUserInterface(filepath); + IUserInterfaceComponent Root = UserInterfaceXmlLoader.LoadUserInterface(WorldFactory.CreateWorld(), filepath); //If the returned value if null, fail Assert.IsNotNull(Root, "User interface loader returned null."); //Execute test @@ -110,7 +114,7 @@ public void TestFirstCorrectAnchorOffset(string filepath, double leftAnchorAmoun if (UserInterfaceXmlLoader == null) Assert.Inconclusive("User interface XML loader dependency was not injected."); //Check for loading errors - IUserInterfaceComponent Root = UserInterfaceXmlLoader.LoadUserInterface(filepath); + IUserInterfaceComponent Root = UserInterfaceXmlLoader.LoadUserInterface(WorldFactory.CreateWorld(), filepath); //If the returned value if null, fail Assert.IsNotNull(Root, "User interface loader returned null."); //Execute test @@ -136,7 +140,7 @@ public void TestXmlLoadingErrors(string filepath) if (UserInterfaceXmlLoader == null) Assert.Inconclusive("User interface XML loader dependency was not injected."); //Check for loading errors - UserInterfaceXmlLoader.LoadUserInterface(filepath); + UserInterfaceXmlLoader.LoadUserInterface(WorldFactory.CreateWorld(), filepath); } [DataTestMethod] @@ -149,7 +153,7 @@ public void TestCorrectInterfaceComponentCount(string filepath, int expectedComp if (UserInterfaceXmlLoader == null) Assert.Inconclusive("User interface XML loader dependency was not injected."); //Check for loading errors - IUserInterfaceComponent Root = UserInterfaceXmlLoader.LoadUserInterface(filepath); + IUserInterfaceComponent Root = UserInterfaceXmlLoader.LoadUserInterface(WorldFactory.CreateWorld(), filepath); //If the returned value if null, fail Assert.IsNotNull(Root, "User interface loader returned null."); //Validate assumptions diff --git a/CorgEng.Tests/UtilityTypes/BatchTests.cs b/CorgEng.Tests/UtilityTypes/BatchTests.cs index ff66c379..9bf11af9 100644 --- a/CorgEng.Tests/UtilityTypes/BatchTests.cs +++ b/CorgEng.Tests/UtilityTypes/BatchTests.cs @@ -8,7 +8,7 @@ namespace CorgEng.Tests.UtilityTypes { [TestClass] - public class BatchTests + public class BatchTests : TestBase { private Vector TestVector { get; } = new Vector(); diff --git a/CorgEng.Tests/UtilityTypes/BinaryListTests.cs b/CorgEng.Tests/UtilityTypes/BinaryListTests.cs index 1a6e94db..72d84e3c 100644 --- a/CorgEng.Tests/UtilityTypes/BinaryListTests.cs +++ b/CorgEng.Tests/UtilityTypes/BinaryListTests.cs @@ -10,7 +10,7 @@ namespace CorgEng.Tests.UtilityTypes { [TestClass] - public class BinaryListTests + public class BinaryListTests : TestBase { [UsingDependency] diff --git a/CorgEng.Tests/UtilityTypes/MatrixTests/GeneralMatrixTests.cs b/CorgEng.Tests/UtilityTypes/MatrixTests/GeneralMatrixTests.cs index 2491492c..c0bb17ac 100644 --- a/CorgEng.Tests/UtilityTypes/MatrixTests/GeneralMatrixTests.cs +++ b/CorgEng.Tests/UtilityTypes/MatrixTests/GeneralMatrixTests.cs @@ -9,7 +9,7 @@ namespace CorgEng.Tests.UtilityTypes.MatrixTests { [TestClass] - public class GeneralMatrixTests + public class GeneralMatrixTests : TestBase { [TestMethod] diff --git a/CorgEng.Tests/UtilityTypes/MatrixTests/IdentityMatrixTests.cs b/CorgEng.Tests/UtilityTypes/MatrixTests/IdentityMatrixTests.cs index e754492a..26335705 100644 --- a/CorgEng.Tests/UtilityTypes/MatrixTests/IdentityMatrixTests.cs +++ b/CorgEng.Tests/UtilityTypes/MatrixTests/IdentityMatrixTests.cs @@ -12,7 +12,7 @@ namespace CorgEng.Tests.UtilityTypes.MatrixTests /// Summary description for IdentityMatrixTests /// [TestClass] - public class IdentityMatrixTests + public class IdentityMatrixTests : TestBase { [TestMethod] diff --git a/CorgEng.Tests/UtilityTypes/MatrixTests/MatrixDimensionErrorTests.cs b/CorgEng.Tests/UtilityTypes/MatrixTests/MatrixDimensionErrorTests.cs index 25ab9038..dc9a57c0 100644 --- a/CorgEng.Tests/UtilityTypes/MatrixTests/MatrixDimensionErrorTests.cs +++ b/CorgEng.Tests/UtilityTypes/MatrixTests/MatrixDimensionErrorTests.cs @@ -9,7 +9,7 @@ namespace CorgEng.Tests.UtilityTypes.MatrixTests { [TestClass] - public class MatrixDimensionErrorTests + public class MatrixDimensionErrorTests : TestBase { [TestMethod] [ExpectedException(typeof(InvalidMatrixDimensionError))] diff --git a/CorgEng.Tests/UtilityTypes/MatrixTests/MultiplicationMatrixTests.cs b/CorgEng.Tests/UtilityTypes/MatrixTests/MultiplicationMatrixTests.cs index f4dbb967..52299074 100644 --- a/CorgEng.Tests/UtilityTypes/MatrixTests/MultiplicationMatrixTests.cs +++ b/CorgEng.Tests/UtilityTypes/MatrixTests/MultiplicationMatrixTests.cs @@ -9,7 +9,7 @@ namespace CorgEng.Tests.UtilityTypes.MatrixTests { [TestClass] - public class MultiplicationMatrixTests + public class MultiplicationMatrixTests : TestBase { [TestMethod] diff --git a/CorgEng.Tests/UtilityTypes/MatrixTests/TranslationMatrixTests.cs b/CorgEng.Tests/UtilityTypes/MatrixTests/TranslationMatrixTests.cs index 135dc1f8..8c633332 100644 --- a/CorgEng.Tests/UtilityTypes/MatrixTests/TranslationMatrixTests.cs +++ b/CorgEng.Tests/UtilityTypes/MatrixTests/TranslationMatrixTests.cs @@ -9,7 +9,7 @@ namespace CorgEng.Tests.UtilityTypes.MatrixTests { [TestClass] - public class TranslationMatrixTests + public class TranslationMatrixTests : TestBase { [TestMethod] public void TestNoTranslation() diff --git a/CorgEng.Tests/UtilityTypes/VectorTests.cs b/CorgEng.Tests/UtilityTypes/VectorTests.cs index 54b14ae7..7e649326 100644 --- a/CorgEng.Tests/UtilityTypes/VectorTests.cs +++ b/CorgEng.Tests/UtilityTypes/VectorTests.cs @@ -10,7 +10,7 @@ namespace CorgEng.Tests.UtilityTypes { [TestClass] - public class VectorTests + public class VectorTests : TestBase { [TestMethod] diff --git a/CorgEng.Tests/World/WorldEntitySystem.cs b/CorgEng.Tests/World/WorldEntitySystem.cs new file mode 100644 index 00000000..1128618f --- /dev/null +++ b/CorgEng.Tests/World/WorldEntitySystem.cs @@ -0,0 +1,23 @@ +using CorgEng.Core.Dependencies; +using CorgEng.EntityComponentSystem.Systems; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.Tests.World +{ + [TestClass] + internal class WorldEntitySystem + { + + [UsingDependency] + private static IWorldFactory WorldFactory = null!; + + + + } +} diff --git a/CorgEng.Tests/World/WorldTests.cs b/CorgEng.Tests/World/WorldTests.cs index 25c474a4..fe49f887 100644 --- a/CorgEng.Tests/World/WorldTests.cs +++ b/CorgEng.Tests/World/WorldTests.cs @@ -1,5 +1,6 @@ using CorgEng.Core.Dependencies; using CorgEng.EntityComponentSystem.Entities; +using CorgEng.EntityComponentSystem.Systems; using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.World; using CorgEng.World.Components; @@ -14,11 +15,44 @@ namespace CorgEng.Tests.World { [TestClass] - public class WorldTests + public class WorldTests : TestBase { [UsingDependency] - public static IWorld WorldAccess; + public static IEntityPositionTracker WorldAccess; + + [UsingDependency] + public static IWorldFactory WorldFactory = null!; + + private class TestSystem : EntitySystem + { + public int setupTimes = 0; + + public override EntitySystemFlags SystemFlags => EntitySystemFlags.HOST_SYSTEM | EntitySystemFlags.CLIENT_SYSTEM; + + public override void SystemSetup(IWorld world) + { + setupTimes++; + } + } + + [TestMethod] + [Timeout(1000)] + public void TestCreatingWorlds() + { + IWorld world = WorldFactory.CreateWorld(); + Assert.AreEqual(1, world.EntitySystemManager.GetSingleton().setupTimes); + world = WorldFactory.CreateWorld(); + Assert.AreEqual(1, world.EntitySystemManager.GetSingleton().setupTimes); + world = WorldFactory.CreateWorld(); + Assert.AreEqual(1, world.EntitySystemManager.GetSingleton().setupTimes); + world = WorldFactory.CreateWorld(); + Assert.AreEqual(1, world.EntitySystemManager.GetSingleton().setupTimes); + world = WorldFactory.CreateWorld(); + Assert.AreEqual(1, world.EntitySystemManager.GetSingleton().setupTimes); + world = WorldFactory.CreateWorld(); + Assert.AreEqual(1, world.EntitySystemManager.GetSingleton().setupTimes); + } [TestMethod] public void TestMassInsertion() diff --git a/CorgEng.UserInterface/Generators/UserInterfaceXmlLoader.cs b/CorgEng.UserInterface/Generators/UserInterfaceXmlLoader.cs index 16fac617..362a2868 100644 --- a/CorgEng.UserInterface/Generators/UserInterfaceXmlLoader.cs +++ b/CorgEng.UserInterface/Generators/UserInterfaceXmlLoader.cs @@ -1,5 +1,6 @@ using CorgEng.Core.Dependencies; using CorgEng.DependencyInjection.Dependencies; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Logging; using CorgEng.GenericInterfaces.UserInterface.Anchors; using CorgEng.GenericInterfaces.UserInterface.Components; @@ -29,14 +30,14 @@ public class UserInterfaceXmlLoader : IUserInterfaceXmlLoader [UsingDependency] private static ILogger Logger; - public IUserInterfaceComponent LoadUserInterface(string filepath) + public IUserInterfaceComponent LoadUserInterface(IWorld world, string filepath) { try { //Load the XML file provided XElement userInterfaceElement = XElement.Load(filepath); //Load the interface component - return LoadFromXmlComponent(userInterfaceElement); + return LoadFromXmlComponent(world, userInterfaceElement); } catch (Exception e) { @@ -45,7 +46,7 @@ public IUserInterfaceComponent LoadUserInterface(string filepath) } } - private IUserInterfaceComponent LoadFromXmlComponent(XElement node, IUserInterfaceComponent parent = null) + private IUserInterfaceComponent LoadFromXmlComponent(IWorld world, XElement node, IUserInterfaceComponent parent = null) { try { @@ -54,6 +55,7 @@ private IUserInterfaceComponent LoadFromXmlComponent(XElement node, IUserInterfa parameters = node.Attributes().ToDictionary(attribute => attribute.Name.LocalName, attribute => attribute.Value); //Create a generic user interface component IUserInterfaceComponent currentElement = UserInterfaceComponentFactory.CreateUserInterfaceComponent( + world, node.Name.LocalName, parent, AnchorFactory.CreateAnchor( @@ -69,7 +71,7 @@ private IUserInterfaceComponent LoadFromXmlComponent(XElement node, IUserInterfa foreach (XElement childElement in node.Elements()) { //Children are automatically added when created with a parent - LoadFromXmlComponent(childElement, currentElement); + LoadFromXmlComponent(world, childElement, currentElement); } } ); diff --git a/CorgEng.UserInterface/Popups/PopupManager.cs b/CorgEng.UserInterface/Popups/PopupManager.cs index 661acbbb..07dae531 100644 --- a/CorgEng.UserInterface/Popups/PopupManager.cs +++ b/CorgEng.UserInterface/Popups/PopupManager.cs @@ -1,6 +1,7 @@ using CorgEng.Core.Dependencies; using CorgEng.Core.Modules; using CorgEng.DependencyInjection.Dependencies; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.UserInterface.Anchors; using CorgEng.GenericInterfaces.UserInterface.Components; using CorgEng.GenericInterfaces.UserInterface.Popups; @@ -12,7 +13,6 @@ namespace CorgEng.UserInterface.Popups { - [Dependency] internal class PopupManager : IPopupManager { @@ -30,10 +30,11 @@ internal class PopupManager : IPopupManager /// private static IUserInterfaceComponent popupRootInterface; - [ModuleLoad(mainThread = true)] - public static void InitialisePopupInterface() + [WorldInitialise] + public static void InitialisePopupInterface(IWorld world) { popupRootInterface = UserInterfaceComponentFactory.CreateGenericUserInterfaceComponent( + world, null, AnchorFactory.CreateAnchor( AnchorDetailFactory.CreateAnchorDetails(AnchorDirections.LEFT, AnchorUnits.PIXELS, 0), diff --git a/CorgEng.UserInterface/Systems/UserInterfaceClickSystem.cs b/CorgEng.UserInterface/Systems/UserInterfaceClickSystem.cs index 39941e09..0727fdc7 100644 --- a/CorgEng.UserInterface/Systems/UserInterfaceClickSystem.cs +++ b/CorgEng.UserInterface/Systems/UserInterfaceClickSystem.cs @@ -16,7 +16,7 @@ public class UserInterfaceClickSystem : EntitySystem public override EntitySystemFlags SystemFlags => EntitySystemFlags.CLIENT_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(OnUserInterfaceElementClicked); RegisterLocalEvent(OnUserInterfaceActionElementClicked); diff --git a/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceBox.cs b/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceBox.cs index 71ab3121..0390e42d 100644 --- a/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceBox.cs +++ b/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceBox.cs @@ -1,5 +1,6 @@ using CorgEng.Core; using CorgEng.Core.Dependencies; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Logging; using CorgEng.GenericInterfaces.UserInterface.Anchors; using CorgEng.GenericInterfaces.UserInterface.Components; @@ -31,12 +32,12 @@ public BoxStyle Style get => boxRenderObject.Style; } - public UserInterfaceBox(IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments) : base(parent, anchorDetails, arguments) + public UserInterfaceBox(IWorld world, IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments) : base(world, parent, anchorDetails, arguments) { Setup(arguments); } - public UserInterfaceBox(IAnchor anchorDetails, IDictionary arguments) : base(anchorDetails, arguments) + public UserInterfaceBox(IWorld world, IAnchor anchorDetails, IDictionary arguments) : base(world, anchorDetails, arguments) { Setup(arguments); } diff --git a/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceButton.cs b/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceButton.cs index 75681842..d655626e 100644 --- a/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceButton.cs +++ b/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceButton.cs @@ -1,4 +1,5 @@ using CorgEng.Core; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.UserInterface.Anchors; using CorgEng.GenericInterfaces.UserInterface.Components; using CorgEng.GenericInterfaces.UtilityTypes; @@ -15,11 +16,11 @@ namespace CorgEng.UserInterface.Components internal class UserInterfaceButton : UserInterfaceBox { - public UserInterfaceButton(IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments) : base(parent, anchorDetails, arguments) + public UserInterfaceButton(IWorld world, IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments) : base(world, parent, anchorDetails, arguments) { } - public UserInterfaceButton(IAnchor anchorDetails, IDictionary arguments) : base(anchorDetails, arguments) + public UserInterfaceButton(IWorld world, IAnchor anchorDetails, IDictionary arguments) : base(world, anchorDetails, arguments) { } diff --git a/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceComponent.cs b/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceComponent.cs index 43ec6cc0..e26212af 100644 --- a/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceComponent.cs +++ b/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceComponent.cs @@ -28,9 +28,6 @@ internal class UserInterfaceComponent : RenderCore, IUserInterfaceComponent [UsingDependency] private static ILogger Logger; - [UsingDependency] - private static IEntityFactory EntityFactory; - /// /// The anchor details for this component /// @@ -85,7 +82,7 @@ public IAnchor Anchor /// so we can have a component based user interface model despite /// the oversight of not implementing it this way initially /// - public IEntity ComponentHolder { get; } = EntityFactory.CreateEmptyEntity(null); + public IEntity ComponentHolder { get; } /// /// Is this component fullscreen? @@ -142,6 +139,8 @@ public IAnchor Anchor public IDictionary Parameters { get; private set; } = null; + public IWorld World => world; + /// /// A unique identifier for this component. /// @@ -154,7 +153,7 @@ public IAnchor Anchor private static object lockObject = new object(); - public UserInterfaceComponent(IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments) : this(anchorDetails, arguments) + public UserInterfaceComponent(IWorld world, IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments) : this(world, anchorDetails, arguments) { //Set the parent Parent = parent; @@ -162,8 +161,9 @@ public UserInterfaceComponent(IUserInterfaceComponent parent, IAnchor anchorDeta Parent?.AddChild(this); } - public UserInterfaceComponent(IAnchor anchorDetails, IDictionary arguments) + public UserInterfaceComponent(IWorld world, IAnchor anchorDetails, IDictionary arguments) : base(world) { + ComponentHolder = world.EntityManager.CreateEmptyEntity(null); Parameters = arguments; RegisterArguments(); ParseArguments(arguments); diff --git a/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceComponentFactory.cs b/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceComponentFactory.cs index 53e6fc45..d0f89efd 100644 --- a/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceComponentFactory.cs +++ b/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceComponentFactory.cs @@ -1,4 +1,5 @@ using CorgEng.DependencyInjection.Dependencies; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.UserInterface.Anchors; using CorgEng.GenericInterfaces.UserInterface.Components; using CorgEng.UserInterface.UserInterfaceComponents; @@ -16,45 +17,45 @@ internal class UserInterfaceComponentFactory : IUserInterfaceComponentFactory private static Dictionary Empty { get; } = new Dictionary(); - public IUserInterfaceComponent CreateGenericUserInterfaceComponent(IUserInterfaceComponent parent, IAnchor anchorDetails, Action preInitialiseAction) + public IUserInterfaceComponent CreateGenericUserInterfaceComponent(IWorld world, IUserInterfaceComponent parent, IAnchor anchorDetails, Action preInitialiseAction) { - UserInterfaceComponent createdComponent = new UserInterfaceComponent(parent, anchorDetails, Empty); + UserInterfaceComponent createdComponent = new UserInterfaceComponent(world, parent, anchorDetails, Empty); preInitialiseAction?.Invoke(createdComponent); createdComponent.Initialize(); return createdComponent; } - public IUserInterfaceComponent CreateGenericUserInterfaceComponent(IAnchor anchorDetails, Action preInitialiseAction) + public IUserInterfaceComponent CreateGenericUserInterfaceComponent(IWorld world, IAnchor anchorDetails, Action preInitialiseAction) { - UserInterfaceComponent createdComponent = new UserInterfaceComponent(anchorDetails, Empty); + UserInterfaceComponent createdComponent = new UserInterfaceComponent(world, anchorDetails, Empty); preInitialiseAction?.Invoke(createdComponent); createdComponent.Initialize(); return createdComponent; } - public IUserInterfaceComponent CreateUserInterfaceComponent(string componentType, IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments, Action preInitialiseAction) + public IUserInterfaceComponent CreateUserInterfaceComponent(IWorld world, string componentType, IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments, Action preInitialiseAction) { UserInterfaceComponent createdComponent; switch (componentType) { case "BoxComponent": - createdComponent = new UserInterfaceBox(parent, anchorDetails, arguments); + createdComponent = new UserInterfaceBox(world, parent, anchorDetails, arguments); break; case "UserInterface": case "UserInterfaceComponent": - createdComponent = new UserInterfaceComponent(parent, anchorDetails, arguments); + createdComponent = new UserInterfaceComponent(world, parent, anchorDetails, arguments); break; case "UserInterfaceButton": - createdComponent = new UserInterfaceButton(parent, anchorDetails, arguments); + createdComponent = new UserInterfaceButton(world, parent, anchorDetails, arguments); break; case "DropdownComponent": - createdComponent = new UserInterfaceDropdown(parent, anchorDetails, arguments); + createdComponent = new UserInterfaceDropdown(world, parent, anchorDetails, arguments); break; case "TextComponent": - createdComponent = new UserInterfaceText(parent, anchorDetails, arguments); + createdComponent = new UserInterfaceText(world, parent, anchorDetails, arguments); break; case "IconComponent": - createdComponent = new UserInterfaceIcon(parent, anchorDetails, arguments); + createdComponent = new UserInterfaceIcon(world, parent, anchorDetails, arguments); break; default: throw new NotImplementedException($"The component {componentType} is not recognised."); @@ -64,9 +65,9 @@ public IUserInterfaceComponent CreateUserInterfaceComponent(string componentType return createdComponent; } - public IUserInterfaceComponent CreateUserInterfaceComponent(string componentType, IAnchor anchorDetails, IDictionary arguments, Action preInitialiseAction) + public IUserInterfaceComponent CreateUserInterfaceComponent(IWorld world, string componentType, IAnchor anchorDetails, IDictionary arguments, Action preInitialiseAction) { - return CreateUserInterfaceComponent(componentType, null, anchorDetails, arguments, preInitialiseAction); + return CreateUserInterfaceComponent(world, componentType, null, anchorDetails, arguments, preInitialiseAction); } } } diff --git a/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceDropdown.cs b/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceDropdown.cs index 31157b64..82ad7d81 100644 --- a/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceDropdown.cs +++ b/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceDropdown.cs @@ -1,4 +1,5 @@ -using CorgEng.GenericInterfaces.UserInterface.Anchors; +using CorgEng.GenericInterfaces.EntityComponentSystem; +using CorgEng.GenericInterfaces.UserInterface.Anchors; using CorgEng.GenericInterfaces.UserInterface.Components; using CorgEng.UserInterface.Components; using System; @@ -31,12 +32,12 @@ internal class UserInterfaceDropdown : UserInterfaceBox private List expandButton = new List(); - public UserInterfaceDropdown(IAnchor anchorDetails, IDictionary arguments) : base(anchorDetails, arguments) + public UserInterfaceDropdown(IWorld world, IAnchor anchorDetails, IDictionary arguments) : base(world, anchorDetails, arguments) { ToggleExpansion(); } - public UserInterfaceDropdown(IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments) : base(parent, anchorDetails, arguments) + public UserInterfaceDropdown(IWorld world, IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments) : base(world, parent, anchorDetails, arguments) { ToggleExpansion(); } diff --git a/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceIcon.cs b/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceIcon.cs index 88c8f51c..35cd6c3e 100644 --- a/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceIcon.cs +++ b/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceIcon.cs @@ -1,6 +1,7 @@ using CorgEng.Constants; using CorgEng.Core; using CorgEng.Core.Dependencies; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Logging; using CorgEng.GenericInterfaces.Rendering.Icons; using CorgEng.GenericInterfaces.Rendering.Textures; @@ -37,12 +38,12 @@ internal class UserInterfaceIcon : UserInterfaceComponent /// protected override bool ScreencastInclude { get; } = false; - public UserInterfaceIcon(IAnchor anchorDetails, IDictionary arguments) : base(anchorDetails, arguments) + public UserInterfaceIcon(IWorld world, IAnchor anchorDetails, IDictionary arguments) : base(world, anchorDetails, arguments) { Init(arguments); } - public UserInterfaceIcon(IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments) : base(parent, anchorDetails, arguments) + public UserInterfaceIcon(IWorld world, IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments) : base(world, parent, anchorDetails, arguments) { Init(arguments); } diff --git a/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceText.cs b/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceText.cs index e8680151..dfc4ee7a 100644 --- a/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceText.cs +++ b/CorgEng.UserInterface/UserInterfaceComponents/UserInterfaceText.cs @@ -1,5 +1,6 @@ using CorgEng.Core; using CorgEng.Core.Dependencies; +using CorgEng.GenericInterfaces.EntityComponentSystem; using CorgEng.GenericInterfaces.Font.Fonts; using CorgEng.GenericInterfaces.Rendering.Cameras.Isometric; using CorgEng.GenericInterfaces.Rendering.Renderers.SpriteRendering; @@ -44,12 +45,12 @@ internal class UserInterfaceText : UserInterfaceComponent private IFont font; - public UserInterfaceText(IAnchor anchorDetails, IDictionary arguments) : base(anchorDetails, arguments) + public UserInterfaceText(IWorld world, IAnchor anchorDetails, IDictionary arguments) : base(world, anchorDetails, arguments) { Setup(arguments); } - public UserInterfaceText(IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments) : base(parent, anchorDetails, arguments) + public UserInterfaceText(IWorld world, IUserInterfaceComponent parent, IAnchor anchorDetails, IDictionary arguments) : base(world, parent, anchorDetails, arguments) { Setup(arguments); } diff --git a/CorgEng.UtilityTypes/BindableProperties/BindableProperty.cs b/CorgEng.UtilityTypes/BindableProperties/BindableProperty.cs index cc3caea1..5f4de780 100644 --- a/CorgEng.UtilityTypes/BindableProperties/BindableProperty.cs +++ b/CorgEng.UtilityTypes/BindableProperties/BindableProperty.cs @@ -1,19 +1,28 @@ using CorgEng.GenericInterfaces.UtilityTypes; using System; +using System.Threading.Tasks; namespace CorgEng.UtilityTypes.BindableProperties { public class BindableProperty : IBindableProperty { - private T _value = default; - public T Value + protected T _value = default; + public virtual T Value { get => _value; set { if (value == null && (!typeof(T).IsGenericType || typeof(T).GetGenericTypeDefinition() != typeof(Nullable<>))) throw new ArgumentNullException("Attempting to set a bindable property of non-nullable type to null."); + // Stop listening for old value changes + if (_value != null && value is IListenable listener) + listener.ValueChanged -= ChangeReact; + // Update the value _value = value; + // Start listening for value changes + if (_value != null && value is IListenable listener2) + listener2.ValueChanged += ChangeReact; + // Indicate that we have changed TriggerChanged(); } } @@ -23,9 +32,17 @@ public T Value public BindableProperty(T value) { _value = value; + // If the value is a listenable type, start listening + if (value is IListenable listener) + listener.ValueChanged += ChangeReact; } - public void TriggerChanged() + private void ChangeReact(object? sender, EventArgs e) + { + TriggerChanged(); + } + + public virtual void TriggerChanged() { ValueChanged?.Invoke(_value, EventArgs.Empty); } diff --git a/CorgEng.UtilityTypes/Linecasting/LineCaster.cs b/CorgEng.UtilityTypes/Linecasting/LineCaster.cs index fc69bc73..a9cf90fd 100644 --- a/CorgEng.UtilityTypes/Linecasting/LineCaster.cs +++ b/CorgEng.UtilityTypes/Linecasting/LineCaster.cs @@ -17,7 +17,7 @@ internal class LineCaster : ILineCaster private const float VERY_SMALL_VALUE = 0.00001f; [UsingDependency] - private static IWorld World = null!; + private static IEntityPositionTracker World = null!; /// /// Lazilly evaluated enumerable of locations between start and end. diff --git a/CorgEng.UtilityTypes/Monads/Result.cs b/CorgEng.UtilityTypes/Monads/Result.cs new file mode 100644 index 00000000..10fd698f --- /dev/null +++ b/CorgEng.UtilityTypes/Monads/Result.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CorgEng.UtilityTypes.Monads +{ + public class Result + { + + public virtual bool HasValue { get; } = true; + + private T value; + + public Result(T value) + { + this.value = value; + } + + public Result Fail(Action failureAction) + { + if (HasValue) + return this; + failureAction(); + return this; + } + + public Result Then(Action successAction) + { + if (!HasValue) + return this; + successAction(value); + return this; + } + + } + + public class Failure : Result + { + + public Failure() : base(default) + { } + + public override bool HasValue => false; + } +} diff --git a/CorgEng.UtilityTypes/Vectors/Vector.cs b/CorgEng.UtilityTypes/Vectors/Vector.cs index bdff54fa..343dc5aa 100644 --- a/CorgEng.UtilityTypes/Vectors/Vector.cs +++ b/CorgEng.UtilityTypes/Vectors/Vector.cs @@ -20,13 +20,13 @@ public struct Vector : IVector, ICustomSerialisationBehaviour public Vector(params T[] values) { Values = values; - OnChange = null; + ValueChanged = null; sizeOfT = Marshal.SizeOf(typeof(T)); } private T[] Values; - public event EventHandler OnChange; + public event EventHandler ValueChanged; /// /// Overload for the [] operator, returns the element of the vector. @@ -36,7 +36,7 @@ public T this[int x] get { return Values[x]; } set { Values[x] = value; - OnChange?.Invoke(this, EventArgs.Empty); + ValueChanged?.Invoke(this, EventArgs.Empty); } } @@ -379,7 +379,7 @@ public void DeserialiseFrom(BinaryReader binaryReader) //Construct //new T[length] causes a stack overflow exception. Values = new T[length]; - OnChange = null; + ValueChanged = null; sizeOfT = Marshal.SizeOf(typeof(T)); //Read Ts for (int i = length - 1; i >= 0; i--) diff --git a/CorgEng.World/EntitySystems/WorldSystem.cs b/CorgEng.World/EntitySystems/WorldSystem.cs index 0cd736a6..edb90fd8 100644 --- a/CorgEng.World/EntitySystems/WorldSystem.cs +++ b/CorgEng.World/EntitySystems/WorldSystem.cs @@ -19,11 +19,11 @@ public class WorldSystem : EntitySystem { [UsingDependency] - private static IWorld WorldAccess = null!; + private static IEntityPositionTracker WorldAccess = null!; public override EntitySystemFlags SystemFlags { get; } = EntitySystemFlags.HOST_SYSTEM; - public override void SystemSetup() + public override void SystemSetup(IWorld world) { RegisterLocalEvent(OnEntityCreated); RegisterLocalEvent(OnEntityLocationChanged); @@ -46,7 +46,7 @@ private void OnEntityCreated(IEntity entity, TrackComponent trackComponent, Comp if (trackComponent.Parent.HasComponent()) return; //Add the entity to the world - WorldAccess.AddEntity(trackComponent.Key, trackComponent, trackComponent.Transform.Position.X, trackComponent.Transform.Position.Y, 0); + WorldAccess.AddEntity(trackComponent.Key, trackComponent, trackComponent.Transform.Position.Value.X, trackComponent.Transform.Position.Value.Y, 0); trackComponent.isTracking = true; } @@ -68,7 +68,7 @@ private void OnEntityLocationChanged(IEntity entity, TrackComponent trackCompone { //Start tracking trackComponent.isTracking = true; - WorldAccess.AddEntity(trackComponent.Key, trackComponent, trackComponent.Transform.Position.X, trackComponent.Transform.Position.Y, 0); + WorldAccess.AddEntity(trackComponent.Key, trackComponent, trackComponent.Transform.Position.Value.X, trackComponent.Transform.Position.Value.Y, 0); } } diff --git a/CorgEng.World/WorldTracking/World.cs b/CorgEng.World/WorldTracking/EntityPositionTracker.cs similarity index 98% rename from CorgEng.World/WorldTracking/World.cs rename to CorgEng.World/WorldTracking/EntityPositionTracker.cs index 1df46cd1..a89ee4e9 100644 --- a/CorgEng.World/WorldTracking/World.cs +++ b/CorgEng.World/WorldTracking/EntityPositionTracker.cs @@ -17,7 +17,7 @@ namespace CorgEng.World.WorldTracking { [Dependency] - internal class World : IWorld + internal class EntityPositionTracker : IEntityPositionTracker { [UsingDependency]