diff --git a/OctoAwesome/Design Documents/Notifications_Multiplayer_Rework.md b/OctoAwesome/Design Documents/Notifications_Multiplayer_Rework.md new file mode 100644 index 00000000..d77cc27e --- /dev/null +++ b/OctoAwesome/Design Documents/Notifications_Multiplayer_Rework.md @@ -0,0 +1,26 @@ +Fragen: +1. Was macht eine Notification aus? + 1. Will man steuern können, ob Notifications gesendet werden (Beibehaltung des Network Channels)? + 2. Einfach jede senden? +2. Darf man am PAH (PackageActionHub) auf eine Notification Registern? + 1. Dürfen mehrere Leute an einer Notification am PAH registrieren? +3. Sollte Notification den Target Channel beinhalten? + 1. Darf eine Notification nur in ein Target Channel gepusht werden? +4. Mehr über Updatehub machen und weniger über locale Relays? + +Antworten: +1. Package Flag Notification + 1. Ja (Wie noch zu klären) + 1. Network Channel beibehalten fürs senden + 2. UpdateHub prüft auf SerID Existenz und sendet bei Ja vorhanden + 3. Man regelt das über die AddSource Methode im Update hub, das hießt der updatehub kann das intern regeln, das wäre ein Mixtur aus 1 und 2. New Parameter SendOverNetwork + 2. Nein +2. Ja + 1. Wäre toll, aber aufwand +3. Vlt. müsste nach SerId kommen und wäre damit Pflichtfeld. Als erweiterung vom UpdateHub + 1. Eher nicht, sondern nur mit multi Relays / PushNetworkOnly +4. Joa why not? Maxi sieht damit kein Problem + +Aussagen: +1. Alle "Notifications" eigene Ser Id +2. Serializer Neue Methode SerializeNetwork wegen SerId \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Basics/AssemblyInfo.cs b/OctoAwesome/OctoAwesome.Basics/AssemblyInfo.cs new file mode 100644 index 00000000..6d08a197 --- /dev/null +++ b/OctoAwesome/OctoAwesome.Basics/AssemblyInfo.cs @@ -0,0 +1,52 @@ +using OctoAwesome; +using OctoAwesome.Basics; +using OctoAwesome.Basics.Entities; +using OctoAwesome.Basics.EntityComponents; +using OctoAwesome.Basics.FunctionBlocks; +using OctoAwesome.Basics.SimulationComponents; +using OctoAwesome.Basics.UI.Components; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +[assembly: NoosonConfiguration( + GenerateDeserializeExtension = false, + DisableWarnings = true, + GenerateStaticDeserializeWithCtor = true, + GenerateDeserializeOnInstance = true, + GenerateStaticSerialize = true, + GenerateStaticDeserializeIntoInstance = true, + NameOfStaticDeserializeWithCtor = "DeserializeAndCreate", + NameOfDeserializeOnInstance = "Deserialize", + NameOfStaticDeserializeIntoInstance = "Deserialize", + NameOfStaticDeserializeWithOutParams = "DeserializeOut")] + +[assembly: SerializationId(2, 1)] +[assembly: SerializationId(2, 2)] +[assembly: SerializationId(2, 3)] +[assembly: SerializationId(2, 4)] +[assembly: SerializationId(2, 5)] +[assembly: SerializationId(2, 6)] +[assembly: SerializationId(2, 7)] +[assembly: SerializationId(2, 8)] +[assembly: SerializationId(2, 9)] +[assembly: SerializationId(2, 10)] +[assembly: SerializationId(2, 11)] +[assembly: SerializationId(2, 12)] +[assembly: SerializationId(2, 13)] +[assembly: SerializationId(2, 14)] +[assembly: SerializationId(2, 15)] +[assembly: SerializationId(2, 16)] +[assembly: SerializationId(2, 17)] +[assembly: SerializationId(2, 18)] +[assembly: SerializationId(2, 19)] +[assembly: SerializationId(2, 20)] +[assembly: SerializationId(2, 21)] +[assembly: SerializationId(2, 22)] +[assembly: SerializationId(2, 23)] +[assembly: SerializationId(2, 24)] +[assembly: SerializationId(2, 25)] +[assembly: SerializationId(2, 26)] diff --git a/OctoAwesome/OctoAwesome.Basics/ComplexPlanet.cs b/OctoAwesome/OctoAwesome.Basics/ComplexPlanet.cs index 09fa0249..46bfedc5 100644 --- a/OctoAwesome/OctoAwesome.Basics/ComplexPlanet.cs +++ b/OctoAwesome/OctoAwesome.Basics/ComplexPlanet.cs @@ -1,4 +1,6 @@ using OctoAwesome.Basics.Biomes; +using OctoAwesome.Serialization; + using OctoAwesome.Location; using System; @@ -10,9 +12,11 @@ namespace OctoAwesome.Basics /// /// A complex planet implementation with complex features. /// - public class ComplexPlanet : Planet + [SerializationId(), Nooson] + public partial class ComplexPlanet : Planet, IConstructionSerializable { // The gravitational constant was chosen on purpose to be that "big", see Issue #220 + private const double GravitationalConstant = 6.67e-7; private SurfaceBiomeGenerator? biomeGenerator; @@ -20,6 +24,7 @@ public class ComplexPlanet : Planet /// /// Gets the biome generator used for generating biomes on the planet. /// + [NoosonIgnore] public SurfaceBiomeGenerator BiomeGenerator { get @@ -49,6 +54,8 @@ public ComplexPlanet(int id, Guid universe, Index3 size, IMapGenerator generator Initialize(); } + + /// /// Initializes a new instance of the class. /// @@ -56,13 +63,6 @@ public ComplexPlanet(int id, Guid universe, Index3 size, IMapGenerator generator public ComplexPlanet(IMapGenerator generator) : base(generator) { - //Initalize(); - } - - /// - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); Initialize(); } diff --git a/OctoAwesome/OctoAwesome.Basics/ComplexPlanetGenerator.cs b/OctoAwesome/OctoAwesome.Basics/ComplexPlanetGenerator.cs index 5637d9e9..4231b8d1 100644 --- a/OctoAwesome/OctoAwesome.Basics/ComplexPlanetGenerator.cs +++ b/OctoAwesome/OctoAwesome.Basics/ComplexPlanetGenerator.cs @@ -3,18 +3,18 @@ using OctoAwesome.Definitions; using OctoAwesome.Location; using OctoAwesome.Pooling; +using OctoAwesome.Serialization; using System; using System.Buffers; using System.IO; -using System.Linq; namespace OctoAwesome.Basics { /// /// Map generator used for generating a . /// - public class ComplexPlanetGenerator : IMapGenerator + public partial class ComplexPlanetGenerator : IMapGenerator { private readonly ChunkPool chunkPool; @@ -58,7 +58,7 @@ ushort GetBlockDefinitionInfo() IChunk[] chunks = new IChunk[planet.Size.Z]; for (int i = 0; i < planet.Size.Z; i++) - chunks[i] = chunkPool.Rent(new Index3(index, i), localPlanet); + chunks[i] = chunkPool.Rent(new Index3(index, i), localPlanet.Id); int obersteSchicht; bool surfaceBlock; @@ -148,7 +148,7 @@ ushort GetBlockDefinitionInfo() } } ArrayPool.Shared.Return(localHeightmap); - ChunkColumn column = new ChunkColumn(chunks, localPlanet, index); + ChunkColumn column = new ChunkColumn(chunks, localPlanet.Id, index); column.CalculateHeights(); return column; } @@ -165,7 +165,7 @@ public IPlanet GeneratePlanet(Stream stream) /// public IChunkColumn GenerateColumn(Stream stream, IPlanet planet, Index2 index) { - IChunkColumn column = new ChunkColumn(planet); + IChunkColumn column = new ChunkColumn(planet.Id); using (var reader = new BinaryReader(stream)) column.Deserialize(reader); return column; diff --git a/OctoAwesome/OctoAwesome.Basics/DebugMapGenerator.cs b/OctoAwesome/OctoAwesome.Basics/DebugMapGenerator.cs index 4d05ef3d..28d5509c 100644 --- a/OctoAwesome/OctoAwesome.Basics/DebugMapGenerator.cs +++ b/OctoAwesome/OctoAwesome.Basics/DebugMapGenerator.cs @@ -30,11 +30,11 @@ public IChunkColumn GenerateColumn(IDefinitionManager definitionManager, IPlanet IChunk[] result = new IChunk[planet.Size.Z]; - ChunkColumn column = new ChunkColumn(result, planet, index); + ChunkColumn column = new ChunkColumn(result, planet.Id, index); for (int layer = 0; layer < planet.Size.Z; layer++) - result[layer] = new Chunk(new Index3(index.X, index.Y, layer), planet); + result[layer] = new Chunk(new Index3(index.X, index.Y, layer), planet.Id); int part = (planet.Size.Z * Chunk.CHUNKSIZE_Z) / 4; @@ -74,7 +74,7 @@ public IPlanet GeneratePlanet(Stream stream) /// public IChunkColumn GenerateColumn(Stream stream, IPlanet planet, Index2 index) { - IChunkColumn column = new ChunkColumn(planet); + IChunkColumn column = new ChunkColumn(planet.Id); using (var reader = new BinaryReader(stream)) column.Deserialize(reader); return column; diff --git a/OctoAwesome/OctoAwesome.Basics/Definitions/Items/ChestItem.cs b/OctoAwesome/OctoAwesome.Basics/Definitions/Items/ChestItem.cs index 4cc98c99..e34bff10 100644 --- a/OctoAwesome/OctoAwesome.Basics/Definitions/Items/ChestItem.cs +++ b/OctoAwesome/OctoAwesome.Basics/Definitions/Items/ChestItem.cs @@ -18,6 +18,7 @@ namespace OctoAwesome.Basics.Definitions.Items /// public class ChestItem : Item, IDisposable { + private readonly IUpdateHub updateHub; private readonly Relay simulationRelay; private readonly IDisposable simulationSource; @@ -29,7 +30,7 @@ public class ChestItem : Item, IDisposable public ChestItem(ChestItemDefinition definition, IMaterialDefinition materialDefinition) : base(definition, materialDefinition) { - var updateHub = TypeContainer.Get(); + updateHub = TypeContainer.Get(); simulationRelay = new Relay(); simulationSource = updateHub.AddSource(simulationRelay, DefaultChannels.Simulation); @@ -49,6 +50,7 @@ public override int Apply(IMaterialDefinition material, IBlockInteraction hitInf }; simulationRelay.OnNext(notification); + return 0; } diff --git a/OctoAwesome/OctoAwesome.Basics/Entities/WauziEntity.cs b/OctoAwesome/OctoAwesome.Basics/Entities/WauziEntity.cs index dc1567f8..e94b9b25 100644 --- a/OctoAwesome/OctoAwesome.Basics/Entities/WauziEntity.cs +++ b/OctoAwesome/OctoAwesome.Basics/Entities/WauziEntity.cs @@ -1,5 +1,5 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; + using engenious; using OctoAwesome.Basics.Definitions.Items.Food; @@ -9,14 +9,14 @@ using OctoAwesome.Location; using OctoAwesome.Extension; using OctoAwesome.Serialization; - namespace OctoAwesome.Basics.Entities { /// /// An entity used for dogs in the game. /// - [SerializationId(1, 2)] - public class WauziEntity : Entity + [SerializationId()] + [Nooson] + public partial class WauziEntity : Entity, ISerializable { class MoveLogic { @@ -127,31 +127,32 @@ public WauziEntity() : base() { } - /// - protected override void OnInteract(GameTime gameTime, Entity entity) - { - if (!entity.Components.TryGet(out var toolbar) - || !entity.Components.TryGet(out var inventory) - || !entity.Components.TryGet(out var position) - || toolbar.ActiveTool?.Item is not MeatRaw) - return; - - Controller.JumpInput = true; - if (!Components.Contains()) - { - var relEntity = new RelatedEntityComponent(); - relEntity.RelatedEntityId = entity.Id; - Components.Add(relEntity); - followEntity = entity; - } - - inventory.RemoveUnit(toolbar.ActiveTool); - if (toolbar.ActiveTool.Amount < 1) - { - inventory.Remove(toolbar.ActiveTool); - toolbar.RemoveSlot(toolbar.ActiveTool); - } - } + /// + //protected override void OnInteract(GameTime gameTime, Entity entity) + //{ + // if (!entity.Components.TryGet(out var toolbar) + // || !entity.Components.TryGet(out var inventory) + // || !entity.Components.TryGet(out var position) + // || toolbar.ActiveTool?.Item is not MeatRaw + // || !Components.TryGet(out var controller)) + // return; + + // controller.JumpInput = true; + // if (!Components.Contains()) + // { + // var relEntity = new RelatedEntityComponent(); + // relEntity.RelatedEntityId = entity.Id; + // Components.AddIfTypeNotExists(relEntity); + // followEntity = entity; + // } + + // inventory.RemoveUnit(toolbar.ActiveTool); + // if (toolbar.ActiveTool.Amount < 1) + // { + // inventory.Remove(toolbar.ActiveTool); + // toolbar.RemoveSlot(toolbar.ActiveTool); + // } + //} /// diff --git a/OctoAwesome/OctoAwesome.Basics/EntityComponents/BodyPowerComponent.cs b/OctoAwesome/OctoAwesome.Basics/EntityComponents/BodyPowerComponent.cs index ffc594bd..c95fb820 100644 --- a/OctoAwesome/OctoAwesome.Basics/EntityComponents/BodyPowerComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/EntityComponents/BodyPowerComponent.cs @@ -1,31 +1,17 @@ -using System.IO; - -namespace OctoAwesome.Basics.EntityComponents +namespace OctoAwesome.Basics.EntityComponents { /// /// Component to apply power to the body of an entity. /// - public sealed class BodyPowerComponent : PowerComponent + [Nooson] + [SerializationId()] + public sealed partial class BodyPowerComponent : PowerComponent { /// /// Gets or sets a value indicating the time a jump should take. /// public int JumpTime { get; set; } - /// - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - - writer.Write(JumpTime); - } - - /// - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - - JumpTime = reader.ReadInt32(); - } } } + \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Basics/EntityComponents/BurningComponent.cs b/OctoAwesome/OctoAwesome.Basics/EntityComponents/BurningComponent.cs index adcd3e6e..804596ac 100644 --- a/OctoAwesome/OctoAwesome.Basics/EntityComponents/BurningComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/EntityComponents/BurningComponent.cs @@ -5,21 +5,21 @@ using OctoAwesome.Crafting; using OctoAwesome.Definitions; using OctoAwesome.EntityComponents; -using OctoAwesome; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.IO; using OctoAwesome.Extension; using static OctoAwesome.StateMachine; +using OctoAwesome.Serialization; +using OctoAwesome.Caching; namespace OctoAwesome.Basics.EntityComponents; -internal class ProductionInventoriesComponent : Component, IEntityComponent +[Nooson] +[SerializationId()] +internal partial class ProductionInventoriesComponent : Component, IEntityComponent, IConstructionSerializable { public InventoryComponent InputInventory { get; set; } public InventoryComponent OutputInventory { get; set; } @@ -30,6 +30,7 @@ public ProductionInventoriesComponent() InputInventory = new(); OutputInventory = new(); ProductionInventory = new(); + Sendable = true; } public ProductionInventoriesComponent(bool fixedSlot, int slotCountProduction) : this() @@ -37,26 +38,24 @@ public ProductionInventoriesComponent(bool fixedSlot, int slotCountProduction) : InputInventory = new(); OutputInventory = new(); ProductionInventory = new(fixedSlot, slotCountProduction); + Sendable = true; } - public override void Serialize(BinaryWriter writer) + protected override void OnParentSetting(IComponentContainer newParent) { - base.Serialize(writer); - InputInventory.Serialize(writer); - OutputInventory.Serialize(writer); - ProductionInventory.Serialize(writer); - } - - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - InputInventory.Deserialize(reader); - OutputInventory.Deserialize(reader); - ProductionInventory.Deserialize(reader); + if (newParent.Simulation is null) + return; + InputInventory.Parent = newParent; + OutputInventory.Parent = newParent; + ProductionInventory.Parent = newParent; + newParent.Simulation.GlobalComponentList.Add(InputInventory); + newParent.Simulation.GlobalComponentList.Add(OutputInventory); + newParent.Simulation.GlobalComponentList.Add(ProductionInventory); } } -internal class BurningComponent : InstanceComponent, IEntityComponent, IUpdateable +[SerializationId()] +internal partial class BurningComponent : Component, IEntityComponent, IUpdateable { private StateMachine stateMachine; private RecipeService recipeService; @@ -69,12 +68,13 @@ internal class BurningComponent : InstanceComponent, IEntity private int energyLeft; private IReadOnlyCollection? recipes; + [NoosonIgnore] private IReadOnlyCollection Recipes { get => NullabilityHelper.NotNullAssert(recipes, $"{nameof(Recipes)} was not initialized!"); set => recipes = NullabilityHelper.NotNullAssert(value, $"{nameof(Recipes)} cannot be initialized with null!"); } - + [NoosonIgnore] internal ProductionInventoriesComponent InventoryComponent { get => NullabilityHelper.NotNullAssert(inventoryComponent, $"{nameof(InventoryComponent)} was not initialized!"); @@ -113,7 +113,7 @@ public void Initialize(string typename) recipeService = TypeContainer.Get(); definitionManager = TypeContainer.Get(); recipes = recipeService.GetByType(typename); - var ivComponent = Instance.GetComponent(); + var ivComponent = Parent.GetComponent(); Debug.Assert(ivComponent is not null, $"Entity for Burning component needs to have a not null {nameof(ProductionInventoriesComponent)}."); inventoryComponent = ivComponent; diff --git a/OctoAwesome/OctoAwesome.Basics/EntityComponents/EntityCollisionComponent.cs b/OctoAwesome/OctoAwesome.Basics/EntityComponents/EntityCollisionComponent.cs index d3bb7b70..d1f8753d 100644 --- a/OctoAwesome/OctoAwesome.Basics/EntityComponents/EntityCollisionComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/EntityComponents/EntityCollisionComponent.cs @@ -5,6 +5,7 @@ namespace OctoAwesome.Basics.EntityComponents /// /// Component for entities that should have collision. /// + [SerializationId()] public sealed class EntityCollisionComponent : CollisionComponent { } diff --git a/OctoAwesome/OctoAwesome.Basics/EntityComponents/ForceComponent.cs b/OctoAwesome/OctoAwesome.Basics/EntityComponents/ForceComponent.cs index 273d655c..704bea14 100644 --- a/OctoAwesome/OctoAwesome.Basics/EntityComponents/ForceComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/EntityComponents/ForceComponent.cs @@ -9,6 +9,7 @@ namespace OctoAwesome.Basics.EntityComponents /// /// Base class for forces to be applied to the entity. /// + [SerializationId()] public abstract class ForceComponent : Component, IEntityComponent { /// diff --git a/OctoAwesome/OctoAwesome.Basics/EntityComponents/GravityComponent.cs b/OctoAwesome/OctoAwesome.Basics/EntityComponents/GravityComponent.cs index 2783754e..d849f9a5 100644 --- a/OctoAwesome/OctoAwesome.Basics/EntityComponents/GravityComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/EntityComponents/GravityComponent.cs @@ -3,6 +3,7 @@ /// /// Component for applying gravity force to the entity. /// + [SerializationId()] public sealed class GravityComponent : ForceComponent { } diff --git a/OctoAwesome/OctoAwesome.Basics/EntityComponents/MassComponent.cs b/OctoAwesome/OctoAwesome.Basics/EntityComponents/MassComponent.cs index e6f242cc..86fbd4d8 100644 --- a/OctoAwesome/OctoAwesome.Basics/EntityComponents/MassComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/EntityComponents/MassComponent.cs @@ -7,6 +7,7 @@ namespace OctoAwesome.Basics.EntityComponents /// /// Component or specifying the mass of entities. /// + [SerializationId()] public sealed class MassComponent : Component, IEntityComponent { /// diff --git a/OctoAwesome/OctoAwesome.Basics/EntityComponents/MoveableComponent.cs b/OctoAwesome/OctoAwesome.Basics/EntityComponents/MoveableComponent.cs index eb9a0738..df88ade7 100644 --- a/OctoAwesome/OctoAwesome.Basics/EntityComponents/MoveableComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/EntityComponents/MoveableComponent.cs @@ -9,7 +9,9 @@ namespace OctoAwesome.Basics.EntityComponents /// /// Component for entities that can be moved. /// - public sealed class MoveableComponent : Component, IEntityComponent + [Nooson] + [SerializationId()] + public sealed partial class MoveableComponent : Component, IEntityComponent { /// /// Gets or sets the velocity of the entity. @@ -30,5 +32,6 @@ public sealed class MoveableComponent : Component, IEntityComponent /// Gets or sets the external powers applied to the entity. /// public Vector3 ExternalPowers { get; set; } + } } diff --git a/OctoAwesome/OctoAwesome.Basics/EntityComponents/PowerComponent.cs b/OctoAwesome/OctoAwesome.Basics/EntityComponents/PowerComponent.cs index 7152e678..a1c07839 100644 --- a/OctoAwesome/OctoAwesome.Basics/EntityComponents/PowerComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/EntityComponents/PowerComponent.cs @@ -1,5 +1,5 @@ using engenious; -using System.IO; + using OctoAwesome.Components; namespace OctoAwesome.Basics.EntityComponents @@ -7,7 +7,9 @@ namespace OctoAwesome.Basics.EntityComponents /// /// Base component to apply power to an entity. /// - public abstract class PowerComponent : Component, IEntityComponent + [Nooson] + [SerializationId()] + public abstract partial class PowerComponent : Component, IEntityComponent { /// /// Gets or sets a value indicating the amount of power to apply onto the entity. @@ -19,21 +21,5 @@ public abstract class PowerComponent : Component, IEntityComponent /// public Vector3 Direction { get; set; } - - /// - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - - writer.Write(Power); - } - - /// - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - - Power = reader.ReadSingle(); - } } } diff --git a/OctoAwesome/OctoAwesome.Basics/EntityComponents/RelatedEntityComponent.cs b/OctoAwesome/OctoAwesome.Basics/EntityComponents/RelatedEntityComponent.cs index 552c7aa3..45864901 100644 --- a/OctoAwesome/OctoAwesome.Basics/EntityComponents/RelatedEntityComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/EntityComponents/RelatedEntityComponent.cs @@ -1,23 +1,13 @@ using OctoAwesome.Components; using System; -using System.IO; namespace OctoAwesome.Basics.EntityComponents; -internal class RelatedEntityComponent : Component, IEntityComponent +[Nooson] +[SerializationId()] +internal partial class RelatedEntityComponent : Component, IEntityComponent { public Guid RelatedEntityId { get; set; } - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - writer.Write(RelatedEntityId.ToString()); - } - - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - RelatedEntityId = Guid.Parse(reader.ReadString()); - } } diff --git a/OctoAwesome/OctoAwesome.Basics/EntityComponents/TransferComponent.cs b/OctoAwesome/OctoAwesome.Basics/EntityComponents/TransferComponent.cs index 3381c63a..125ef4a8 100644 --- a/OctoAwesome/OctoAwesome.Basics/EntityComponents/TransferComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/EntityComponents/TransferComponent.cs @@ -1,7 +1,6 @@ using OctoAwesome.Components; using OctoAwesome.EntityComponents; -using System; using System.Collections.Generic; namespace OctoAwesome.Basics.EntityComponents; @@ -9,6 +8,7 @@ namespace OctoAwesome.Basics.EntityComponents; /// /// Used for transfering items from one entity to another /// +[SerializationId()] public class TransferComponent : Component, IEntityComponent { /// diff --git a/OctoAwesome/OctoAwesome.Basics/Extension.cs b/OctoAwesome/OctoAwesome.Basics/Extension.cs index 0fa54259..d48ecdf9 100644 --- a/OctoAwesome/OctoAwesome.Basics/Extension.cs +++ b/OctoAwesome/OctoAwesome.Basics/Extension.cs @@ -11,6 +11,7 @@ using OctoAwesome.Extension; using OctoAwesome.Services; using OctoAwesome.UI.Components; +using OctoAwesome.Rx; using System; using System.Reflection; @@ -40,29 +41,81 @@ private ITypeContainer TypeContainer /// public void Register(ITypeContainer typeContainer) { + typeContainer.Register(); typeContainer.Register(); this.typeContainer = typeContainer; } /// - public void Register(ExtensionService extensionLoader) + public void RegisterTypes(ExtensionService extensionLoader) { - foreach (var t in Assembly.GetExecutingAssembly().GetTypes()) { if (!t.IsAbstract && t.IsPublic && typeof(IDefinition).IsAssignableFrom(t)) extensionLoader.Register(t, ChannelNames.Definitions); } + } + + /// + public void Register(ExtensionService extensionLoader) + { extensionLoader.Register(new ComplexPlanetGenerator()); extensionLoader.Register(new TreePopulator()); extensionLoader.Register(new WauziPopulator(TypeContainer.Get())); - extensionLoader.Register(typeof(WauziEntity), ChannelNames.Serialization); - extensionLoader.Register(typeof(Chest), ChannelNames.Serialization); - extensionLoader.Register(typeof(Furnace), ChannelNames.Serialization); + extensionLoader.RegisterTypesWithSerializationId(typeof(Extension).Assembly); + Extend(extensionLoader); + RegisterInteracts(); + } + private void RegisterInteracts() + { + var interactService = typeContainer.Get(); + interactService.Register(nameof(Chest), (gt, interactor, target) => + { + if (interactor.TryGetComponent(out var transferComponent) + && interactor.TryGetComponent(out var lastUiMappingComponent) + && target.TryGetComponent(out var inventoryComponent) + && target.TryGetComponent(out var uiKeyComp) + && target.TryGetComponent(out var animationComponent)) + { + transferComponent.Targets.Clear(); + transferComponent.Targets.Add(inventoryComponent); + lastUiMappingComponent.Changed.OnNext((interactor, uiKeyComp.PrimaryKey, true)); + lastUiMappingComponent.Changed.SubscribeOnce(UiComponentChanged); + + void UiComponentChanged((ComponentContainer, string, bool show) e) + { + if (e.show) + return; + animationComponent.AnimationSpeed = -60f; + } + + animationComponent.CurrentTime = 0f; + animationComponent.AnimationSpeed = 60f; + } + }); + + interactService.Register(nameof(Furnace), (gt, interactor, target) => + { + if (target.TryGetComponent(out var ownUiKeyComponent) + && interactor.TryGetComponent(out var transferComponent) + && interactor.TryGetComponent(out var uiMappingComponent) + && target.TryGetComponent(out var productionInventoryCompopnents)) + { + transferComponent.Targets.Clear(); + transferComponent.Targets.Add(productionInventoryCompopnents.InputInventory); + transferComponent.Targets.Add(productionInventoryCompopnents.OutputInventory); + transferComponent.Targets.Add(productionInventoryCompopnents.ProductionInventory); + uiMappingComponent.Changed.OnNext((interactor, ownUiKeyComponent.PrimaryKey, true)); + } + }); + } + + private void Extend(ExtensionService extensionLoader) + { extensionLoader.Extend(wauziEntity => wauziEntity.RegisterDefault()); extensionLoader.Extend((player) => @@ -79,6 +132,7 @@ public void Register(ExtensionService extensionLoader) player.Components.AddIfTypeNotExists(new LocalChunkCacheComponent(posComponent.Planet.GlobalChunkCache, 4, 2)); player.Components.AddIfTypeNotExists(new TransferComponent()); player.Components.AddIfTypeNotExists(new UiMappingComponent() { }); + player.Components.AddIfNotExists(new RenderComponent() { Name = "Wauzi", ModelName = "dog", TextureName = "texdog", BaseZRotation = -90 }); }); @@ -110,14 +164,14 @@ public void Register(ExtensionService extensionLoader) c.Components.AddIfTypeNotExists(inventoryComponent); } - - c.Components.AddIfNotExists(new UiKeyComponent("Transfer")); + var uiKeyComp = new UiKeyComponent("Transfer"); + c.Components.AddIfNotExists(uiKeyComp); c.Components.AddIfNotExists(new BodyComponent() { Height = 0.4f, Radius = 0.2f }); c.Components.AddIfNotExists(new BoxCollisionComponent(new[] { new BoundingBox(new Vector3(0, 0), new Vector3(1, 1, 1)) })); c.Components.AddIfNotExists(new RenderComponent() { Name = "Chest", ModelName = "chest", TextureName = "texchestmodel", BaseZRotation = -90 }); c.Components.AddIfTypeNotExists(new UniquePositionComponent()); - + c.Components.AddIfTypeNotExists(new InteractKeyComponent { Key = nameof(Chest) }); }); extensionLoader.Extend((furnace) => @@ -164,6 +218,9 @@ public void Register(ExtensionService extensionLoader) f.Components.AddIfNotExists(new BoxCollisionComponent(new[] { new BoundingBox(new Vector3(0, 0, 0), new Vector3(1, 1, 1)) })); f.Components.AddIfNotExists(new RenderComponent() { Name = "Furnace", ModelName = "furnace", TextureName = "furnacetext" }); f.Components.AddIfTypeNotExists(new UniquePositionComponent()); + f.Components.AddIfTypeNotExists(new InteractKeyComponent { Key = nameof(Furnace) }); + + }); @@ -176,7 +233,7 @@ public void Register(ExtensionService extensionLoader) s.Components.AddIfTypeNotExists(new AccelerationComponent()); s.Components.AddIfTypeNotExists(new MoveComponent()); //TODO: Fix this - s.Components.AddIfTypeNotExists(new BlockInteractionComponent(s, TypeContainer.Get())); + s.Components.AddIfTypeNotExists(new BlockInteractionComponent(s, TypeContainer.Get(), TypeContainer.Get())); //TODO: ugly //TODO: TypeContainer? @@ -189,7 +246,6 @@ public void Register(ExtensionService extensionLoader) s.Components.AddIfTypeNotExists(new FurnaceUIComponent()); s.Add(TypeContainer.GetUnregistered()); }); - } } } diff --git a/OctoAwesome/OctoAwesome.Basics/FunctionBlocks/Chest.cs b/OctoAwesome/OctoAwesome.Basics/FunctionBlocks/Chest.cs index f53298f4..2dd9df87 100644 --- a/OctoAwesome/OctoAwesome.Basics/FunctionBlocks/Chest.cs +++ b/OctoAwesome/OctoAwesome.Basics/FunctionBlocks/Chest.cs @@ -9,16 +9,21 @@ using OctoAwesome.UI.Components; using System; +using OctoAwesome.Extension; using System.IO; + + namespace OctoAwesome.Basics.FunctionBlocks { /// /// Chest entity implementation. /// - [SerializationId(1, 3)] - public class Chest : Entity + [SerializationId()] + [Nooson] + public partial class Chest : Entity, ISerializable { + [NoosonIgnore] internal AnimationComponent AnimationComponent { get => NullabilityHelper.NotNullAssert(animationComponent, $"{nameof(AnimationComponent)} was not initialized!"); @@ -37,12 +42,6 @@ public Chest() } - /// - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - //Doesnt get called - } /// /// Initializes a new instance of the class. @@ -59,32 +58,32 @@ public Chest(Coordinate position, float direction) } - private void UiComponentChanged((ComponentContainer, string, bool show) e) - { - if (e.show) - return; - AnimationComponent.AnimationSpeed = -60f; - changedSub?.Dispose(); + //private void UiComponentChanged((ComponentContainer, string, bool show) e) + //{ + // if (e.show) + // return; + // AnimationComponent.AnimationSpeed = -60f; + // changedSub?.Dispose(); - } + //} - /// - protected override void OnInteract(GameTime gameTime, Entity entity) - { - if (TryGetComponent(out var ownUiKeyComponent) - && entity.TryGetComponent(out var transferComponent) - && entity.TryGetComponent(out var lastUiMappingComponent) - && this.TryGetComponent(out var inventoryComponent)) - { - transferComponent.Targets.Clear(); - transferComponent.Targets.Add(inventoryComponent); - lastUiMappingComponent.Changed.OnNext((entity, ownUiKeyComponent.PrimaryKey, true)); - changedSub?.Dispose(); - changedSub = lastUiMappingComponent.Changed.Subscribe(UiComponentChanged); + ///// + //protected override void OnInteract(GameTime gameTime, Entity entity) + //{ + // if (TryGetComponent(out var ownUiKeyComponent) + // && entity.TryGetComponent(out var transferComponent) + // && entity.TryGetComponent(out var lastUiMappingComponent) + // && this.TryGetComponent(out var inventoryComponent)) + // { + // transferComponent.Targets.Clear(); + // transferComponent.Targets.Add(inventoryComponent); + // lastUiMappingComponent.Changed.OnNext((entity, ownUiKeyComponent.PrimaryKey, true)); + // changedSub?.Dispose(); + // changedSub = lastUiMappingComponent.Changed.Subscribe(UiComponentChanged); - AnimationComponent.CurrentTime = 0f; - AnimationComponent.AnimationSpeed = 60f; - } - } + // AnimationComponent.CurrentTime = 0f; + // AnimationComponent.AnimationSpeed = 60f; + // } + //} } } diff --git a/OctoAwesome/OctoAwesome.Basics/FunctionBlocks/Furnace.cs b/OctoAwesome/OctoAwesome.Basics/FunctionBlocks/Furnace.cs index 6882b4e2..e54408a9 100644 --- a/OctoAwesome/OctoAwesome.Basics/FunctionBlocks/Furnace.cs +++ b/OctoAwesome/OctoAwesome.Basics/FunctionBlocks/Furnace.cs @@ -1,19 +1,10 @@ using engenious; - -using OctoAwesome.Basics.Definitions.Materials; using OctoAwesome.Basics.EntityComponents; -using OctoAwesome.Crafting; -using OctoAwesome.Definitions; using OctoAwesome.EntityComponents; using OctoAwesome.Location; using OctoAwesome; using OctoAwesome.Serialization; using OctoAwesome.UI.Components; - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using OctoAwesome.Extension; @@ -22,8 +13,9 @@ namespace OctoAwesome.Basics.FunctionBlocks; /// /// Represents the furnace object in the world /// -[SerializationId(1, 4)] -public class Furnace : Entity +[SerializationId()] +[Nooson] +public partial class Furnace : Entity, IConstructionSerializable { internal ProductionInventoriesComponent ProductionInventoriesComponent { @@ -35,6 +27,8 @@ internal AnimationComponent AnimationComponent get => NullabilityHelper.NotNullAssert(animationComponent, $"{nameof(AnimationComponent)} was not initialized!"); set => animationComponent = NullabilityHelper.NotNullAssert(value, $"{nameof(AnimationComponent)} cannot be initialized with null!"); } + + [NoosonIgnore] internal BurningComponent BurningComponent { get => NullabilityHelper.NotNullAssert(burningComponent, $"{nameof(BurningComponent)} was not initialized!"); @@ -53,9 +47,9 @@ public Furnace() } /// - protected override void OnInitialize(IResourceManager manager) + protected override void OnInitialize() { - base.OnInitialize(manager); + base.OnInitialize(); GetComponent()?.Initialize("furnace"); } @@ -73,24 +67,5 @@ public Furnace(Coordinate position, float direction) : this() }); } - /// - public override void Deserialize(BinaryReader reader) => base.Deserialize(reader);//Doesnt get called - /// - protected override void OnInteract(GameTime gameTime, Entity entity) - { - if (TryGetComponent(out var ownUiKeyComponent) - && entity.TryGetComponent(out var transferComponent) - && entity.TryGetComponent(out var uiMappingComponent)) - { - transferComponent.Targets.Clear(); - transferComponent.Targets.Add(ProductionInventoriesComponent.InputInventory); - transferComponent.Targets.Add(ProductionInventoriesComponent.OutputInventory); - transferComponent.Targets.Add(ProductionInventoriesComponent.ProductionInventory); - uiMappingComponent.Changed.OnNext((entity, ownUiKeyComponent.PrimaryKey, true)); - - AnimationComponent.CurrentTime = 0f; - AnimationComponent.AnimationSpeed = 60f; - } - } } diff --git a/OctoAwesome/OctoAwesome.Basics/OctoAwesome.Basics.csproj b/OctoAwesome/OctoAwesome.Basics/OctoAwesome.Basics.csproj index 949830c4..f3d1fd93 100644 --- a/OctoAwesome/OctoAwesome.Basics/OctoAwesome.Basics.csproj +++ b/OctoAwesome/OctoAwesome.Basics/OctoAwesome.Basics.csproj @@ -1,24 +1,31 @@  - - net7.0 - enable - True - + + net7.0 + enable + True + - - - - - + + + + + + + - - - + + + + + + + + + + + + - - - - \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/AccelerationComponent.cs b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/AccelerationComponent.cs index 46639059..3c52d79f 100644 --- a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/AccelerationComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/AccelerationComponent.cs @@ -14,6 +14,7 @@ namespace OctoAwesome.Basics.SimulationComponents /// /// Component for simulation with accelerations. /// + [SerializationId()] public sealed class AccelerationComponent : SimulationComponent< Entity, AccelerationComponent.AcceleratedEntity, @@ -60,6 +61,18 @@ protected override void UpdateValue(GameTime gameTime, AcceleratedEntity entity) // Calculate Move Vector for the upcoming frame entity.Move.PositionMove = entity.Move.Velocity * (float)gameTime.ElapsedGameTime.TotalSeconds; + + // Fix fluctuations for direction because of external forces + var tmp = entity.Move.PositionMove; + if (Math.Abs(tmp.X) < 0.02) + { + tmp.X = 0; + } + if (Math.Abs(tmp.Y) < 0.02) + { + tmp.Y = 0; + } + entity.Move.PositionMove = tmp; } /// diff --git a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/BlockInteractionComponent.cs b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/BlockInteractionComponent.cs index 8705fc54..fde9b145 100644 --- a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/BlockInteractionComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/BlockInteractionComponent.cs @@ -14,6 +14,7 @@ namespace OctoAwesome.Basics.SimulationComponents /// /// Component for simulation with block interactions. /// + [SerializationId()] public class BlockInteractionComponent : SimulationComponent< Entity, SimulationComponentRecord, @@ -22,19 +23,21 @@ public class BlockInteractionComponent : SimulationComponent< { private readonly Simulation simulation; private readonly BlockInteractionService service; + private readonly InteractService interactService; /// /// Initializes a new instance of the class. /// /// The simulation the block interactions should happen in. - /// + /// /// The interaction service to actually interact with blocks in the simulation. /// - public BlockInteractionComponent(Simulation simulation, BlockInteractionService interactionService) + public BlockInteractionComponent(Simulation simulation, BlockInteractionService blockInteractionService, InteractService interactService) { this.simulation = simulation; - service = interactionService; + service = blockInteractionService; + this.interactService = interactService; } /// @@ -61,7 +64,13 @@ protected override void UpdateValue(GameTime gameTime, SimulationComponentRecord Debug.Assert(toolbar != null, nameof(toolbar) + " != null"); ApplyWith(applyInfo, inventory, toolbar, cache); }, - componentContainer => componentContainer?.Interact(gameTime, entity) + componentContainer => + { + if (componentContainer.TryGetComponent(out var keyComp)) + { + interactService.Interact(keyComp.Key, gameTime, entity, componentContainer); + } + } ); if (toolbar != null && controller.ApplyBlock.HasValue) diff --git a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/CollisionComponent.cs b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/CollisionComponent.cs index 8fe4b9cc..51422105 100644 --- a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/CollisionComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/CollisionComponent.cs @@ -6,6 +6,7 @@ namespace OctoAwesome.Basics.SimulationComponents /// /// Component for simulation with collisions of entities. /// + [SerializationId()] public sealed class CollisionComponent : SimulationComponent { /// diff --git a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/ComponentContainerInteractionComponent.cs b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/ComponentContainerInteractionComponent.cs index e42c6305..0ece8daf 100644 --- a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/ComponentContainerInteractionComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/ComponentContainerInteractionComponent.cs @@ -9,6 +9,7 @@ namespace OctoAwesome.Basics.SimulationComponents /// /// Component for simulation with block interactions with entities. /// + [SerializationId()] public class ComponentContainerInteractionComponent : SimulationComponent< Entity, SimulationComponentRecord, diff --git a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/ForceAggregatorComponent.cs b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/ForceAggregatorComponent.cs index 90bcf2f6..3f2cd476 100644 --- a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/ForceAggregatorComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/ForceAggregatorComponent.cs @@ -9,6 +9,7 @@ namespace OctoAwesome.Basics.SimulationComponents /// /// Component for simulation with force applied to entities. /// + [SerializationId()] public sealed class ForceAggregatorComponent : SimulationComponent< Entity, ForceAggregatorComponent.ForcedEntity, diff --git a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/MoveComponent.cs b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/MoveComponent.cs index 563a6e0d..04fae57b 100644 --- a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/MoveComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/MoveComponent.cs @@ -5,6 +5,11 @@ using OctoAwesome.EntityComponents; using engenious.Helper; using OctoAwesome.Components; +using OctoAwesome.Serialization; +using OctoAwesome.Notifications; +using OctoAwesome.Pooling; +using System.Linq; +using NonSucking.Framework.Extension.IoC; using OctoAwesome.Location; namespace OctoAwesome.Basics.SimulationComponents @@ -12,12 +17,26 @@ namespace OctoAwesome.Basics.SimulationComponents /// /// Component for simulation with moveable entities. /// + [SerializationId()] public sealed class MoveComponent : SimulationComponent< Entity, SimulationComponentRecord, MoveableComponent, PositionComponent> { + private readonly IPool entityNotificationPool; + private readonly IUpdateHub updateHub; + private readonly IPool propertyChangedNotificationPool; + + public MoveComponent() + { + var tc = TypeContainer.Get(); + entityNotificationPool = tc.Get>(); + updateHub = tc.Get(); + + propertyChangedNotificationPool = tc.Get>(); + } + /// protected override SimulationComponentRecord OnAdd(Entity entity) { @@ -53,13 +72,24 @@ protected override void UpdateValue(GameTime gameTime, SimulationComponentRecord { CheckBoxCollision(gameTime, entity, movecomp, poscomp); } + var tmp = movecomp.PositionMove; + if (Math.Abs(tmp.X) < 0.02) + { + tmp.X = 0; + } + if (Math.Abs(tmp.Y) < 0.02) + { + tmp.Y = 0; + } + + var newposition = poscomp.Position + tmp; + bool send = newposition != poscomp.Position; var cacheComp = entity.Components.Get(); Debug.Assert(cacheComp != null, nameof(cacheComp) + " != null"); var cache = cacheComp.LocalChunkCache; - var newposition = poscomp.Position + movecomp.PositionMove; newposition.NormalizeChunkIndexXY(cache.Planet.Size); if (poscomp.Position.ChunkIndex != newposition.ChunkIndex) { @@ -72,26 +102,16 @@ protected override void UpdateValue(GameTime gameTime, SimulationComponentRecord poscomp.Position = newposition; } - // Fix fluctuations for direction because of external forces - var tmp = movecomp.PositionMove; - if (Math.Abs(tmp.X) < 0.01) - { - tmp.X = 0; - } - if (Math.Abs(tmp.Y) < 0.01) - { - tmp.Y = 0; - } - movecomp.PositionMove = tmp; - //Direction - if (movecomp.PositionMove.LengthSquared != 0) + if (tmp.LengthSquared != 0) { poscomp.Direction = MathHelper.WrapAngle((float)Math.Atan2(movecomp.PositionMove.Y, movecomp.PositionMove.X)); } + if (send) + poscomp.IncrementVersion(); } - private void CheckBoxCollision(GameTime gameTime, Entity entity, MoveableComponent movecomp, PositionComponent poscomp) + private static void CheckBoxCollision(GameTime gameTime, Entity entity, MoveableComponent movecomp, PositionComponent poscomp) { if (!entity.Components.TryGet(out var bc) || !entity.Components.TryGet(out var localChunkCacheComponent)) @@ -104,22 +124,22 @@ private void CheckBoxCollision(GameTime gameTime, Entity entity, MoveableCompone // Find blocks which could cause a collision int minx = (int)Math.Floor(Math.Min( position.BlockPosition.X - bc.Radius, - position.BlockPosition.X - bc.Radius + movecomp.PositionMove.X)); + position.BlockPosition.X - bc.Radius + move.X)); int maxx = (int)Math.Ceiling(Math.Max( position.BlockPosition.X + bc.Radius, - position.BlockPosition.X + bc.Radius + movecomp.PositionMove.X)); + position.BlockPosition.X + bc.Radius + move.X)); int miny = (int)Math.Floor(Math.Min( position.BlockPosition.Y - bc.Radius, - position.BlockPosition.Y - bc.Radius + movecomp.PositionMove.Y)); + position.BlockPosition.Y - bc.Radius + move.Y)); int maxy = (int)Math.Ceiling(Math.Max( position.BlockPosition.Y + bc.Radius, - position.BlockPosition.Y + bc.Radius + movecomp.PositionMove.Y)); + position.BlockPosition.Y + bc.Radius + move.Y)); int minz = (int)Math.Floor(Math.Min( position.BlockPosition.Z, - position.BlockPosition.Z + movecomp.PositionMove.Z)); + position.BlockPosition.Z + move.Z)); int maxz = (int)Math.Ceiling(Math.Max( position.BlockPosition.Z + bc.Height, - position.BlockPosition.Z + bc.Height + movecomp.PositionMove.Z)); + position.BlockPosition.Z + bc.Height + move.Z)); // The relevant collision planes of the player var playerplanes = CollisionPlane.GetEntityCollisionPlanes(bc.Radius, bc.Height, movecomp.Velocity, poscomp.Position); diff --git a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/NewtonGravitatorComponent.cs b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/NewtonGravitatorComponent.cs index 4997e9d5..f4806e8a 100644 --- a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/NewtonGravitatorComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/NewtonGravitatorComponent.cs @@ -13,6 +13,7 @@ namespace OctoAwesome.Basics.SimulationComponents /// /// Component for simulation with newtonian gravity. /// + [SerializationId()] public class NewtonGravitatorComponent : SimulationComponent< Entity, NewtonGravitatorComponent.GravityEntity, diff --git a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/PowerAggregatorComponent.cs b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/PowerAggregatorComponent.cs index 0c0d95a5..a5e84087 100644 --- a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/PowerAggregatorComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/PowerAggregatorComponent.cs @@ -9,6 +9,7 @@ namespace OctoAwesome.Basics.SimulationComponents /// /// Component for simulation aggregated power application to entities. /// + [SerializationId()] public sealed class PowerAggregatorComponent : SimulationComponent< Entity, PowerAggregatorComponent.PoweredEntity, diff --git a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/WattMoverComponent.cs b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/WattMoverComponent.cs index e61c6e7c..9a87da66 100644 --- a/OctoAwesome/OctoAwesome.Basics/SimulationComponents/WattMoverComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/SimulationComponents/WattMoverComponent.cs @@ -11,6 +11,7 @@ namespace OctoAwesome.Basics.SimulationComponents /// /// Component for simulation with watt energy system. /// + [SerializationId()] public class WattMoverComponent : SimulationComponent< Entity, SimulationComponentRecord, diff --git a/OctoAwesome/OctoAwesome.Basics/UI/Components/FurnaceUIComponent.cs b/OctoAwesome/OctoAwesome.Basics/UI/Components/FurnaceUIComponent.cs index a172a2b1..714bab3c 100644 --- a/OctoAwesome/OctoAwesome.Basics/UI/Components/FurnaceUIComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/UI/Components/FurnaceUIComponent.cs @@ -11,6 +11,7 @@ namespace OctoAwesome.Basics.UI.Components; /// /// Component to provide an UI for transferring items from and to a furnace from a different inventory. /// +[SerializationId()] public class FurnaceUIComponent : UIComponent, InventoryComponent, TransferComponent> { /// @@ -26,7 +27,7 @@ public InventoryComponent InventoryA /// Gets a value indicating the version of the value, /// which is changed when has changed. /// - public int VersionA { get; private set; } + public uint VersionA { get; private set; } /// /// Gets the input inventory of the furnace. @@ -41,7 +42,7 @@ public InventoryComponent InputInventory /// Gets a value indicating the version of the value, /// which is changed when has changed. /// - public int InputVersion { get; private set; } + public uint InputVersion { get; private set; } /// /// Gets the output inventory of the furnace. @@ -56,7 +57,7 @@ public InventoryComponent OutputInventory /// Gets a value indicating the version of the value, /// which is changed when has changed. /// - public int OutputVersion { get; private set; } + public uint OutputVersion { get; private set; } /// /// Gets the production inventory of the furnace(e.g. fuel). @@ -71,7 +72,7 @@ public InventoryComponent ProductionResourceInventory /// Gets a value indicating the version of the value, /// which is changed when has changed. /// - public int ProductionResourceVersion { get; private set; } + public uint ProductionResourceVersion { get; private set; } private bool show = false; private InventoryComponent? inventoryA, inputInventory, outputInventory, productionResourceInventory; @@ -80,10 +81,10 @@ protected override bool TryUpdate(ComponentContainer value, InventoryComponent c { if (show == Show && (component2.Targets.Count == 0 - || ((inventoryA?.Version ?? -1) == VersionA - && (inputInventory?.Version ?? -1) == InputVersion - && (productionResourceInventory?.Version ?? -1) == ProductionResourceVersion - && (outputInventory?.Version ?? -1) == OutputVersion) + || ((inventoryA?.Version ?? 0) == VersionA + && (inputInventory?.Version ?? 0) == InputVersion + && (productionResourceInventory?.Version ?? 0) == ProductionResourceVersion + && (outputInventory?.Version ?? 0) == OutputVersion) ) || PrimaryUiKey != "Furnace") diff --git a/OctoAwesome/OctoAwesome.Basics/UI/Components/TransferUIComponent.cs b/OctoAwesome/OctoAwesome.Basics/UI/Components/TransferUIComponent.cs index f4a7389c..d68b16a1 100644 --- a/OctoAwesome/OctoAwesome.Basics/UI/Components/TransferUIComponent.cs +++ b/OctoAwesome/OctoAwesome.Basics/UI/Components/TransferUIComponent.cs @@ -11,6 +11,7 @@ namespace OctoAwesome.Basics.UI.Components; /// /// Component to provide an UI for transferring items from one inventory to another. /// +[SerializationId()] public class TransferUIComponent : UIComponent, InventoryComponent, TransferComponent> { /// @@ -26,7 +27,7 @@ public InventoryComponent InventoryA /// Gets a value indicating the version of the value, /// which is changed when has changed. /// - public int VersionA { get; private set; } + public uint VersionA { get; private set; } /// /// Gets the second inventory to transfer to and from. @@ -41,7 +42,7 @@ public InventoryComponent InventoryB /// Gets a value indicating the version of the value, /// which is changed when has changed. /// - public int VersionB { get; private set; } + public uint VersionB { get; private set; } private bool show = false; private InventoryComponent? inventoryA; @@ -52,8 +53,8 @@ protected override bool TryUpdate(ComponentContainer value, InventoryComponent c { if (show == Show && (component2.Targets.Count == 0 - || ((inventoryA?.Version ?? -1) == VersionA - && (inventoryB?.Version ?? -1) == VersionB)) + || ((inventoryA?.Version ?? 0) == VersionA + && (inventoryB?.Version ?? 0) == VersionB)) || PrimaryUiKey != "Transfer") { diff --git a/OctoAwesome/OctoAwesome.Basics/UI/Controls/FurnaceControl.cs b/OctoAwesome/OctoAwesome.Basics/UI/Controls/FurnaceControl.cs index dfa9894a..08f2dca6 100644 --- a/OctoAwesome/OctoAwesome.Basics/UI/Controls/FurnaceControl.cs +++ b/OctoAwesome/OctoAwesome.Basics/UI/Controls/FurnaceControl.cs @@ -5,11 +5,8 @@ using OctoAwesome.Client.UI.Components; using OctoAwesome.Client.UI.Controls; -using OctoAwesome.EntityComponents; -using OctoAwesome.UI.Components; using System.Collections.Generic; -using System.Text; namespace OctoAwesome.Basics.UI.Controls; diff --git a/OctoAwesome/OctoAwesome.Basics/UI/Screens/TransferScreen.cs b/OctoAwesome/OctoAwesome.Basics/UI/Screens/TransferScreen.cs index 5572459c..1ea4a14f 100644 --- a/OctoAwesome/OctoAwesome.Basics/UI/Screens/TransferScreen.cs +++ b/OctoAwesome/OctoAwesome.Basics/UI/Screens/TransferScreen.cs @@ -51,7 +51,6 @@ private enum TransferDirection BToA } - //TODO Where and how to initialize this screen? /// /// Initializes a new instance of the class. /// diff --git a/OctoAwesome/OctoAwesome.Basics/WauziPopulator.cs b/OctoAwesome/OctoAwesome.Basics/WauziPopulator.cs index dec9e6d4..ff073d6f 100644 --- a/OctoAwesome/OctoAwesome.Basics/WauziPopulator.cs +++ b/OctoAwesome/OctoAwesome.Basics/WauziPopulator.cs @@ -42,9 +42,6 @@ public WauziPopulator(IResourceManager resManager) /// public void Populate(IResourceManager resourcemanager, IPlanet planet, IChunkColumn column00, IChunkColumn column01, IChunkColumn column10, IChunkColumn column11) { - //HACK: Activate Wauzi - //return; - if (ispop-- <= 0) return; @@ -71,6 +68,7 @@ protected virtual void Dispose(bool disposing) if (disposing) { simulationSubscription.Dispose(); + simulationRelay.Dispose(); } disposedValue = true; diff --git a/OctoAwesome/OctoAwesome.Client.UI/AssemblyInfo.cs b/OctoAwesome/OctoAwesome.Client.UI/AssemblyInfo.cs new file mode 100644 index 00000000..bee28c60 --- /dev/null +++ b/OctoAwesome/OctoAwesome.Client.UI/AssemblyInfo.cs @@ -0,0 +1,23 @@ +using OctoAwesome; +using OctoAwesome.UI.Components; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +[assembly: NoosonConfiguration( + GenerateDeserializeExtension = false, + DisableWarnings = true, + GenerateStaticDeserializeWithCtor = true, + GenerateDeserializeOnInstance = true, + GenerateStaticSerialize = true, + GenerateStaticDeserializeIntoInstance = true, + NameOfStaticDeserializeWithCtor = "DeserializeAndCreate", + NameOfDeserializeOnInstance = "Deserialize", + NameOfStaticDeserializeIntoInstance = "Deserialize", + NameOfStaticDeserializeWithOutParams = "DeserializeOut")] + +[assembly: SerializationId(3, 1)] +[assembly: SerializationId(3, 2)] \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Client.UI/Components/UIComponent.cs b/OctoAwesome/OctoAwesome.Client.UI/Components/UIComponent.cs index cfc6147e..e77fc475 100644 --- a/OctoAwesome/OctoAwesome.Client.UI/Components/UIComponent.cs +++ b/OctoAwesome/OctoAwesome.Client.UI/Components/UIComponent.cs @@ -14,6 +14,9 @@ namespace OctoAwesome.UI.Components /// public abstract class UIComponent : Component, IHoldComponent { + static int NextId => nextId--; + static int nextId = -2; + /// /// Gets or sets if this component should be shown /// @@ -46,6 +49,7 @@ public abstract class UIComponent : Component, IHoldComponent(); + Id = NextId; } /// diff --git a/OctoAwesome/OctoAwesome.Client.UI/Components/UiKeyComponent.cs b/OctoAwesome/OctoAwesome.Client.UI/Components/UiKeyComponent.cs index 8aa73ad7..35cf2796 100644 --- a/OctoAwesome/OctoAwesome.Client.UI/Components/UiKeyComponent.cs +++ b/OctoAwesome/OctoAwesome.Client.UI/Components/UiKeyComponent.cs @@ -1,4 +1,6 @@ -using OctoAwesome.Components; +using NonSucking.Framework.Serialization; + +using OctoAwesome.Components; using System; using System.Collections.Generic; @@ -8,18 +10,26 @@ namespace OctoAwesome.UI.Components; /// /// Component to identify which UIComponent to use for an entity interaction. /// -public class UiKeyComponent : Component, IEntityComponent, IEquatable +[Nooson] +[SerializationId()] +public partial class UiKeyComponent : Component, IEntityComponent, IEquatable { /// /// Gets the primary key. /// - public string PrimaryKey { get; } + public string PrimaryKey { get; private set; } + + public UiKeyComponent() : base() + { + Sendable = true; + PrimaryKey = ""; + } /// /// Initializes a new instance of the class. /// /// The primary key. - public UiKeyComponent(string primaryKey) + public UiKeyComponent(string primaryKey) : this() { PrimaryKey = primaryKey; } diff --git a/OctoAwesome/OctoAwesome.Client.UI/Components/UiMappingComponent.cs b/OctoAwesome/OctoAwesome.Client.UI/Components/UiMappingComponent.cs index 698778ee..f0d92bf0 100644 --- a/OctoAwesome/OctoAwesome.Client.UI/Components/UiMappingComponent.cs +++ b/OctoAwesome/OctoAwesome.Client.UI/Components/UiMappingComponent.cs @@ -1,12 +1,15 @@ using OctoAwesome.Components; using OctoAwesome.Rx; +using System; + namespace OctoAwesome.UI.Components; /// /// Class for mapping a to an /// -public class UiMappingComponent : Component, IEntityComponent +[SerializationId()] +public class UiMappingComponent : Component, IEntityComponent, IDisposable { /// /// Gets the relay that can be subscribed for receiving changed events @@ -20,4 +23,10 @@ public UiMappingComponent() { Changed = new Relay<(ComponentContainer instance, string primaryKey, bool show)>(); } + + /// + public void Dispose() + { + Changed?.Dispose(); + } } \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Client.UI/Extension.cs b/OctoAwesome/OctoAwesome.Client.UI/Extension.cs new file mode 100644 index 00000000..79ef76dc --- /dev/null +++ b/OctoAwesome/OctoAwesome.Client.UI/Extension.cs @@ -0,0 +1,43 @@ +using engenious; + +using OctoAwesome.EntityComponents; +using OctoAwesome.Extension; +using OctoAwesome.Serialization; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OctoAwesome.Client.UI +{ + /// + /// The base extension implementation. + /// + internal sealed class ClientUiExtension : IExtension + { + /// + public string Description => "OctoAwesome Client Ui"; + + /// + public string Name => "OctoAwesome.Client.Ui"; + + /// + public void Register(OctoAwesome.Extension.ExtensionService extensionLoader) + { + + } + + /// + public void Register(ITypeContainer typeContainer) + { + } + + /// + public void RegisterTypes(ExtensionService extensionLoader) + { + extensionLoader.RegisterTypesWithSerializationId(typeof(ClientUiExtension).Assembly); + } + } +} diff --git a/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoClient.Designer.cs b/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoClient.Designer.cs index a921bbac..b79e30d1 100644 --- a/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoClient.Designer.cs +++ b/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoClient.Designer.cs @@ -375,6 +375,15 @@ public static string Name { } } + /// + /// Looks up a localized string similar to Open to LAN. + /// + public static string OpenToNetwork { + get { + return ResourceManager.GetString("OpenToNetwork", resourceCulture); + } + } + /// /// Looks up a localized string similar to Options. /// diff --git a/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoClient.de.resx b/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoClient.de.resx index 64f0c532..b61147eb 100644 --- a/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoClient.de.resx +++ b/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoClient.de.resx @@ -291,4 +291,7 @@ Anvisierter Block + + Im LAN öffnen + \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoClient.resx b/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoClient.resx index de865bb0..a4fbfe7c 100644 --- a/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoClient.resx +++ b/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoClient.resx @@ -293,4 +293,7 @@ Targeted Block + + Open to LAN + \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoKeys.Designer.cs b/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoKeys.Designer.cs index 5d12a468..544376b8 100644 --- a/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoKeys.Designer.cs +++ b/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoKeys.Designer.cs @@ -243,6 +243,18 @@ public static string teleport { } } + /// + /// Looks up a localized string similar to Toggle Chat. + /// + public static string toggle_chat { + get { + return ResourceManager.GetString("toggle_chat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggle Ambient Occlusion. + /// public static string toggleAmbientOcclusion { get { return ResourceManager.GetString("toggleAmbientOcclusion", resourceCulture); diff --git a/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoKeys.resx b/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoKeys.resx index 5f1fc3ab..5b02ddb1 100644 --- a/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoKeys.resx +++ b/OctoAwesome/OctoAwesome.Client.UI/Languages/OctoKeys.resx @@ -222,4 +222,7 @@ Toggle Wireframe + + Toggle Chat + \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Client.UI/OctoAwesome.Client.UI.csproj b/OctoAwesome/OctoAwesome.Client.UI/OctoAwesome.Client.UI.csproj index e157a675..3d83a62a 100644 --- a/OctoAwesome/OctoAwesome.Client.UI/OctoAwesome.Client.UI.csproj +++ b/OctoAwesome/OctoAwesome.Client.UI/OctoAwesome.Client.UI.csproj @@ -13,11 +13,16 @@ + + + + + diff --git a/OctoAwesome/OctoAwesome.Client/Cache/ChunkRendererDbContext.cs b/OctoAwesome/OctoAwesome.Client/Cache/ChunkRendererDbContext.cs deleted file mode 100644 index 3931b867..00000000 --- a/OctoAwesome/OctoAwesome.Client/Cache/ChunkRendererDbContext.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using OctoAwesome.Database; -using OctoAwesome.Location; -using OctoAwesome.Serialization; -using System.IO; - -namespace OctoAwesome.Client.Cache -{ - internal class ChunkRendererDbContext : DatabaseContext - { - public ChunkRendererDbContext(Database database) : base(database) - { - } - - public override void AddOrUpdate(VerticesForChunk? value) - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - - using (Database.Lock(Operation.Write)) - Database.AddOrUpdate(new Index3Tag(value.ChunkPosition), new Value(Serializer.Serialize(value))); - } - - public VerticesForChunk? Get(Index3 key) - => Get(new Index3Tag(key)); - - public override VerticesForChunk? Get(Index3Tag key) - { - if (!Database.ContainsKey(key)) - return null; - - var verticesForChunk = new VerticesForChunk(); - using var stream = new MemoryStream(Database.GetValue(key).Content); - using var buffered = new BufferedStream(stream); - using var reader = new BinaryReader(buffered); - - verticesForChunk.Deserialize(reader); - return verticesForChunk; - } - - public override void Remove(VerticesForChunk? value) - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - - using (Database.Lock(Operation.Write)) - Database.Remove(new Index3Tag(value.ChunkPosition)); - } - } -} diff --git a/OctoAwesome/OctoAwesome.Client/Cache/VerticesForChunk.cs b/OctoAwesome/OctoAwesome.Client/Cache/VerticesForChunk.cs index e27a4706..c5a17d72 100644 --- a/OctoAwesome/OctoAwesome.Client/Cache/VerticesForChunk.cs +++ b/OctoAwesome/OctoAwesome.Client/Cache/VerticesForChunk.cs @@ -8,6 +8,7 @@ namespace OctoAwesome.Client.Cache { + internal class VerticesForChunk : IDisposable, ISerializable { public int Version { get; set; } diff --git a/OctoAwesome/OctoAwesome.Client/Components/CameraComponent.cs b/OctoAwesome/OctoAwesome.Client/Components/CameraComponent.cs index d36530c6..3e78de91 100644 --- a/OctoAwesome/OctoAwesome.Client/Components/CameraComponent.cs +++ b/OctoAwesome/OctoAwesome.Client/Components/CameraComponent.cs @@ -46,7 +46,7 @@ public override void Update(GameTime gameTime) if (!player.Enabled) return; - Entity entity = player.CurrentEntity; + Entity entity = player.CurrentPlayer; HeadComponent head = player.CurrentEntityHead; PositionComponent position = player.Position; diff --git a/OctoAwesome/OctoAwesome.Client/Components/PlayerComponent.cs b/OctoAwesome/OctoAwesome.Client/Components/PlayerComponent.cs index f8573186..ee031f5d 100644 --- a/OctoAwesome/OctoAwesome.Client/Components/PlayerComponent.cs +++ b/OctoAwesome/OctoAwesome.Client/Components/PlayerComponent.cs @@ -44,10 +44,10 @@ internal sealed class PlayerComponent : GameComponent private InventoryComponent? inventory; private ToolBarComponent? toolbar; private PositionComponent? position; - private Entity? currentEntity; + private Player? currentPlayer; - public Entity CurrentEntity - => NullabilityHelper.NotNullAssert(currentEntity, $"{nameof(CurrentEntity)} was not initialized!"); + public Player CurrentPlayer + => NullabilityHelper.NotNullAssert(currentPlayer, $"{nameof(CurrentPlayer)} was not initialized!"); public HeadComponent CurrentEntityHead => NullabilityHelper.NotNullAssert(currentEntityHead, $"{nameof(CurrentEntityHead)} was not initialized!"); @@ -86,18 +86,18 @@ public PlayerComponent(OctoGame game, IResourceManager resourceManager) public void Unload() { Enabled = false; - currentEntity = null; + currentPlayer = null; currentEntityHead = null; Selection = null; SelectedBox = null; SelectedPoint = null; } - public void Load(Entity entity) + public void Load(Player entity) { // Map other Components - currentEntity = entity; + currentPlayer = entity; var controlComp = entity.Components.Get(); @@ -194,7 +194,7 @@ public override void Update(GameTime gameTime) /// internal void AllBlocksDebug() { - var inventory = CurrentEntity.Components.Get(); + var inventory = CurrentPlayer.Components.Get(); if (inventory == null) return; @@ -206,7 +206,7 @@ internal void AllBlocksDebug() internal void AllFoodsDebug() { - var inventory = CurrentEntity.Components.Get(); + var inventory = CurrentPlayer.Components.Get(); if (inventory == null) return; @@ -225,7 +225,7 @@ internal void AllFoodsDebug() internal void AllItemsDebug() { - var inventory = CurrentEntity.Components.Get(); + var inventory = CurrentPlayer.Components.Get(); if (inventory == null) return; diff --git a/OctoAwesome/OctoAwesome.Client/Components/ScreenComponent.cs b/OctoAwesome/OctoAwesome.Client/Components/ScreenComponent.cs index 67b2cc84..b90505ed 100644 --- a/OctoAwesome/OctoAwesome.Client/Components/ScreenComponent.cs +++ b/OctoAwesome/OctoAwesome.Client/Components/ScreenComponent.cs @@ -19,10 +19,8 @@ namespace OctoAwesome.Client.Components { - internal sealed class ScreenComponent : BaseScreenComponent, IAssetRelatedComponent, IScreenComponent + internal sealed class ScreenComponent : BaseScreenComponent, IAssetRelatedComponent, IScreenComponent, IComponentContainer { - private readonly ExtensionService extensionService; - private readonly IDisposable componentSubscription; public new OctoGame Game { get; private set; } @@ -31,15 +29,20 @@ internal sealed class ScreenComponent : BaseScreenComponent, IAssetRelatedCompon public CameraComponent Camera => Game.Camera; public ComponentList Components { get; private set; } + public Guid Id { get; } + public Simulation? Simulation { get; } + + private readonly ExtensionService extensionService; + private readonly IDisposable componentSubscription; private readonly List screens; public ScreenComponent(OctoGame game, ExtensionService extensionService) : base(game) { Game = game; TitlePrefix = "OctoAwesome"; - + Id = Guid.NewGuid(); screens = new(); - Components = new ComponentList(null, null, Add, Remove); + Components = new ComponentList(null, null, Add, Remove, this); this.extensionService = extensionService; @@ -47,7 +50,7 @@ public ScreenComponent(OctoGame game, ExtensionService extensionService) : base( = game .ResourceManager .UpdateHub - .ListenOn(DefaultChannels.UI) + .ListenOn(DefaultChannels.Simulation) .Subscribe(OnNext); } @@ -65,7 +68,7 @@ protected override void LoadContent() NavigateFromTransition = new AlphaTransition(Frame, Transition.Linear, TimeSpan.FromMilliseconds(200), 0f); NavigateToTransition = new AlphaTransition(Frame, Transition.Linear, TimeSpan.FromMilliseconds(200), 1f); - this.extensionService.ExecuteExtender(this); + extensionService.ExecuteExtender(this); NavigateToScreen(new MainScreen(Game.Assets)); @@ -118,7 +121,7 @@ public void ReloadAssets() } - private void OnNext(Notification notification) + private void OnNext(object notification) { switch (notification) { @@ -193,5 +196,14 @@ private void RemoveEntity(Guid entityId) { //TODO: Remove component container? } + + public bool ContainsComponent() + => Components.Contains(); + + public T? GetComponent() + => Components.Get(); + + public T? GetComponent(int id) + => Components.Get(id); } } diff --git a/OctoAwesome/OctoAwesome.Client/Components/SimulationComponent.cs b/OctoAwesome/OctoAwesome.Client/Components/SimulationComponent.cs index 0db3d45c..019ee3f8 100644 --- a/OctoAwesome/OctoAwesome.Client/Components/SimulationComponent.cs +++ b/OctoAwesome/OctoAwesome.Client/Components/SimulationComponent.cs @@ -1,7 +1,6 @@ using System; using engenious; using OctoAwesome.EntityComponents; -using OctoAwesome.Common; using OctoAwesome.Extension; using OctoAwesome.Notifications; using OctoAwesome.Rx; @@ -22,13 +21,11 @@ public Simulation Simulation get => NullabilityHelper.NotNullAssert(simulation, $"{nameof(Simulation)} was not initialized!"); } - public IGameService Service { get; } public SimulationState State => simulation?.State ?? SimulationState.Undefined; public SimulationComponent(OctoGame game, ExtensionService extensionService, IResourceManager resourceManager) : base(game) { - Service = game.Service; this.extensionService = extensionService; this.resourceManager = resourceManager; simulationRelay = new Relay(); @@ -46,10 +43,7 @@ private void ExitSimulation() public Guid NewGame(string name, string seed) { ExitSimulation(); - - simulation = new Simulation(resourceManager, extensionService, Service); - var newGame = Simulation.NewGame(name, seed); - Enabled = true; + var newGame = Simulation.NewGame(resourceManager, name, seed); return newGame; } @@ -57,7 +51,7 @@ public void LoadGame(Guid guid) { ExitSimulation(); - simulation = new Simulation(resourceManager, extensionService, Service); + simulation = new Simulation(resourceManager, extensionService); if (Simulation.TryLoadGame(guid)) Enabled = true; } @@ -79,7 +73,6 @@ public Player LoginPlayer(string playerName) Player player = resourceManager.LoadPlayer(playerName); - player.Components.AddIfNotExists(new RenderComponent() { Name = "Wauzi", ModelName = "dog", TextureName = "texdog", BaseZRotation = -90 }); simulationRelay.OnNext(new EntityNotification(EntityNotification.ActionType.Add, player) { OverwriteExisting = true }); diff --git a/OctoAwesome/OctoAwesome.Client/ContainerResourceManager.cs b/OctoAwesome/OctoAwesome.Client/ContainerResourceManager.cs deleted file mode 100644 index d7c3c8cd..00000000 --- a/OctoAwesome/OctoAwesome.Client/ContainerResourceManager.cs +++ /dev/null @@ -1,236 +0,0 @@ - -using OctoAwesome.Chunking; -using OctoAwesome.Components; -using OctoAwesome.Definitions; -using OctoAwesome.EntityComponents; -using OctoAwesome.Extension; -using OctoAwesome.Location; -using OctoAwesome.Network; -using OctoAwesome.Notifications; -using OctoAwesome.Runtime; - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net; - -namespace OctoAwesome.Client -{ - /// - /// This is only temporary - /// - public class ContainerResourceManager : IResourceManager, IDisposable - { - /// - public IDefinitionManager DefinitionManager => ResourceManager.DefinitionManager; - - /// - public IUniverse? CurrentUniverse => ResourceManager.CurrentUniverse; - - /// - /// Gets a value indicating whether the resource manager is in multiplayer mode. - /// - public bool IsMultiplayer { get; private set; } - - /// - public Player CurrentPlayer => ResourceManager.CurrentPlayer; - - /// - public IUpdateHub UpdateHub { get; } - - /// - public ConcurrentDictionary Planets - { - get - { - Debug.Assert(ResourceManager != null, nameof(ResourceManager) + " != null"); - return ResourceManager.Planets; - } - } - - private readonly OctoAwesome.Extension.ExtensionService extensionService; - private ResourceManager ResourceManager - { - get - { - Debug.Assert(resourceManager != null, nameof(resourceManager) + " != null"); - return resourceManager; - } - } - - private readonly IDefinitionManager definitionManager; - private readonly ISettings settings; - private readonly ITypeContainer typeContainer; - - private ResourceManager? resourceManager; - private NetworkUpdateManager? networkUpdateManager; - - /// - /// Initializes a new instance of the class. - /// - /// The type container to manage types. - /// The update hub to use for update notifications. - /// The extension service. - /// The manager for definitions. - /// The application settings. - public ContainerResourceManager(ITypeContainer typeContainer, IUpdateHub updateHub, OctoAwesome.Extension.ExtensionService extensionService, IDefinitionManager definitionManager, ISettings settings) - { - UpdateHub = updateHub; - this.typeContainer = typeContainer; - this.extensionService = extensionService; - this.definitionManager = definitionManager; - this.settings = settings; - } - - /// - /// Create the wrapped manager dependant on whether it is a multiplayer game or not. - /// - /// Whether a multiplayer resource manager should be initialized or not. - /// - /// Creates for single player; - /// otherwise . - /// - public void CreateManager(bool multiplayer) - { - IPersistenceManager persistenceManager; - - if (resourceManager != null) - { - if (resourceManager.CurrentUniverse != null) - resourceManager.UnloadUniverse(); - - if (resourceManager is IDisposable disposable) - disposable.Dispose(); - - resourceManager = null; - } - - - if (multiplayer) - { - var rawIpAddress = settings.Get("server").Trim(); - string host; - int port = -1; - if (rawIpAddress[0] == '[' || !IPAddress.TryParse(rawIpAddress, out var iPAddress)) //IPV4 || IPV6 without port - { - string stringIpAddress; - if (rawIpAddress[0] == '[') // IPV6 with Port - { - port = int.Parse(rawIpAddress.Split(':').Last()); - stringIpAddress = rawIpAddress[1..rawIpAddress.IndexOf(']')]; - } - else if (rawIpAddress.Contains(':') && - IPAddress.TryParse(rawIpAddress.Substring(0, rawIpAddress.IndexOf(':')), out iPAddress)) //IPV4 with Port - { - port = int.Parse(rawIpAddress.Split(':').Last()); - stringIpAddress = iPAddress.ToString(); - } - else if (rawIpAddress.Contains(':')) //Domain with Port - { - port = int.Parse(rawIpAddress.Split(':').Last()); - stringIpAddress = rawIpAddress.Split(':').First(); - } - else //Domain without Port - { - stringIpAddress = rawIpAddress; - } - host = stringIpAddress; - } - else - { - host = rawIpAddress; - } - - var client = new Network.Client(); - client.Connect(host, port > 0 ? (ushort)port : (ushort)8888); - persistenceManager = new NetworkPersistenceManager(typeContainer, client); - networkUpdateManager = new NetworkUpdateManager(client, UpdateHub); - } - else - { - persistenceManager = new DiskPersistenceManager(extensionService, settings, UpdateHub); - } - - resourceManager = new ResourceManager(extensionService, definitionManager, settings, persistenceManager, UpdateHub); - - - IsMultiplayer = multiplayer; - - //if (multiplayer) - //{ - // resourceManager.GlobalChunkCache.ChunkColumnChanged += (s, c) => - // { - // var networkPersistence = (NetworkPersistenceManager)persistenceManager; - // networkPersistence.SendChangedChunkColumn(c); - // }; - //} - - - } - - /// - public void DeleteUniverse(Guid id) => ResourceManager.DeleteUniverse(id); - - /// - public IPlanet GetPlanet(int planetId) => ResourceManager.GetPlanet(planetId); - - - /// - public IUniverse[] ListUniverses() => ResourceManager.ListUniverses(); - - /// - public Player LoadPlayer(string playerName) => ResourceManager.LoadPlayer(playerName); - - /// - public bool TryLoadUniverse(Guid universeId) => ResourceManager.TryLoadUniverse(universeId); - - /// - public Guid NewUniverse(string name, int seed) => ResourceManager.NewUniverse(name, seed); - - /// - public void SaveComponentContainer(TContainer container) - where TContainer : ComponentContainer - where TComponent : IComponent - => ResourceManager.SaveComponentContainer(container); - - - /// - public void SavePlayer(Player player) => ResourceManager.SavePlayer(player); - - /// - public void UnloadUniverse() => ResourceManager.UnloadUniverse(); - - /// - public void SaveChunkColumn(IChunkColumn chunkColumn) => ResourceManager.SaveChunkColumn(chunkColumn); - - /// - public IChunkColumn? LoadChunkColumn(IPlanet planet, Index2 index) => ResourceManager.LoadChunkColumn(planet, index); - - /// - public void Dispose() - { - if (resourceManager is IDisposable disposable) - disposable.Dispose(); - } - - /// - public Entity? LoadEntity(Guid entityId) - => ResourceManager.LoadEntity(entityId); - - /// - public TContainer? LoadComponentContainer(Guid id) - where TContainer : ComponentContainer - where TComponent : IComponent - => ResourceManager.LoadComponentContainer(id); - - /// - public (Guid Id, T Component)[] GetAllComponents() where T : IComponent, new() - => ResourceManager.GetAllComponents(); - - /// - public T GetComponent(Guid id) where T : IComponent, new() - => ResourceManager.GetComponent(id); - } -} diff --git a/OctoAwesome/OctoAwesome.Client/Content/Fonts/BoldFont.spritefont b/OctoAwesome/OctoAwesome.Client/Content/Fonts/BoldFont.spritefont index 065bfcc7..78921c86 100644 --- a/OctoAwesome/OctoAwesome.Client/Content/Fonts/BoldFont.spritefont +++ b/OctoAwesome/OctoAwesome.Client/Content/Fonts/BoldFont.spritefont @@ -1,6 +1,6 @@  - Cascadia Code + Cascadia Mono BitmapFont 12 0 @@ -44,13 +44,13 @@ 223 223 - - 127462 - 127487 - - - 128293 - 128293 - + + 127462 + 127487 + + + 128293 + 128293 + \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Client/Content/Fonts/GameFont.spritefont b/OctoAwesome/OctoAwesome.Client/Content/Fonts/GameFont.spritefont index 11826bb2..d1b8f202 100644 --- a/OctoAwesome/OctoAwesome.Client/Content/Fonts/GameFont.spritefont +++ b/OctoAwesome/OctoAwesome.Client/Content/Fonts/GameFont.spritefont @@ -1,57 +1,56 @@  - Cascadia Code - BitmapFont - 12 - 0 - True - - - - - 9 - 9 - - - 32 - 126 - - - 228 - 228 - - - 246 - 246 - - - 252 - 252 - - - 196 - 196 - - - 214 - 214 - - - 220 - 220 - - - 223 - 223 - - - 127462 - 127487 - - - 128293 - 128293 - - - + Cascadia Mono + BitmapFont + 12 + 0 + True + + + + + 9 + 9 + + + 32 + 126 + + + 228 + 228 + + + 246 + 246 + + + 252 + 252 + + + 196 + 196 + + + 214 + 214 + + + 220 + 220 + + + 223 + 223 + + + 127462 + 127487 + + + 128293 + 128293 + + \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Client/Content/Fonts/HeadlineFont.spritefont b/OctoAwesome/OctoAwesome.Client/Content/Fonts/HeadlineFont.spritefont index 17052b55..d25d8a3d 100644 --- a/OctoAwesome/OctoAwesome.Client/Content/Fonts/HeadlineFont.spritefont +++ b/OctoAwesome/OctoAwesome.Client/Content/Fonts/HeadlineFont.spritefont @@ -1,56 +1,56 @@  - Cascadia Code - BitmapFont - 16 - 0 - True - - - - - 9 - 9 - - - 32 - 126 - - - 228 - 228 - - - 246 - 246 - - - 252 - 252 - - - 196 - 196 - - - 214 - 214 - - - 220 - 220 - - - 223 - 223 - - - 127462 - 127487 - - - 128293 - 128293 - - + Cascadia Mono + BitmapFont + 16 + 0 + True + + + + + 9 + 9 + + + 32 + 126 + + + 228 + 228 + + + 246 + 246 + + + 252 + 252 + + + 196 + 196 + + + 214 + 214 + + + 220 + 220 + + + 223 + 223 + + + 127462 + 127487 + + + 128293 + 128293 + + \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Client/Content/Fonts/Hud.spritefont b/OctoAwesome/OctoAwesome.Client/Content/Fonts/Hud.spritefont index 1110ed90..d1b8f202 100644 --- a/OctoAwesome/OctoAwesome.Client/Content/Fonts/Hud.spritefont +++ b/OctoAwesome/OctoAwesome.Client/Content/Fonts/Hud.spritefont @@ -1,56 +1,56 @@  - Cascadia Code - BitmapFont - 12 - 0 - True - - - - - 9 - 9 - - - 32 - 126 - - - 228 - 228 - - - 246 - 246 - - - 252 - 252 - - - 196 - 196 - - - 214 - 214 - - - 220 - 220 - - - 223 - 223 - - - 127462 - 127487 - - - 128293 - 128293 - - + Cascadia Mono + BitmapFont + 12 + 0 + True + + + + + 9 + 9 + + + 32 + 126 + + + 228 + 228 + + + 246 + 246 + + + 252 + 252 + + + 196 + 196 + + + 214 + 214 + + + 220 + 220 + + + 223 + 223 + + + 127462 + 127487 + + + 128293 + 128293 + + \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Client/Controls/ChatControl.cs b/OctoAwesome/OctoAwesome.Client/Controls/ChatControl.cs new file mode 100644 index 00000000..7a233fd5 --- /dev/null +++ b/OctoAwesome/OctoAwesome.Client/Controls/ChatControl.cs @@ -0,0 +1,195 @@ +using engenious; +using engenious.Input; +using engenious.UI; +using engenious.UI.Controls; +using OctoAwesome.Notifications; +using OctoAwesome.Pooling; +using OctoAwesome.Rx; +using System; + +namespace OctoAwesome.Client.Controls; + + +internal class ChatControl : ContainerControl, IDisposable +{ + private readonly Textbox textBox; + private readonly StackPanel textStack; + private readonly StackPanel autoCompletionsStack; + private readonly ScrollContainer textScroll; + private readonly ScrollContainer autoCompletionsScroll; + private readonly ITypeContainer typeContainer; + private readonly IUpdateHub updateHub; + private readonly Pool chatMessagePool; + private readonly IDisposable messageSubscription; + private bool shouldScrollDown; + + private GameTime currentGameTime; + private GameTime lastReceived; + + public ChatControl() + { + typeContainer = TypeContainer.Get(); + updateHub = typeContainer.Get(); + chatMessagePool = typeContainer.Get>(); + + messageSubscription = updateHub.ListenOn(DefaultChannels.Chat).Subscribe(ChatMessageReceived); + + textBox = new Textbox() + { + HorizontalAlignment = HorizontalAlignment.Stretch, + Background = new BorderBrush(Color.Black * 0.7f), + TextColor = Color.White, + Visible = false + }; + + textBox.KeyDown += TextBox_KeyDown; + + textStack = new StackPanel + { + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalAlignment = VerticalAlignment.Bottom + }; + + textScroll = new ScrollContainer + { + VerticalAlignment = VerticalAlignment.Stretch, + HorizontalAlignment = HorizontalAlignment.Stretch + }; + textScroll.Content = textStack; + autoCompletionsStack = new StackPanel + { + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalAlignment = VerticalAlignment.Bottom + }; + autoCompletionsScroll = new ScrollContainer + { + VerticalAlignment = VerticalAlignment.Stretch, + HorizontalAlignment = HorizontalAlignment.Stretch + }; + autoCompletionsScroll.Content = autoCompletionsStack; + autoCompletionsScroll.Visible = false; + var mainGrid = new Grid + { + VerticalAlignment = VerticalAlignment.Stretch, + HorizontalAlignment = HorizontalAlignment.Stretch, + Background = new BorderBrush(Color.Gray * 0.7f) + }; + + mainGrid.Columns.Add(new ColumnDefinition() { ResizeMode = ResizeMode.Parts, Width = 1 }); + mainGrid.Rows.Add(new RowDefinition() { ResizeMode = ResizeMode.FitParts, Height = 1 }); + mainGrid.Rows.Add(new RowDefinition() { ResizeMode = ResizeMode.Auto }); + + mainGrid.AddControl(textScroll, 0, 0); + mainGrid.AddControl(autoCompletionsScroll, 0, 0); + mainGrid.AddControl(textBox, 0, 1); + Controls.Add(mainGrid); + + HorizontalAlignment = HorizontalAlignment.Left; + VerticalAlignment = VerticalAlignment.Bottom; + Height = 200; + Width = 500; + Visible = false; + } + + private void ChatMessageReceived(object obj) + { + if (obj is not ChatNotification notification) + return; + + AddTextMessage($"[{notification.TimeStamp:HH:mm:ss}] {notification.Username}: {notification.Text}"); + } + + public void AddTextMessage(string message) + { + lastReceived = currentGameTime; + Visible = true; + + var label = new Label + { + Text = message, + HorizontalAlignment = HorizontalAlignment.Left, + TextColor = Color.White, + LineWrap = true, + WordWrap = true, + + }; + textStack.Controls.Add(label); + shouldScrollDown = true; + } + + public void Activate() + { + Visible = true; + + textBox.Visible = true; + ScreenManager.FreeMouse(); + textBox.Focus(); + shouldScrollDown = true; + } + + protected override void OnUpdate(GameTime gameTime) + { + currentGameTime = gameTime; + + base.OnUpdate(gameTime); + if (shouldScrollDown) + { + shouldScrollDown = false; + textScroll.VerticalScrollPosition = int.MaxValue; + } + + if (Visible && textBox.Focused != TreeState.Active && lastReceived.TotalGameTime.Add(TimeSpan.FromSeconds(5)) <= gameTime.TotalGameTime) + { + Visible = false; + } + } + private void TextBox_KeyDown(Control sender, KeyEventArgs args) + { + if ((args.Key == Keys.Enter || args.Key == Keys.KeypadEnter) && Visible && !string.IsNullOrWhiteSpace(textBox.Text)) + { + var notification = chatMessagePool.Rent(); + notification.Text = textBox.Text; + notification.Username = ((OctoGame)ScreenManager.Game).Player.CurrentPlayer.Name; + notification.TimeStamp = DateTimeOffset.Now; + + updateHub.PushNetwork(notification, DefaultChannels.Chat); + chatMessagePool.Return(notification); + textBox.Text = ""; + } + } + + protected override void OnVisibleChanged(PropertyEventArgs args) + { + if (args.NewValue) + { + ScreenManager.FreeMouse(); + textBox.Focus(); + } + else + { + ScreenManager.CaptureMouse(); + textBox.Unfocus(); + } + base.OnVisibleChanged(args); + } + + protected override void OnKeyDown(KeyEventArgs args) + { + if (Visible && textBox.Visible) + { + args.Handled = true; + if (args.Key == Keys.Escape) + { + ScreenManager.CaptureMouse(); + textBox.Unfocus(); + textBox.Visible = false; + } + } + base.OnKeyDown(args); + } + + public void Dispose() + { + messageSubscription.Dispose(); + } +} diff --git a/OctoAwesome/OctoAwesome.Client/Controls/DebugControl.cs b/OctoAwesome/OctoAwesome.Client/Controls/DebugControl.cs index 4f253ff8..502740fb 100644 --- a/OctoAwesome/OctoAwesome.Client/Controls/DebugControl.cs +++ b/OctoAwesome/OctoAwesome.Client/Controls/DebugControl.cs @@ -154,7 +154,7 @@ protected override void OnDrawContent(SpriteBatch batch, Rectangle contentArea, if (!Visible || !Enabled || !assets.Ready) return; - if (Player.CurrentEntity == null || Player.CurrentEntityHead == null) + if (Player.CurrentPlayer == null || Player.CurrentEntityHead == null) return; //Calculate FPS diff --git a/OctoAwesome/OctoAwesome.Client/Controls/SceneControl.cs b/OctoAwesome/OctoAwesome.Client/Controls/SceneControl.cs index e99764f4..05c8cc50 100644 --- a/OctoAwesome/OctoAwesome.Client/Controls/SceneControl.cs +++ b/OctoAwesome/OctoAwesome.Client/Controls/SceneControl.cs @@ -309,7 +309,7 @@ public override void OnResolutionChanged() protected override void OnUpdate(GameTime gameTime) { - if (disposed || player?.CurrentEntity == null) + if (disposed || player?.CurrentPlayer == null) return; sunPosition += (float)gameTime.ElapsedGameTime.TotalMinutes * MathHelper.TwoPi; diff --git a/OctoAwesome/OctoAwesome.Client/GameService.cs b/OctoAwesome/OctoAwesome.Client/GameService.cs new file mode 100644 index 00000000..f9247650 --- /dev/null +++ b/OctoAwesome/OctoAwesome.Client/GameService.cs @@ -0,0 +1,153 @@ + +using engenious; + +using OctoAwesome.Components; +using OctoAwesome.Definitions; +using OctoAwesome.EntityComponents; +using OctoAwesome.Extension; +using OctoAwesome.Network; +using OctoAwesome.Notifications; +using OctoAwesome.Runtime; + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; + +namespace OctoAwesome.Client +{ + /// + /// This is only temporary + /// + internal class GameService : IDisposable + { + /// + public IUpdateHub UpdateHub { get; } + + private readonly ExtensionService extensionService; + + private readonly ISettings settings; + private readonly OctoGame game; + private readonly ITypeContainer typeContainer; + + internal ResourceManager ResourceManager { get; private set; } + private NetworkPackageManager? networkPackageManager; + + /// + /// Initializes a new instance of the class. + /// + /// The octo game to hold all the current game properties. + /// The type container to manage types. + /// The update hub to use for update notifications. + /// The extension service. + /// The manager for definitions. + /// The application settings. + public GameService(OctoGame game, ITypeContainer typeContainer, IUpdateHub updateHub, ExtensionService extensionService, IDefinitionManager definitionManager, ISettings settings) + { + UpdateHub = updateHub; + this.game = game; + this.typeContainer = typeContainer; + this.extensionService = extensionService; + this.settings = settings; + ResourceManager = new ResourceManager(extensionService, definitionManager, settings, UpdateHub); + } + + + /// + /// Create the wrapped manager dependant on whether it is a multiplayer game or not. + /// + /// Whether a resource manager should be initialized or not. + /// + /// Creates for single player; + /// otherwise . + /// + public void StartMultiplayer(string playerName, string rawIpAddress) + { + settings.Set("player", playerName); + settings.Set("server", rawIpAddress); + + string host; + int port = -1; + + if (rawIpAddress[0] == '[' || !IPAddress.TryParse(rawIpAddress, out _)) //IPV4 || IPV6 without port + { + string stringIpAddress; + + IPAddress? iPAddress; + if (rawIpAddress[0] == '[') // IPV6 with Port + { + port = int.Parse(rawIpAddress.Split(':').Last()); + stringIpAddress = rawIpAddress[1..rawIpAddress.IndexOf(']')]; + } + else if (rawIpAddress.Contains(':') && + IPAddress.TryParse(rawIpAddress.AsSpan(0, rawIpAddress.IndexOf(':')), out iPAddress)) //IPV4 with Port + { + port = int.Parse(rawIpAddress.Split(':').Last()); + stringIpAddress = iPAddress.ToString(); + } + else if (rawIpAddress.Contains(':')) //Domain with Port + { + port = int.Parse(rawIpAddress.Split(':').Last()); + stringIpAddress = rawIpAddress.Split(':').First(); + } + else //Domain without Port + { + stringIpAddress = rawIpAddress; + } + host = stringIpAddress; + } + else + { + host = rawIpAddress; + } + + var client = new Network.Client(host, port > 0 ? (ushort)port : (ushort)8888); + networkPackageManager = new NetworkPackageManager(client, UpdateHub, typeContainer); + var persistenceManager = new NetworkPersistenceManager(typeContainer, networkPackageManager); + + StartGame(persistenceManager, new NetworkIdManager(networkPackageManager), Guid.Empty, playerName); + + } + + public void StartSinglePlayer(Guid gameId) + { + settings.Set("LastUniverse", gameId.ToString()); + var persistenceManager = new DiskPersistenceManager(extensionService, settings, UpdateHub); + + StartGame(persistenceManager, new LocalIdManager(), gameId, ""); + } + + public void StartGame(IPersistenceManager persistenceManager, IIdManager idManager, Guid gameId, string playerName) + { + if (ResourceManager.CurrentUniverse != null) + ResourceManager.UnloadUniverse(); + + game.Player.Unload(); + ResourceManager.PersistenceManager = persistenceManager; + ResourceManager.IdManager = idManager; + game.Simulation.LoadGame(gameId); + var player = game.Simulation.LoginPlayer(playerName); + player.Name = playerName; + game.Player.Load(player); + + } + + public void ExitGame() + { + game.Player.Unload(); + game.Simulation.ExitGame(); + networkPackageManager?.Dispose(); + + } + + /// + public void Dispose() + { + if (ResourceManager is IDisposable disposable) + disposable.Dispose(); + } + + } +} diff --git a/OctoAwesome/OctoAwesome.Client/OctoAwesome.Client.csproj b/OctoAwesome/OctoAwesome.Client/OctoAwesome.Client.csproj index 78ddbf29..cf8be2e7 100644 --- a/OctoAwesome/OctoAwesome.Client/OctoAwesome.Client.csproj +++ b/OctoAwesome/OctoAwesome.Client/OctoAwesome.Client.csproj @@ -5,15 +5,11 @@ net7.0 enable true - 11 octoawesome.ico true True - true - $(MSBuildThisFileDirectory)\Generated - - + @@ -68,8 +64,9 @@ - - + + + diff --git a/OctoAwesome/OctoAwesome.Client/OctoGame.cs b/OctoAwesome/OctoAwesome.Client/OctoGame.cs index cb312617..b419760a 100644 --- a/OctoAwesome/OctoAwesome.Client/OctoGame.cs +++ b/OctoAwesome/OctoAwesome.Client/OctoGame.cs @@ -1,25 +1,21 @@ -using engenious.UI; -using engenious; -using engenious.Input; +using engenious; using engenious.Graphics; - -using OctoAwesome.Notifications; -using OctoAwesome.Common; -using OctoAwesome.Runtime; -using OctoAwesome.Definitions; +using engenious.Input; +using engenious.UI; using OctoAwesome.Client.Components; +using OctoAwesome.Client.Controls; using OctoAwesome.Client.UI.Components; -using OctoAwesome.UI.Components; -using OctoAwesome.Extension; using OctoAwesome.Crafting; -using OctoAwesome.Client.Controls; - +using OctoAwesome.Definitions; +using OctoAwesome.Extension; +using OctoAwesome.Notifications; +using OctoAwesome.Runtime; using System; using System.Collections.Generic; using System.Diagnostics; - using EventArgs = System.EventArgs; + namespace OctoAwesome.Client { /// @@ -31,7 +27,7 @@ internal class OctoGame : Game public const int DefaultResolutionHeight = 1050; public const int DefaultViewRange = 4; - + private readonly ITypeContainer typeContainer; //GraphicsDeviceManager graphics; @@ -42,8 +38,6 @@ internal class OctoGame : Game public SimulationComponent Simulation { get; } - public GameService Service { get; } - public ScreenComponent Screen { get; } public KeyMapper KeyMapper { get; } @@ -54,13 +48,15 @@ internal class OctoGame : Game public IDefinitionManager DefinitionManager { get; } - public IResourceManager ResourceManager { get; } + public IResourceManager ResourceManager => GameService.ResourceManager; - public ExtensionService ExtensionService { get; private set; } + public ExtensionService ExtensionService { get; } public EntityGameComponent Entity { get; private set; } public ExtensionLoader ExtensionLoader { get; } + public GameService GameService { get; } + /// /// Initializes a new instance of the class /// @@ -70,17 +66,22 @@ public OctoGame() : base() IsMouseVisible = true; typeContainer = TypeContainer.Get(); - + typeContainer.Register(this); Register(typeContainer); ExtensionLoader = typeContainer.Get(); ExtensionLoader.LoadExtensions(); + ExtensionLoader.RegisterExtensions(); + + + var gs = typeContainer.Get(); + typeContainer.Register(gs.ResourceManager); ExtensionService = typeContainer.Get(); DefinitionManager = typeContainer.Get(); - ResourceManager = typeContainer.Get(); + GameService = typeContainer.Get(); Settings = typeContainer.Get(); @@ -92,6 +93,7 @@ public OctoGame() : base() typeContainer.Register(Screen); typeContainer.Register(Screen); + ExtensionLoader.InstantiateExtensions(); typeContainer.Register(Assets); @@ -100,7 +102,6 @@ public OctoGame() : base() typeContainer.Get().Load("Recipes"); - Service = typeContainer.Get(); //TargetElapsedTime = new TimeSpan(0, 0, 0, 0, 15); int width = Settings.Get("Width", DefaultResolutionWidth); @@ -125,7 +126,7 @@ public OctoGame() : base() #region GameComponents - + Player = new PlayerComponent(this, ResourceManager); Player.UpdateOrder = 2; @@ -176,20 +177,18 @@ public override void LoadContent() private static void Register(ITypeContainer typeContainer) { - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + } private void SetKeyBindings() @@ -224,6 +223,7 @@ private void SetKeyBindings() KeyMapper.RegisterBinding("octoawesome:freemouse", UI.Languages.OctoKeys.freemouse); KeyMapper.RegisterBinding("octoawesome:fullscreen", UI.Languages.OctoKeys.fullscreen); KeyMapper.RegisterBinding("octoawesome:teleport", UI.Languages.OctoKeys.teleport); + KeyMapper.RegisterBinding("octoawesome:toggle_chat", UI.Languages.OctoKeys.toggle_chat); KeyMapper.RegisterBinding("octoawesome:toggleAmbientOcclusion", UI.Languages.OctoKeys.toggleAmbientOcclusion); KeyMapper.RegisterBinding("octoawesome:toggleWireFrame", UI.Languages.OctoKeys.toggleWireFrame); KeyMapper.RegisterBinding("octoawesome:toggleCamera", "Toggle Camera"); @@ -266,6 +266,7 @@ private void SetKeyBindings() { "octoawesome:freemouse", Keys.F12 }, { "octoawesome:fullscreen", Keys.F11 }, { "octoawesome:teleport", Keys.T }, + { "octoawesome:toggle_chat", Keys.Enter }, { "octoawesome:toggleAmbientOcclusion", Keys.O }, { "octoawesome:toggleWireFrame", Keys.J } }; @@ -286,5 +287,7 @@ protected override void OnExiting(EventArgs args) Player.Unload(); Simulation.ExitGame(); } + + } } diff --git a/OctoAwesome/OctoAwesome.Client/Program.cs b/OctoAwesome/OctoAwesome.Client/Program.cs index 1f8174e4..05ad7047 100644 --- a/OctoAwesome/OctoAwesome.Client/Program.cs +++ b/OctoAwesome/OctoAwesome.Client/Program.cs @@ -1,5 +1,7 @@ #region Using Statements +using engenious; + using OctoAwesome.Caching; using OctoAwesome.Logging; @@ -46,6 +48,9 @@ static void Main() using (game = new OctoGame()) game.Run(60, 60); + typeContainer.Remove(); + typeContainer.Remove(); + typeContainer.Remove(); } } diff --git a/OctoAwesome/OctoAwesome.Client/Screens/ConnectionScreen.cs b/OctoAwesome/OctoAwesome.Client/Screens/ConnectionScreen.cs index 8076a5f1..39c32a1d 100644 --- a/OctoAwesome/OctoAwesome.Client/Screens/ConnectionScreen.cs +++ b/OctoAwesome/OctoAwesome.Client/Screens/ConnectionScreen.cs @@ -68,31 +68,14 @@ public ConnectionScreen(AssetComponent assets) createButton.Visible = true; createButton.LeftMouseClick += (s, e) => { - game.Settings.Set("server", serverNameInput.Text); - game.Settings.Set("player", playerNameInput.Text); + game.GameService.StartMultiplayer(playerNameInput.Text, serverNameInput.Text); - ((ContainerResourceManager)game.ResourceManager) - .CreateManager(true); - - PlayMultiplayer(ScreenManager, playerNameInput.Text); + ScreenManager.NavigateToScreen(new LoadingScreen(Assets)); }; - grid.Rows.Add(new RowDefinition() { ResizeMode = ResizeMode.Auto, }); + grid.Rows.Add(new RowDefinition { ResizeMode = ResizeMode.Auto, }); grid.AddControl(createButton, 1, grid.Rows.Count - 1); } - - private void PlayMultiplayer(ScreenComponent manager, string playerName) - { - ScreenManager.Player.Unload(); - - ScreenManager.Game.Simulation.LoadGame(Guid.Empty); - //settings.Set("LastUniverse", levelList.SelectedItem.Id.ToString()); - - Player player = ScreenManager.Game.Simulation.LoginPlayer(playerName); - ScreenManager.Game.Player.Load(player); - - ScreenManager.NavigateToScreen(new GameScreen(Assets)); - } } } diff --git a/OctoAwesome/OctoAwesome.Client/Screens/CreateUniverseScreen.cs b/OctoAwesome/OctoAwesome.Client/Screens/CreateUniverseScreen.cs index dc11f394..6c38b386 100644 --- a/OctoAwesome/OctoAwesome.Client/Screens/CreateUniverseScreen.cs +++ b/OctoAwesome/OctoAwesome.Client/Screens/CreateUniverseScreen.cs @@ -87,11 +87,8 @@ private void Create() ScreenManager.Player.Unload(); - Guid guid = ScreenManager.Game.Simulation.NewGame(nameInput.Text, seedInput.Text); - settings.Set("LastUniverse", guid.ToString()); - - Player player = ScreenManager.Game.Simulation.LoginPlayer(""); - ScreenManager.Game.Player.Load(player); + Guid guid = ScreenManager.Game.Simulation.NewGame(nameInput.Text, seedInput.Text); + ScreenManager.Game.GameService.StartSinglePlayer(guid); ScreenManager.NavigateToScreen(new LoadingScreen(Assets)); } diff --git a/OctoAwesome/OctoAwesome.Client/Screens/GameScreen.cs b/OctoAwesome/OctoAwesome.Client/Screens/GameScreen.cs index 603e9d6a..bcd06ae2 100644 --- a/OctoAwesome/OctoAwesome.Client/Screens/GameScreen.cs +++ b/OctoAwesome/OctoAwesome.Client/Screens/GameScreen.cs @@ -3,13 +3,12 @@ using engenious.UI; using engenious.UI.Controls; -using OctoAwesome.Definitions; using OctoAwesome.Client.Components; -using OctoAwesome.Client.UI.Controls; -using OctoAwesome.Client.UI.Components; using OctoAwesome.Client.Controls; -using OctoAwesome.Location; +using OctoAwesome.Client.UI.Components; +using OctoAwesome.Client.UI.Controls; +using OctoAwesome.Definitions; using System; using System.Collections.Generic; using System.Runtime.Loader; @@ -32,6 +31,7 @@ public event EventHandler OnCenterChanged private readonly ToolbarControl toolbar; private readonly MinimapControl minimap; private readonly CrosshairControl crosshair; + private readonly ChatControl chat; private readonly HealthBarControl healthbar; private readonly PlayerComponent playerComponent; private readonly IDefinitionManager definitionManager; @@ -92,6 +92,18 @@ public GameScreen(AssetComponent assets) : base(assets) crosshair.VerticalAlignment = VerticalAlignment.Center; Controls.Add(crosshair); + chat = new ChatControl() + { + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Bottom, + Height = 400, + MaxHeight = 400, + Width = 500, + Visible = false, + Margin = Border.All(20, 0, 0, 80), + }; + Controls.Add(chat); + Title = UI.Languages.OctoClient.Game; RegisterKeyActions(); @@ -124,6 +136,7 @@ protected override void OnUpdate(GameTime gameTime) public void Unload() { scene.Dispose(); + chat.Dispose(); } #region Mouse Input @@ -388,18 +401,25 @@ private void RegisterKeyActions() }); ScreenManager.Game.KeyMapper.AddAction("octoawesome:toggleWireFrame", type => { - if (!IsActiveScreen || type != KeyMapper.KeyType.Up) + if (!IsActiveScreen || type != KeyMapper.KeyType.Down) return; ChunkRenderer.WireFrame = !ChunkRenderer.WireFrame; }); ScreenManager.Game.KeyMapper.AddAction("octoawesome:toggleAmbientOcclusion", type => { - if (!IsActiveScreen || type != KeyMapper.KeyType.Up) + if (!IsActiveScreen || type != KeyMapper.KeyType.Down) return; ChunkRenderer.OverrideLightLevel = ChunkRenderer.OverrideLightLevel > 0f ? 0f : 1f; }); + ScreenManager.Game.KeyMapper.AddAction("octoawesome:toggle_chat", type => + { + if (!IsActiveScreen || type != KeyMapper.KeyType.Down) + return; + + chat.Activate(); + }); List viewCreators = new(); foreach (var item in AssemblyLoadContext.Default.Assemblies) diff --git a/OctoAwesome/OctoAwesome.Client/Screens/LoadScreen.cs b/OctoAwesome/OctoAwesome.Client/Screens/LoadScreen.cs index 4e39d663..355769b2 100644 --- a/OctoAwesome/OctoAwesome.Client/Screens/LoadScreen.cs +++ b/OctoAwesome/OctoAwesome.Client/Screens/LoadScreen.cs @@ -177,14 +177,8 @@ protected override void OnKeyDown(KeyEventArgs args) private void Play() { Debug.Assert(levelList.SelectedItem != null, "levelList.SelectedItem != null"); - ScreenManager.Player.Unload(); - - ScreenManager.Game.Simulation.LoadGame(levelList.SelectedItem.Id); - settings.Set("LastUniverse", levelList.SelectedItem.Id.ToString()); - - Player player = ScreenManager.Game.Simulation.LoginPlayer(""); - ScreenManager.Game.Player.Load(player); + ScreenManager.Game.GameService.StartSinglePlayer(levelList.SelectedItem.Id); ScreenManager.NavigateToScreen(new LoadingScreen(Assets)); } } diff --git a/OctoAwesome/OctoAwesome.Client/Screens/LoadingScreen.cs b/OctoAwesome/OctoAwesome.Client/Screens/LoadingScreen.cs index f1c823e6..d94afca6 100644 --- a/OctoAwesome/OctoAwesome.Client/Screens/LoadingScreen.cs +++ b/OctoAwesome/OctoAwesome.Client/Screens/LoadingScreen.cs @@ -109,8 +109,7 @@ public LoadingScreen(AssetComponent assets) { tokenSource.Cancel(); tokenSource.Dispose(); - ScreenManager.Player.Unload(); - ScreenManager.Game.Simulation.ExitGame(); + ScreenManager.Game.GameService.ExitGame(); gameScreen.Unload(); ScreenManager.NavigateBack(); }; diff --git a/OctoAwesome/OctoAwesome.Client/Screens/MainScreen.cs b/OctoAwesome/OctoAwesome.Client/Screens/MainScreen.cs index 51a9e0ac..bdfa6ca5 100644 --- a/OctoAwesome/OctoAwesome.Client/Screens/MainScreen.cs +++ b/OctoAwesome/OctoAwesome.Client/Screens/MainScreen.cs @@ -29,7 +29,6 @@ public MainScreen(AssetComponent assets) startButton.Margin = new Border(0, 0, 0, 10); startButton.LeftMouseClick += (s, e) => { - ((ContainerResourceManager)ScreenManager.Game.ResourceManager).CreateManager(false); ScreenManager.NavigateToScreen(new LoadScreen(assets)); }; stack.Controls.Add(startButton); diff --git a/OctoAwesome/OctoAwesome.Client/Screens/PauseScreen.cs b/OctoAwesome/OctoAwesome.Client/Screens/PauseScreen.cs index 662f30ba..feb97b93 100644 --- a/OctoAwesome/OctoAwesome.Client/Screens/PauseScreen.cs +++ b/OctoAwesome/OctoAwesome.Client/Screens/PauseScreen.cs @@ -33,6 +33,15 @@ public PauseScreen(AssetComponent assets) }; stack.Controls.Add(resumeButton); + Button openToLanButton = new TextButton(UI.Languages.OctoClient.OpenToNetwork); + openToLanButton.HorizontalAlignment = HorizontalAlignment.Stretch; + openToLanButton.Margin = new Border(0, 0, 0, 10); + openToLanButton.LeftMouseClick += (s, e) => + { + + }; + //stack.Controls.Add(openToLanButton); + Button optionButton = new TextButton(UI.Languages.OctoClient.Options); optionButton.HorizontalAlignment = HorizontalAlignment.Stretch; optionButton.Margin = new Border(0, 0, 0, 10); @@ -57,9 +66,7 @@ public PauseScreen(AssetComponent assets) mainMenuButton.Margin = new Border(0, 0, 0, 10); mainMenuButton.LeftMouseClick += (s, e) => { - ScreenManager.Player.Unload(); - ScreenManager.Game.Simulation.ExitGame(); - + ScreenManager.Game.GameService.ExitGame(); foreach (var gameScreen in ScreenManager.History.OfType()) { gameScreen.Unload(); diff --git a/OctoAwesome/OctoAwesome.Client/Screens/TargetScreen.cs b/OctoAwesome/OctoAwesome.Client/Screens/TargetScreen.cs index dee1f5e4..62e3e1ae 100644 --- a/OctoAwesome/OctoAwesome.Client/Screens/TargetScreen.cs +++ b/OctoAwesome/OctoAwesome.Client/Screens/TargetScreen.cs @@ -113,7 +113,7 @@ public TargetScreen(AssetComponent assets, Action tp, Coordinate pos var gl = c.GroundLevel(x, y); c.Flush(); - var playerHeight = player.CurrentEntity.GetComponent()?.Height ?? 4; + var playerHeight = player.CurrentPlayer.GetComponent()?.Height ?? 4; var offset = (int)Math.Round(playerHeight * 2, MidpointRounding.ToPositiveInfinity); coordinate.GlobalBlockIndex += new Index3(0, 0, gl + offset); diff --git a/OctoAwesome/OctoAwesome.GameServer/Commands/GeneralCommands.cs b/OctoAwesome/OctoAwesome.GameServer/Commands/GeneralCommands.cs deleted file mode 100644 index 2f1ea12f..00000000 --- a/OctoAwesome/OctoAwesome.GameServer/Commands/GeneralCommands.cs +++ /dev/null @@ -1,52 +0,0 @@ -using CommandManagementSystem.Attributes; -using OctoAwesome.Network; -using System; -using System.IO; - -namespace OctoAwesome.GameServer.Commands -{ - /// - /// Contains general remote commands. - /// - public static class GeneralCommands - { - /// - /// Gets the current universe. - /// - /// This is currently ignored. - /// The universe data. - [Command((ushort)OfficialCommand.GetUniverse)] - public static byte[] GetUniverse(CommandParameter parameter) // TODO: use parameter for multi universe server? - { - var universe = TypeContainer.Get().GetUniverse(); - - using (var memoryStream = new MemoryStream()) - using (var writer = new BinaryWriter(memoryStream)) - { - universe.Serialize(writer); - return memoryStream.ToArray(); - } - } - - /// - /// Gets the planet. - /// - /// This is currently ignored. - /// The planet with id 0 - for now. - [Command((ushort)OfficialCommand.GetPlanet)] - public static byte[] GetPlanet(CommandParameter parameter) // TODO: use parameter for actual planet server? - { - Console.WriteLine("Just got in here"); - - var planet = TypeContainer.Get().GetPlanet(0); - - using (var memoryStream = new MemoryStream()) - using (var writer = new BinaryWriter(memoryStream)) - { - planet.Serialize(writer); - Console.WriteLine("Sending Planet Result"); - return memoryStream.ToArray(); - } - } - } -} diff --git a/OctoAwesome/OctoAwesome.GameServer/Commands/NotificationCommands.cs b/OctoAwesome/OctoAwesome.GameServer/Commands/NotificationCommands.cs deleted file mode 100644 index 1c0f7c2e..00000000 --- a/OctoAwesome/OctoAwesome.GameServer/Commands/NotificationCommands.cs +++ /dev/null @@ -1,93 +0,0 @@ -using CommandManagementSystem.Attributes; -using OctoAwesome.Network; -using OctoAwesome.Notifications; -using OctoAwesome.Pooling; -using OctoAwesome.Rx; -using OctoAwesome.Serialization; -using System; - -namespace OctoAwesome.GameServer.Commands -{ - /// - /// Contains remote notification commands. - /// - public static class NotificationCommands - { - private static readonly IPool entityNotificationPool; - private static readonly IPool blockChangedNotificationPool; - private static readonly IPool blocksChangedNotificationPool; - - private static readonly ConcurrentRelay simulationChannel; - private static readonly ConcurrentRelay networkChannel; - private static readonly ConcurrentRelay chunkChannel; - private static readonly IDisposable simulationChannelSub; - private static readonly IDisposable networkChannelSub; - private static readonly IDisposable chunkChannelSub; - - static NotificationCommands() - { - var updateHub = TypeContainer.Get(); - entityNotificationPool = TypeContainer.Get>(); - blockChangedNotificationPool = TypeContainer.Get>(); - blocksChangedNotificationPool = TypeContainer.Get>(); - - simulationChannel = new(); - networkChannel = new(); - chunkChannel = new(); - - simulationChannelSub = updateHub.AddSource(simulationChannel, DefaultChannels.Simulation); - networkChannelSub = updateHub.AddSource(networkChannel, DefaultChannels.Network); - chunkChannelSub = updateHub.AddSource(chunkChannel, DefaultChannels.Chunk); - } - - /// - /// Manifests entity changes received from . - /// - /// The containing the entity notification data. - /// null - [Command((ushort)OfficialCommand.EntityNotification)] - public static byte[]? EntityNotification(CommandParameter parameter) - { - var entityNotification = Serializer.DeserializePoolElement(entityNotificationPool, parameter.Data); - entityNotification.SenderId = parameter.ClientId; - - simulationChannel.OnNext(entityNotification); - networkChannel.OnNext(entityNotification); - - entityNotification.Release(); - return null; - } - - /// - /// Manifests block changes received from . - /// - /// The containing the chunk notification data. - /// null - [Command((ushort)OfficialCommand.ChunkNotification)] - public static byte[]? ChunkNotification(CommandParameter parameter) - { - var notificationType = (BlockNotificationType)parameter.Data[0]; - Notification chunkNotification; - switch (notificationType) - { - case BlockNotificationType.BlockChanged: - chunkNotification = Serializer.DeserializePoolElement(blockChangedNotificationPool, parameter.Data); - break; - case BlockNotificationType.BlocksChanged: - chunkNotification = Serializer.DeserializePoolElement(blocksChangedNotificationPool, parameter.Data); - break; - default: - throw new NotSupportedException($"This Type is not supported: {notificationType}"); - } - - chunkNotification.SenderId = parameter.ClientId; - - chunkChannel.OnNext(chunkNotification); - networkChannel.OnNext(chunkNotification); - - chunkNotification.Release(); - - return null; - } - } -} diff --git a/OctoAwesome/OctoAwesome.GameServer/Extensions.cs b/OctoAwesome/OctoAwesome.GameServer/Extensions.cs new file mode 100644 index 00000000..677993a5 --- /dev/null +++ b/OctoAwesome/OctoAwesome.GameServer/Extensions.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OctoAwesome.GameServer; +internal static class Extensions +{ + + internal static Command Create(this Command command, string name, string description) + { + var childCommand = new Command(name, description); + + command.Add(childCommand); + return childCommand; + } + internal static Command Create(this Command command, string name, string description, Action handler) + { + var childCommand = new Command(name, description); + + childCommand.SetHandler(handler); + command.Add(childCommand); + return childCommand; + } + + internal static Command Create(this Command command, string name, string description, Action handler, Option option) + { + var childCommand = new Command(name, description) + { + option + }; + + childCommand.SetHandler(handler, option); + command.Add(childCommand); + return childCommand; + } + internal static Command Create(this Command command, string name, string description, Action handler, Option option1, Option option2) + { + var childCommand = new Command(name, description) + { + option1, + option2 + }; + + childCommand.SetHandler(handler, option1, option2); + command.Add(childCommand); + return childCommand; + } + internal static Command Create(this Command command,string name, string description, Action handler, Option option1, Option option2, Option option3) + { + var childCommand = new Command(name, description) + { + option1, + option2, + option3 + }; + + childCommand.SetHandler(handler, option1, option2, option3); + command.Add(childCommand); + return childCommand; + } + internal static Command Create(this Command command,string name, string description, Action handler, Option option1, Option option2, Option option3, Option option4) + { + var childCommand = new Command(name, description) + { + option1, + option2, + option3, + option4 + }; + + childCommand.SetHandler(handler, option1, option2, option3, option4); + command.Add(childCommand); + return childCommand; + } + internal static Command Create(this Command command,string name, string description, Action handler, Option option1, Option option2, Option option3, Option option4, Option option5) + { + var childCommand = new Command(name, description) + { + option1, + option2, + option3, + option4, + option5 + }; + + childCommand.SetHandler(handler, option1, option2, option3, option4, option5); + command.Add(childCommand); + return childCommand; + } + + internal static Command Add(this Command parent, string name, string description, Action handler) + { + parent.Create(name, description, handler); + return parent; + } + internal static Command Add(this Command parent, string name, string description, Action handler, Option option) + { + parent.Create(name, description, handler, option); + return parent; + } + internal static Command Add(this Command parent, string name, string description, Action handler, Option option1, Option option2) + { + parent.Create(name, description, handler, option1, option2); + return parent; + } + internal static Command Add(this Command parent, string name, string description, Action handler, Option option1, Option option2, Option option3) + { + parent.Create(name, description, handler, option1, option2, option3); + return parent; + } + internal static Command Add(this Command parent, string name, string description, Action handler, Option option1, Option option2, Option option3, Option option4) + { + parent.Create(name, description, handler, option1, option2, option3, option4); + return parent; + } + internal static Command Add(this Command parent, string name, string description, Action handler, Option option1, Option option2, Option option3, Option option4, Option option5) + { + parent.Create(name, description, handler, option1, option2, option3, option4, option5); + return parent; + } +} diff --git a/OctoAwesome/OctoAwesome.GameServer/OctoAwesome.GameServer.csproj b/OctoAwesome/OctoAwesome.GameServer/OctoAwesome.GameServer.csproj index 13a5382f..cf2fe63e 100644 --- a/OctoAwesome/OctoAwesome.GameServer/OctoAwesome.GameServer.csproj +++ b/OctoAwesome/OctoAwesome.GameServer/OctoAwesome.GameServer.csproj @@ -9,8 +9,8 @@ - + @@ -19,4 +19,7 @@ + + + \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.GameServer/Program.cs b/OctoAwesome/OctoAwesome.GameServer/Program.cs index 48ec9ef0..168eeb0d 100644 --- a/OctoAwesome/OctoAwesome.GameServer/Program.cs +++ b/OctoAwesome/OctoAwesome.GameServer/Program.cs @@ -1,69 +1,243 @@ -using OctoAwesome.Logging; +using engenious; + +using OctoAwesome.Logging; using OctoAwesome.Network; + using System; +using System.CommandLine; +using System.CommandLine.Builder; +using System.CommandLine.Invocation; +using System.CommandLine.Parsing; using System.IO; using System.Threading; namespace OctoAwesome.GameServer { + + internal class ServerContext : IDisposable + { + public Parser IngameCommandParser { get; set; } + public Parser StartupCommandParser { get; set; } + public ITypeContainer TypeContainer { get; set; } + public ServerHandler ServerHandler { get; set; } + public ISettings Settings { get; set; } + + private bool disposedValue; + + public ServerContext(Parser ingameCommandParser, Parser startupCommandParser, ITypeContainer typeContainer, ServerHandler serverHandler, ISettings settings) + { + IngameCommandParser = ingameCommandParser; + StartupCommandParser = startupCommandParser; + TypeContainer = typeContainer; + ServerHandler = serverHandler; + Settings = settings; + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + TypeContainer.Remove(); + + TypeContainer.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } + internal class Program { private static void Main(string[] args) { - using (var typeContainer = TypeContainer.Get()) - { - Startup.Register(typeContainer); - Startup.ConfigureLogger(ClientType.GameServer); + /* + * StartParameter: + * - start -> runs immediately + * * port + * - execute (Run Command immediately after start) + * - wizard (setup) + * + * In-game (Additional): + * - stop / shutdown / restart + * - world + * * Save + * * create + * * load + * * delete + */ - Network.Startup.Register(typeContainer); + //var res = builder.Parse("execute stop"); + //if (res.Errors.Count == 0) + // res.Invoke(); - var logger = (TypeContainer.GetOrNull() ?? NullLogger.Default).As("OctoAwesome.GameServer"); - AppDomain.CurrentDomain.UnhandledException += (s, e) => - { - File.WriteAllText( - Path.Combine(".", "logs", $"server-dump-{DateTime.Now:ddMMyy_hhmmss}.txt"), - e.ExceptionObject.ToString()); + //res = ingameCommands.Parse("stop"); + //if (res.Errors.Count == 0) + // res.Invoke(); - string message = $"Unhandled Exception: {e.ExceptionObject}"; - if (e.ExceptionObject is Exception ex) - logger.Fatal(message, ex); - else - logger.Fatal(message); + Startup.ConfigureLogger(ClientType.GameServer); + var typeContainer = CreateTypeContainer(); - logger.Flush(); - }; + var logger = (TypeContainer.GetOrNull() ?? NullLogger.Default).As("OctoAwesome.GameServer"); - var manualResetEvent = new ManualResetEvent(false); + using ServerContext context = CreateServerContext(logger, typeContainer); - logger.Info("Server start"); - var fileInfo = new FileInfo(Path.Combine(".", "settings.json")); - Settings settings; + AppDomain.CurrentDomain.UnhandledException += (s, e) => + { + File.WriteAllText( + Path.Combine(".", "logs", $"server-dump-{DateTime.Now:ddMMyy_hhmmss}.txt"), + e.ExceptionObject.ToString()); - if (!fileInfo.Exists) - { - logger.Debug("Create new Default Settings"); - settings = new Settings() - { - FileInfo = fileInfo - }; - settings.Save(); - } + string message = $"Unhandled Exception: {e.ExceptionObject}"; + if (e.ExceptionObject is Exception ex) + logger.Fatal(message, ex); else + logger.Fatal(message); + + logger.Flush(); + }; + + Run(context, args); + } + + private static void Run(ServerContext context, string[] args) + { + string? command = string.Join(' ', args); + while (true) + { + ExecuteCommand( + context.ServerHandler.SimulationManager.IsRunning + ? context.IngameCommandParser + : context.StartupCommandParser, + command); + + command = Console.ReadLine(); + + } + } + + private static int ExecuteCommand(Parser parser, string? command) + { + if (command is null) + return -1; + try + { + + var commandResult = parser.Parse(command); + + if (commandResult.Errors.Count > 0) { - logger.Debug("Load Settings"); - settings = new Settings(fileInfo); + //TODO Write the fucking manual and then read it! } + return commandResult.Invoke(); + } + catch (Exception ex) + { + Console.WriteLine(ex); + return -1; - typeContainer.Register(settings); - typeContainer.Register(settings); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Get().Start(); + } + } - Console.CancelKeyPress += (s, e) => manualResetEvent.Set(); - manualResetEvent.WaitOne(); + private static Settings GetSettings(ILogger logger) + { + Settings settings; + var fileInfo = new FileInfo(Path.Combine(".", "settings.json")); + + if (!fileInfo.Exists) + { + logger.Debug("Create new Default Settings"); + settings = new Settings() + { + FileInfo = fileInfo + }; settings.Save(); } + else + { + logger.Debug("Load Settings"); + settings = new Settings(fileInfo); + } + + return settings; + } + + private static ServerContext CreateServerContext(ILogger logger, ITypeContainer typeContainer) + { + Settings settings = GetSettings(logger); + + typeContainer.Register(settings); + typeContainer.Register(settings); + + + var sh = new ServerHandler(typeContainer); + typeContainer.Register(sh); + + var portOption = new Option("--port", ()=>8888, "Defines the port where the server listen on"); + var ingameRoot = new Command("execute"); + var root = new RootCommand("starts the server with default options") + { + ingameRoot, portOption + }; + root.SetHandler(sh.Start, portOption); + + //var stopCommand = new Command("stop"); + //stopCommand.SetHandler(() => Console.WriteLine("Stopped")); + //ingameRoot.Add(stopCommand); + + root + .Add("start", "this starts the server immediately", sh.Start, portOption) + //.Add("wizard", "guides through the creation of a new world", ExecuteStart, portOption) + //.Add("execute", "Run Command immediately after start", ExecuteStart, portOption) + ; + + ingameRoot + .Add("stop", "stops the server and saves the world", sh.Stop) + .Add("start", "starts a previously stopped server", sh.Start, portOption) + // .Add("restart", "restarts the server after saving the world", ExecuteStart, portOption) + // .Add("shutdown", "force stops the server without saving", ExecuteStart, portOption) + ; + + //ingameRoot + // .Create("world", "world related commands") + // .Add("save", "saves the world", ExecuteStart, portOption) + // .Add("create", "created a whole new world, with a fantastic point of view", ExecuteStart, portOption) + // .Add("load", "loads an existing world", ExecuteStart, portOption) + // .Add("delete", "deletes an existing world", ExecuteStart, portOption); + + var builder = new CommandLineBuilder(root).UseDefaults().Build(); + var ingameCommands = new CommandLineBuilder(ingameRoot).UseDefaults().Build(); + + return new ServerContext + ( + ingameCommands, + builder, + typeContainer, + sh, + settings + ); + } + + private static ITypeContainer CreateTypeContainer() + { + var typeContainer = TypeContainer.Get(); + Startup.Register(typeContainer); + Network.Startup.Register(typeContainer); + return typeContainer; + } + + private static void ExecuteStart(bool obj) + { + throw new NotImplementedException(); } diff --git a/OctoAwesome/OctoAwesome.GameServer/ServerHandler.cs b/OctoAwesome/OctoAwesome.GameServer/ServerHandler.cs deleted file mode 100644 index 62c8c64b..00000000 --- a/OctoAwesome/OctoAwesome.GameServer/ServerHandler.cs +++ /dev/null @@ -1,99 +0,0 @@ -using CommandManagementSystem; -using OctoAwesome.Logging; -using OctoAwesome.Network; -using OctoAwesome.Notifications; -using System; -using System.Net; -using OctoAwesome.Rx; - - -namespace OctoAwesome.GameServer -{ - /// - /// Handler for server connection and simulation. - /// - public class ServerHandler - { - /// - /// Gets the simulation manager. - /// - public SimulationManager SimulationManager { get; } - - /// - /// Gets the update hub. - /// - public IUpdateHub UpdateHub { get; } - - private readonly ILogger logger; - private readonly Server server; - private readonly DefaultCommandManager defaultManager; - - /// - /// Initializes a new instance of the class. - /// - public ServerHandler() - { - logger = (TypeContainer.GetOrNull() ?? NullLogger.Default).As(typeof(ServerHandler)); - - TypeContainer.Register(InstanceBehavior.Singleton); - TypeContainer.Register(InstanceBehavior.Singleton); - TypeContainer.Register(InstanceBehavior.Singleton); - TypeContainer.Register(InstanceBehavior.Singleton); - - SimulationManager = TypeContainer.Get(); - UpdateHub = TypeContainer.Get(); - server = TypeContainer.Get(); - - defaultManager = new DefaultCommandManager(typeof(ServerHandler).Namespace + ".Commands"); - } - - /// - /// Start the game server simulation and connection. - /// - public void Start() - { - SimulationManager.Start(); //Temp - server.Start(new IPEndPoint(IPAddress.Any, 8888), new IPEndPoint(IPAddress.IPv6Any, 8888)); - server.OnClientConnected += ServerOnClientConnected; - } - - private void ServerOnClientConnected(object? sender, ConnectedClient e) - { - logger.Debug("Hurra ein neuer Spieler"); - e.ServerSubscription = e.Packages.Subscribe(OnNext, ex => logger.Error(ex.Message, ex)); - } - - /// - /// Gets called when a new package is received. - /// - /// The received package. - public void OnNext(Package value) - { - if (value.Command == 0 && value.Payload.Length == 0) - { - logger.Debug("Received null package"); - return; - } - logger.Trace("Received a new Package with ID: " + value.UId); - try - { - value.Payload = defaultManager.Dispatch(value.Command, new CommandParameter(value.BaseClient.Id, value.Payload)); - } - catch (Exception ex) - { - logger.Error($"Dispatch failed in Command {value.OfficialCommand}\r\n{ex}", ex); - return; - } - - logger.Trace(value.OfficialCommand); - - if (value.Payload == null) - { - logger.Trace($"Payload is null, returning from Command {value.OfficialCommand} without sending return package."); - return; - } - - _ = value.BaseClient.SendPackageAsync(value); - } - } -} diff --git a/OctoAwesome/OctoAwesome.Network.Tests/AsyncStreamExperiments.cs b/OctoAwesome/OctoAwesome.Network.Tests/AsyncStreamExperiments.cs deleted file mode 100644 index 8c00c1a4..00000000 --- a/OctoAwesome/OctoAwesome.Network.Tests/AsyncStreamExperiments.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace OctoAwesome.Network.Tests -{ - public class AsyncStreamExperiments - { - [Test] - public void ReadWriteSwap() - { - OctoNetworkStream test = new OctoNetworkStream(); - byte[] writeData = new byte[400]; - byte[] readData = new byte[400]; - Random r = new Random(); - - r.NextBytes(writeData); - - test.Write(writeData, 0, writeData.Length); - test.Read(readData, 0, readData.Length); - - Assert.IsTrue(writeData.SequenceEqual(readData)); - } - - [Test] - public async Task ReadWriteSwapAsync() - { - OctoNetworkStream test = new OctoNetworkStream(); - byte[] writeData = new byte[400]; - byte[] readData = new byte[400]; - Random r = new Random(); - - r.NextBytes(writeData); - Task readTask = new Task(async () => - { - int o = 0; - while (test.Read(readData, o, 100) != 0) - { - await Task.Delay(200); - o += 100; - } - }); - test.Write(writeData, 0, writeData.Length); - await Task.Delay(100); - - readTask.Start(); - - //Task writeTask = new Task(() => { - // for (int i = 0; i < 5; i++) - // { - // Thread.Sleep(1000); - // } - - //}); - await readTask; - Assert.IsTrue(writeData.SequenceEqual(readData)); - - } - - /// - /// You can rely on lama - /// - [Test] - public void Greet() - { - string viewer = @" - <'l - ll - llama~ - || || - '' '' - "; - - Assert.NotNull(viewer); - } - } -} diff --git a/OctoAwesome/OctoAwesome.Network.Tests/OctoAwesome.Network.Tests.csproj b/OctoAwesome/OctoAwesome.Network.Tests/OctoAwesome.Network.Tests.csproj index 450bd20e..db5e19da 100644 --- a/OctoAwesome/OctoAwesome.Network.Tests/OctoAwesome.Network.Tests.csproj +++ b/OctoAwesome/OctoAwesome.Network.Tests/OctoAwesome.Network.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/OctoAwesome/OctoAwesome.Network.Tests/OctoNetworkPackageTest.cs b/OctoAwesome/OctoAwesome.Network.Tests/OctoNetworkPackageTest.cs deleted file mode 100644 index cf1a6f3c..00000000 --- a/OctoAwesome/OctoAwesome.Network.Tests/OctoNetworkPackageTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Linq; -using NUnit.Framework; - -namespace OctoAwesome.Network.Tests -{ - [TestOf(typeof(Package))] - public class OctoNetworkPackageTest - { - private Package package; - private OctoNetworkStream networkStream; - - [Test] - public void PackageNormal() - { - package = new Package(0, 100); - Package packageDes = new Package(0, 100); - - Random r = new Random(); - - networkStream = new OctoNetworkStream(200); - r.NextBytes(package.Payload); - - //package.SerializePackage(networkStream); - - //packageDes.DeserializePackage(networkStream); - - Assert.IsTrue(packageDes.Payload.SequenceEqual(package.Payload)); - Assert.AreEqual(packageDes.Command, package.Command); - } - - [Test] - public void PackageWithSubPackages() - { - package = new Package(0, 1000); - Package packageDes = new Package(0, 1000); - - Random r = new Random(); - - networkStream = new OctoNetworkStream(100); - r.NextBytes(package.Payload); - - //package.SerializePackage(networkStream); - - //packageDes.DeserializePackage(networkStream); - - Assert.IsTrue(packageDes.Payload.SequenceEqual(package.Payload)); - Assert.AreEqual(packageDes.Command, package.Command); - } - - [Test] - public void TestReadWriteStream() - { - - } - } -} diff --git a/OctoAwesome/OctoAwesome.Network.Tests/OctoNetworkStreamTest.cs b/OctoAwesome/OctoAwesome.Network.Tests/OctoNetworkStreamTest.cs deleted file mode 100644 index 5d389922..00000000 --- a/OctoAwesome/OctoAwesome.Network.Tests/OctoNetworkStreamTest.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace OctoAwesome.Network.Tests -{ - [TestOf(typeof(OctoNetworkStream))] - public class OctoNetworkStreamTest - { - private OctoNetworkStream testStream; - private Random rand; - - /// - /// Initializes a new instance of the class - /// - public OctoNetworkStreamTest() - { - testStream = new OctoNetworkStream(); - rand = new Random(); - } - - [Test] - public void WriteTest() - { - var buffer = new byte[500]; - rand.NextBytes(buffer); - - testStream.Write(buffer, 0, buffer.Length); - } - - [Test] - public void ReadTest() - { - var buffer = new byte[500]; - var resultTest = new byte[500]; - rand.NextBytes(buffer); - - testStream.Write(buffer, 0, buffer.Length); - testStream.Read(resultTest, 0, resultTest.Length); - - Assert.AreEqual(buffer.Length, resultTest.Length); - Assert.IsTrue(buffer.SequenceEqual(resultTest)); - } - - [Test] - public void RingTest() - { - var buffer = new byte[500]; - var resultTest = new byte[500]; - rand.NextBytes(buffer); - - testStream.Write(buffer, 0, buffer.Length); - testStream.Read(resultTest, 0, resultTest.Length); - - buffer = new byte[600]; - resultTest = new byte[600]; - rand.NextBytes(buffer); - - testStream.Write(buffer, 0, buffer.Length); - testStream.Read(resultTest, 0, resultTest.Length); - - Assert.AreEqual(buffer.Length, resultTest.Length); - Assert.IsTrue(buffer.SequenceEqual(resultTest)); - } - } -} diff --git a/OctoAwesome/OctoAwesome.Network.Tests/ServerTests.cs b/OctoAwesome/OctoAwesome.Network.Tests/ServerTests.cs index b12d28d1..54d038ef 100644 --- a/OctoAwesome/OctoAwesome.Network.Tests/ServerTests.cs +++ b/OctoAwesome/OctoAwesome.Network.Tests/ServerTests.cs @@ -3,10 +3,21 @@ using System.Net; using System.Net.Sockets; using System.Text; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System.Linq; + +using NLog; + +using NonSucking.Framework.Extension.IoC; + using NUnit.Framework; +using OctoAwesome.Network.Pooling; +using OctoAwesome.Notifications; +using OctoAwesome.Rx; + namespace OctoAwesome.Network.Tests { [TestOf(typeof(Server))] @@ -22,20 +33,69 @@ public void NewServerTest() [Test] public void ConnectionTest() { - var resetEvent = new ManualResetEvent(false); - var server = new Server(); - server.Start(new IPEndPoint(IPAddress.Any, 44444)); - var testClient = new TcpClient("localhost", 44444); + TypeContainer.Register(new OctoAwesome.Logging.NullLogger("")); + TypeContainer.Register(new PackagePool()); + TypeContainer.Register(new UpdateHub()); - for (int i = 0; i < 201; i++) + byte[] data1 = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; + byte[] data2 = new byte[] { 0, 1, 2, 4, 8, 16, 32, 64, 128 }; + int count = 0; + int packagesReceivedServer = 0; + int packagesReceivedClient= 0; + List currentConnectedClients = new(); + while (true) { - Thread.Sleep(10); + var server = new Server(); + server.OnClientConnected += (s, o) => + { + currentConnectedClients.Add(o); + }; + using AutoResetEvent re = new AutoResetEvent(false); + using AutoResetEvent re2 = new AutoResetEvent(false); + + server.Start(new IPEndPoint(IPAddress.IPv6Any, 44444)); + + using var testClient = new Client("localhost", 44444); + testClient.Packages.Subscribe(x => { re2.Set(); packagesReceivedClient++; }); + while (true) + { + if (currentConnectedClients.Count == count) + Thread.Sleep(1); + else + break; + } + var latest = currentConnectedClients.Last(); + latest.Packages.Subscribe(x => { re.Set(); packagesReceivedServer++; }); + var pkg = new Package(data1); + testClient.SendPackageAsync(pkg).GetAwaiter().GetResult(); + if (!re.WaitOne(1000)) + ; + latest.SendPackageAsync(pkg).GetAwaiter().GetResult(); + if (!re2.WaitOne(1000)) + ; - if (testClient.Connected) - break; - Assert.IsTrue(i < 200); + var pkg2 = new Package(data2); + testClient.SendPackageAsync(pkg2).GetAwaiter().GetResult(); + if (!re.WaitOne(1000)) + ; + latest.SendPackageAsync(pkg2).GetAwaiter().GetResult(); + if (!re2.WaitOne(1000)) + ; + count++; + latest.Stop(); + server.Stop(); } + + //for (int i = 0; i < 201; i++) + //{ + // Thread.Sleep(10); + + // if (testClient.Connected) + // break; + + // Assert.IsTrue(i < 200); + //} } [Test] @@ -59,8 +119,7 @@ public void SendingTest() Task.Run(() => { - var testClient = new Client(); - testClient.Connect("127.0.0.1", 44444); + var testClient = new Client("127.0.0.1", 44444); wait.WaitOne(); diff --git a/OctoAwesome/OctoAwesome.Network/AssemblyInfo.cs b/OctoAwesome/OctoAwesome.Network/AssemblyInfo.cs new file mode 100644 index 00000000..73aa3083 --- /dev/null +++ b/OctoAwesome/OctoAwesome.Network/AssemblyInfo.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using NonSucking.Framework.Serialization; + +using OctoAwesome; +using OctoAwesome.Network.Request; + +[assembly: NoosonConfiguration( + GenerateDeserializeExtension = false, + DisableWarnings = true, + GenerateStaticDeserializeWithCtor = true, + GenerateDeserializeOnInstance = true, + GenerateStaticSerialize = true, + GenerateStaticDeserializeIntoInstance = true, + NameOfStaticDeserializeWithCtor = "DeserializeAndCreate", + NameOfDeserializeOnInstance = "Deserialize", + NameOfStaticDeserializeIntoInstance = "Deserialize", + NameOfStaticDeserializeWithOutParams = "DeserializeOut")] + +[assembly: SerializationId(1, uint.MaxValue - 1)] \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Network/BaseClient.cs b/OctoAwesome/OctoAwesome.Network/BaseClient.cs index b05c24a0..dd04bcbd 100644 --- a/OctoAwesome/OctoAwesome.Network/BaseClient.cs +++ b/OctoAwesome/OctoAwesome.Network/BaseClient.cs @@ -7,6 +7,10 @@ using System.Threading; using System.Threading.Tasks; using OctoAwesome.Extension; +using System.Collections.Generic; +using System.Collections.Concurrent; +using OctoAwesome.Logging; +using System.IO; namespace OctoAwesome.Network { @@ -18,10 +22,11 @@ public abstract class BaseClient : IDisposable private static uint NextId => ++nextId; private static uint nextId; - static BaseClient() - { - nextId = 0; - } + /// + /// Called when a the client has been disconnected from the server. + /// + public event EventHandler? ClientDisconnected; + /// /// Gets the client id. /// @@ -35,76 +40,122 @@ static BaseClient() /// /// The underlying socket. /// - protected Socket Socket + protected TcpClient TcpClient { - get => NullabilityHelper.NotNullAssert(socket); - set => socket = NullabilityHelper.NotNullAssert(value); + get => NullabilityHelper.NotNullAssert(tcpClient); + set => tcpClient = NullabilityHelper.NotNullAssert(value); } - /// - /// Low level pooled for receiving socket data. - /// - protected readonly SocketAsyncEventArgs ReceiveArgs; - - private byte readSendQueueIndex; - private byte nextSendQueueWriteIndex; - private bool sending; private Package? currentPackage; private readonly PackagePool packagePool; - private readonly SocketAsyncEventArgs sendArgs; - - private readonly (byte[] data, int len)[] sendQueue; - private readonly object sendLock; + private readonly ILogger logger; private readonly CancellationTokenSource cancellationTokenSource; private readonly ConcurrentRelay packages; - private Socket? socket; + private TcpClient? tcpClient; + private NetworkStream stream; + private ConcurrentQueue> toSend = new(); + private bool disposed; + static BaseClient() + { + nextId = 0; + } /// /// Initializes a new instance of the class. /// - protected BaseClient() + /// The low level base socket. + protected BaseClient(TcpClient socket) { packages = new ConcurrentRelay(); - sendQueue = new (byte[] data, int len)[256]; - sendLock = new object(); - ReceiveArgs = new SocketAsyncEventArgs(); - ReceiveArgs.Completed += OnReceived; - ReceiveArgs.SetBuffer(ArrayPool.Shared.Rent(1024 * 1024), 0, 1024 * 1024); packagePool = TypeContainer.Get(); - - sendArgs = new SocketAsyncEventArgs(); - sendArgs.Completed += OnSent; - + logger = TypeContainer.Get().As(typeof(BaseClient)); cancellationTokenSource = new CancellationTokenSource(); Id = NextId; - } - - /// - /// Initializes a new instance of the class. - /// - /// The low level base socket. - protected BaseClient(Socket socket) : this() - { - Socket = socket; - Socket.NoDelay = true; + TcpClient = socket; + TcpClient.NoDelay = true; + TcpClient.SendTimeout = 2000; } /// /// Starts receiving data for this client asynchronously. /// /// The created receiving task. - public Task Start() + public void Start() { - return Task.Run(() => + logger.Debug($"Starting connection to {TcpClient.Client.RemoteEndPoint}"); + stream = TcpClient.GetStream(); + CancellationToken token = cancellationTokenSource.Token; + _ = Task.Run(() => { - if (Socket.ReceiveAsync(ReceiveArgs)) - return; + try + { + do + { + if (token.IsCancellationRequested) + return; + + if (!toSend.TryDequeue(out var bytes)) + { + Thread.Sleep(1); + continue; + } + stream.Write(bytes.Span); + stream.Flush(); + logger.Trace($"Did send the data with len {bytes.Length}"); + + } while (true); + } + catch (IOException ex) + { + logger.Error(ex); + Stop(); + } + }); + + Task.Run(async () => + { + var buffer = new byte[1024 * 1024]; + try + { + do + { + token.ThrowIfCancellationRequested(); + + var readBytes = await stream.ReadAsync(buffer, token); + + logger.Trace($"Recieved bytes amount: {readBytes}"); + if (readBytes < 1) + { + //abort! + if (!token.IsCancellationRequested) + Stop(); + return; + } + + int offset = 0; + do + { + offset += DataReceived(buffer, readBytes, offset); + logger.Trace($"Recieved bytes new offest: {offset}"); + } while (offset < readBytes); + + } while (true); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + Stop(); + logger.Error("Got exception during stream read", ex); + throw; + } + }); - Receive(ReceiveArgs); - }, cancellationTokenSource.Token); } /// @@ -112,7 +163,10 @@ public Task Start() /// public void Stop() { - cancellationTokenSource.Cancel(); + logger.Debug($"Stopping connection {TcpClient.Client.RemoteEndPoint}"); + if (!disposed) + cancellationTokenSource.Cancel(); + ClientDisconnected?.Invoke(this, EventArgs.Empty); } /// @@ -121,28 +175,18 @@ public void Stop() /// The byte buffer to send data from. /// The slice length of the data to send. /// The created sending task. - public Task SendAsync(byte[] data, int len) + public ValueTask SendAsync(byte[] data, int len) { - lock (sendLock) - { - if (sending) - { - sendQueue[nextSendQueueWriteIndex++] = (data, len); - return Task.CompletedTask; - } - - sending = true; - } - - return Task.Run(() => SendInternal(data, len)); + return SendInternal(data, len); } /// /// Send a package asynchronously. /// /// The package to send asynchronously. - public async Task SendPackageAsync(Package package) + public async ValueTask SendPackageAsync(Package package) { + logger.Debug($"Send package with id: {package.UId} and Flags: {package.PackageFlags} to client: {TcpClient.Client.RemoteEndPoint}"); byte[] bytes = new byte[package.Payload.Length + Package.HEAD_LENGTH]; package.SerializePackage(bytes, 0); await SendAsync(bytes, bytes.Length); @@ -153,7 +197,7 @@ public async Task SendPackageAsync(Package package) /// /// The package to send asynchronously. /// - public async Task SendPackageAndReleaseAsync(Package package) + public async ValueTask SendPackageAndReleaseAsync(Package package) { await SendPackageAsync(package); package.Release(); @@ -164,91 +208,20 @@ public async Task SendPackageAndReleaseAsync(Package package) /// /// The package to send asynchronously. /// - public void SendPackageAndRelease(Package package) + public async Task SendPackageAndRelease(Package package) { - var task = Task.Run(async () => await SendPackageAsync(package)); - task.Wait(); + await SendPackageAsync(package).ConfigureAwait(false); package.Release(); } - private void SendInternal(byte[] data, int len) + private async ValueTask SendInternal(byte[] data, int len) { - while (true) - { - sendArgs.SetBuffer(data, 0, len); - - Console.WriteLine("Send now a package"); - if (Socket.SendAsync(sendArgs)) - return; - - lock (sendLock) - { - if (readSendQueueIndex < nextSendQueueWriteIndex) - { - (data, len) = sendQueue[readSendQueueIndex++]; - } - else - { - nextSendQueueWriteIndex = 0; - readSendQueueIndex = 0; - sending = false; - return; - } - } - } - } - - private void OnSent(object? sender, SocketAsyncEventArgs e) - { - byte[] data; - int len; - - lock (sendLock) - { - if (readSendQueueIndex < nextSendQueueWriteIndex) - { - (data, len) = sendQueue[readSendQueueIndex++]; - } - else - { - nextSendQueueWriteIndex = 0; - readSendQueueIndex = 0; - sending = false; - return; - } - } + toSend.Enqueue(data.AsMemory(0, len)); - SendInternal(data, len); } - private void OnReceived(object? sender, SocketAsyncEventArgs e) - { - Receive(e); - } - /// - /// Receive low level socket data. - /// - /// Low level socket receive event arguments. - protected void Receive(SocketAsyncEventArgs e) - { - do - { - if (e.BytesTransferred < 1 || e.Buffer is null) - return; - - int offset = 0; - - do - { - offset += DataReceived(e.Buffer, e.BytesTransferred, offset); - - } while (offset < e.BytesTransferred); - - } while (!Socket.ReceiveAsync(e)); - } - - private int DataReceived(byte[] buffer, int length, int bufferOffset) + private int DataReceived(Span buffer, int length, int bufferOffset) { int offset = 0; @@ -259,9 +232,11 @@ private int DataReceived(byte[] buffer, int length, int bufferOffset) if (length - bufferOffset < Package.HEAD_LENGTH) { - var ex = new Exception($"Buffer is to small for package head deserialization [length: {length} | offset: {bufferOffset}]"); + var ex = new ArgumentOutOfRangeException(nameof(buffer), $"Buffer is to small for package head deserialization [buffersize: {buffer.Length} | length: {length} | offset: {bufferOffset}]"); + ex.Data.Add("buffer.Length", buffer.Length); ex.Data.Add(nameof(length), length); ex.Data.Add(nameof(bufferOffset), bufferOffset); + logger.Error($"Buffer was to small", ex); throw ex; } else @@ -272,7 +247,9 @@ private int DataReceived(byte[] buffer, int length, int bufferOffset) } else { - throw new InvalidCastException("Can not deserialize header with these bytes :("); + var ex = new InvalidCastException("Can not deserialize header with these bytes :("); + logger.Error($"Header deserialization failed", ex); + throw ex; } } } @@ -283,21 +260,25 @@ private int DataReceived(byte[] buffer, int length, int bufferOffset) { var package = currentPackage; - Debug.WriteLine("Package: " + package.UId); + logger.Trace($"Package {package} was complete, dispatching"); packages.OnNext(package); currentPackage = null; } + else + { + logger.Trace("Package was not complete, waiting for more bytes"); + } return offset; } - /// public virtual void Dispose() { + disposed = true; + cancellationTokenSource.Cancel(); packages.Dispose(); - ReceiveArgs.Dispose(); - sendArgs.Dispose(); cancellationTokenSource.Dispose(); + stream?.Dispose(); } } } diff --git a/OctoAwesome/OctoAwesome.Network/Client.cs b/OctoAwesome/OctoAwesome.Network/Client.cs index b17f8430..5085fdba 100644 --- a/OctoAwesome/OctoAwesome.Network/Client.cs +++ b/OctoAwesome/OctoAwesome.Network/Client.cs @@ -10,34 +10,17 @@ namespace OctoAwesome.Network /// public class Client : BaseClient { - /// - /// Connect to an OctoAwesome server on a specific port. - /// - /// The host ip or address to connect to. - /// The port to connect over. - /// Thrown when the host address could not be resolved. - public void Connect(string host, ushort port) + public Client(string host, ushort port) : base(new TcpClient()) { var address = Dns.GetHostAddresses(host).FirstOrDefault(); - if (address == default) - { - throw new ArgumentException(nameof(host)); - } - Socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - Socket.BeginConnect(new IPEndPoint(address, port), OnConnected, null); + + TcpClient.BeginConnect(address, port, OnConnected, null); } private void OnConnected(IAsyncResult ar) { - Socket.EndConnect(ar); - - while (true) - { - if (Socket.ReceiveAsync(ReceiveArgs)) - return; - - Receive(ReceiveArgs); - } + TcpClient.EndConnect(ar); + Start(); } } } \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.GameServer/CommandParameter.cs b/OctoAwesome/OctoAwesome.Network/CommandParameter.cs similarity index 95% rename from OctoAwesome/OctoAwesome.GameServer/CommandParameter.cs rename to OctoAwesome/OctoAwesome.Network/CommandParameter.cs index 41221f7d..c4a35c60 100644 --- a/OctoAwesome/OctoAwesome.GameServer/CommandParameter.cs +++ b/OctoAwesome/OctoAwesome.Network/CommandParameter.cs @@ -1,4 +1,4 @@ -namespace OctoAwesome.GameServer +namespace OctoAwesome.Network { /// /// Struct for command parameters. diff --git a/OctoAwesome/OctoAwesome.GameServer/Commands/ChunkCommands.cs b/OctoAwesome/OctoAwesome.Network/Commands/ChunkCommands.cs similarity index 55% rename from OctoAwesome/OctoAwesome.GameServer/Commands/ChunkCommands.cs rename to OctoAwesome/OctoAwesome.Network/Commands/ChunkCommands.cs index 611a4755..b4162f15 100644 --- a/OctoAwesome/OctoAwesome.GameServer/Commands/ChunkCommands.cs +++ b/OctoAwesome/OctoAwesome.Network/Commands/ChunkCommands.cs @@ -1,13 +1,13 @@ -using CommandManagementSystem.Attributes; - + using OctoAwesome.Chunking; using OctoAwesome.Location; using OctoAwesome.Network; +using OctoAwesome.Serialization; using System; using System.IO; -namespace OctoAwesome.GameServer.Commands +namespace OctoAwesome.Network.Commands { /// /// Contains commands for chunk loading and saving remotely. @@ -19,8 +19,7 @@ public static class ChunkCommands /// /// The given location to load the column at. /// The loaded chunk column data. - [Command((ushort)OfficialCommand.LoadColumn)] - public static byte[] LoadColumn(CommandParameter parameter) + public static ISerializable LoadColumn(ITypeContainer tc, CommandParameter parameter) { Guid guid; int planetId; @@ -34,14 +33,7 @@ public static byte[] LoadColumn(CommandParameter parameter) index2 = new Index2(reader.ReadInt32(), reader.ReadInt32()); } - var column = TypeContainer.Get().LoadColumn(planetId, index2); - - using (var memoryStream = new MemoryStream()) - using (var writer = new BinaryWriter(memoryStream)) - { - column.Serialize(writer); - return memoryStream.ToArray(); - } + return tc.Get().LoadColumn(planetId, index2); } /// @@ -49,20 +41,9 @@ public static byte[] LoadColumn(CommandParameter parameter) /// /// The containing the chunk column data. /// null - [Command((ushort)OfficialCommand.SaveColumn)] - public static byte[]? SaveColumn(CommandParameter parameter) + public static void SaveColumn(ITypeContainer tc, CommandParameter parameter, ChunkColumn chunkColumn) { - var chunkColumn = new ChunkColumn(); - - using (var memoryStream = new MemoryStream(parameter.Data)) - using (var reader = new BinaryReader(memoryStream)) - { - chunkColumn.Deserialize(reader); - } - - TypeContainer.Get().Simulation.ResourceManager.SaveChunkColumn(chunkColumn); - - return null; + tc.Get().Simulation.ResourceManager.SaveChunkColumn(chunkColumn, tc.Get().GetPlanet(chunkColumn.PlanetId)); } } } diff --git a/OctoAwesome/OctoAwesome.Network/Commands/GeneralCommands.cs b/OctoAwesome/OctoAwesome.Network/Commands/GeneralCommands.cs new file mode 100644 index 00000000..86ae5d6d --- /dev/null +++ b/OctoAwesome/OctoAwesome.Network/Commands/GeneralCommands.cs @@ -0,0 +1,36 @@ + +using OctoAwesome.Network; +using OctoAwesome.Serialization; + +using System; +using System.IO; + +namespace OctoAwesome.Network.Commands +{ + /// + /// Contains general remote commands. + /// + public static class GeneralCommands + { + /// + /// Gets the current universe. + /// + /// This is currently ignored. + /// The universe data. + public static ISerializable GetUniverse(ITypeContainer tc, CommandParameter parameter) // TODO: use parameter for multi universe server? + { + return tc.Get().GetUniverse(); + + } + + /// + /// Gets the planet. + /// + /// This is currently ignored. + /// The planet with id 0 - for now. + public static ISerializable GetPlanet(ITypeContainer tc, CommandParameter parameter) // TODO: use parameter for actual planet server? + { + return tc.Get().GetPlanet(0); + } + } +} diff --git a/OctoAwesome/OctoAwesome.Network/Commands/NotificationCommands.cs b/OctoAwesome/OctoAwesome.Network/Commands/NotificationCommands.cs new file mode 100644 index 00000000..ee51ac8c --- /dev/null +++ b/OctoAwesome/OctoAwesome.Network/Commands/NotificationCommands.cs @@ -0,0 +1,44 @@ + +using OctoAwesome.Network; +using OctoAwesome.Notifications; +using OctoAwesome.Pooling; +using OctoAwesome.Rx; +using OctoAwesome.Serialization; +using System; + +namespace OctoAwesome.Network.Commands +{ + /// + /// Contains remote notification commands. + /// + public class NotificationCommands : IDisposable + { + private readonly IUpdateHub updateHub; + private readonly IDisposable chunkChannelSubscription; + private readonly IDisposable chatChannelSubscription; + + public NotificationCommands() + { + updateHub = TypeContainer.Get(); + + chunkChannelSubscription = updateHub.ListenOn(DefaultChannels.Chunk).Subscribe(ChunkChannelOnNext); + chatChannelSubscription = updateHub.ListenOn(DefaultChannels.Chat).Subscribe(OnChatMessageReceived); + } + + private void OnChatMessageReceived(object obj) + { + updateHub.PushNetwork(obj, DefaultChannels.Chat); + } + + private void ChunkChannelOnNext(object obj) + { + updateHub.PushNetwork(obj, DefaultChannels.Chunk); + } + + public void Dispose() + { + chunkChannelSubscription.Dispose(); + chatChannelSubscription.Dispose(); + } + } +} diff --git a/OctoAwesome/OctoAwesome.GameServer/Commands/PlayerCommands.cs b/OctoAwesome/OctoAwesome.Network/Commands/PlayerCommands.cs similarity index 67% rename from OctoAwesome/OctoAwesome.GameServer/Commands/PlayerCommands.cs rename to OctoAwesome/OctoAwesome.Network/Commands/PlayerCommands.cs index 74d9acf4..1b5c3e74 100644 --- a/OctoAwesome/OctoAwesome.GameServer/Commands/PlayerCommands.cs +++ b/OctoAwesome/OctoAwesome.Network/Commands/PlayerCommands.cs @@ -1,6 +1,4 @@ -using CommandManagementSystem.Attributes; - -using engenious; +using engenious; using OctoAwesome.EntityComponents; using OctoAwesome.Location; @@ -11,29 +9,27 @@ using OctoAwesome.Serialization; using System; +using System.IO; using System.Text; -namespace OctoAwesome.GameServer.Commands +namespace OctoAwesome.Network.Commands { /// /// Contains remote player commands. /// public static class PlayerCommands { + private static readonly IUpdateHub updateHub; private static readonly ConcurrentRelay simulationChannel; - private static readonly ConcurrentRelay networkChannel; private static readonly IDisposable simulationChannelSub; - private static readonly IDisposable networkChannelSub; static PlayerCommands() { - var updateHub = TypeContainer.Get(); + updateHub = TypeContainer.Get(); simulationChannel = new ConcurrentRelay(); - networkChannel = new ConcurrentRelay(); simulationChannelSub = updateHub.AddSource(simulationChannel, DefaultChannels.Simulation); - networkChannelSub = updateHub.AddSource(networkChannel, DefaultChannels.Network); } /// @@ -41,20 +37,25 @@ static PlayerCommands() /// /// The containing the player data. /// null - [Command((ushort)OfficialCommand.Whoami)] - public static byte[] Whoami(CommandParameter parameter) + public static ISerializable Whoami(ITypeContainer tc, CommandParameter parameter) { - var updateHub = TypeContainer.Get(); string playername = Encoding.UTF8.GetString(parameter.Data); var player = new Player(); - var entityNotificationPool = TypeContainer.Get>(); + var entityNotificationPool = tc.Get>(); var entityNotification = entityNotificationPool.Rent(); entityNotification.Entity = player; entityNotification.Type = EntityNotification.ActionType.Add; + simulationChannel.OnNext(entityNotification); - entityNotification.Release(); + player.Components.Add(new ServerManagedComponent() { OnServer = true, Enabled = true }); + if(player.TryGetComponent(out var posComp)) + { + updateHub.PushNetwork(posComp.Planet, DefaultChannels.Planet); + } + + entityNotification.Release(); var remotePlayer = new RemoteEntity(player); remotePlayer.Components.AddIfTypeNotExists(new PositionComponent() { Position = new Coordinate(0, new Index3(0, 0, 78), new Vector3(0, 0, 0)) }); @@ -66,9 +67,16 @@ public static byte[] Whoami(CommandParameter parameter) entityNotification.Entity = remotePlayer; entityNotification.Type = EntityNotification.ActionType.Add; - networkChannel.OnNext(entityNotification); + using var ms = new MemoryStream(); + using var bw = new BinaryWriter(ms); + entityNotification.Serialize(bw); + //ms.Position = 0; + //using var br = new BinaryReader(ms); + //var deserers = EntityNotification.DeserializeAndCreate(br); + + updateHub.PushNetwork(entityNotification, DefaultChannels.Simulation); entityNotification.Release(); - return Serializer.Serialize(player); + return player; } } } diff --git a/OctoAwesome/OctoAwesome.Network/ConnectedClient.cs b/OctoAwesome/OctoAwesome.Network/ConnectedClient.cs index 7cb1b74a..0b9c6ff4 100644 --- a/OctoAwesome/OctoAwesome.Network/ConnectedClient.cs +++ b/OctoAwesome/OctoAwesome.Network/ConnectedClient.cs @@ -1,8 +1,13 @@ -using OctoAwesome.Network.Pooling; +using OctoAwesome.Caching; +using OctoAwesome.Network.Pooling; +using OctoAwesome.Network.Request; using OctoAwesome.Notifications; +using OctoAwesome.Pooling; using OctoAwesome.Rx; using OctoAwesome.Serialization; + using System; +using System.IO; using System.Net.Sockets; namespace OctoAwesome.Network @@ -20,65 +25,68 @@ public sealed class ConnectedClient : BaseClient public IDisposable? ServerSubscription { get; set; } private readonly PackagePool packagePool; + private readonly Pool requestPool; /// /// Initializes a new instance of the class. /// /// The low level base socket. - public ConnectedClient(Socket socket) : base(socket) + public ConnectedClient(TcpClient socket) : base(socket) { packagePool = TypeContainer.Get(); var updateHub = TypeContainer.Get(); - networkSubscription = updateHub.ListenOn(DefaultChannels.Network).Subscribe(OnNext, OnError); + networkSubscription = updateHub.ListenOnNetwork().Subscribe(OnNext, OnError); + requestPool = new(); } - private void OnError(Exception error) { - Socket.Close(); + TcpClient.Close(); throw error; } - private void OnNext(Notification value) + private void OnNext(PushInfo value) { - if (value.SenderId == Id) - return; - - OfficialCommand command; - byte[] payload; - switch (value) + if (value.Notification is not SerializableNotification notification) { - case EntityNotification entityNotification: - command = OfficialCommand.EntityNotification; - payload = Serializer.Serialize(entityNotification); - break; - - case BlocksChangedNotification _: - case BlockChangedNotification _: - command = OfficialCommand.ChunkNotification; - payload = Serializer.Serialize((SerializableNotification)value); - break; - default: - return; + SendGeneric(value); + return; } - BuildAndSendPackage(payload, command); + if (notification.SenderId == Id) + return; + SendGeneric(value); } - private void BuildAndSendPackage(byte[] data, OfficialCommand officialCommand) + private void SendGeneric(PushInfo value) { + if (value.Notification is not ISerializable ser) + return; + var package = packagePool.Rent(); - package.Payload = data; - package.Command = (ushort)officialCommand; - SendPackageAndRelease(package); + + using (var memoryStream = Serializer.Manager.GetStream()) + using (var binaryWriter = new BinaryWriter(memoryStream)) + { + binaryWriter.Write(ser.GetType().SerializationId()); + binaryWriter.Write(value.Channel); + ser.Serialize(binaryWriter); + + package.PackageFlags = PackageFlags.Notification; + package.Payload = memoryStream.ToArray(); + + + _ = SendPackageAndRelease(package); + } } + /// public override void Dispose() { base.Dispose(); + networkSubscription?.Dispose(); ServerSubscription?.Dispose(); - networkSubscription.Dispose(); } } } \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Network/NetworkIdManager.cs b/OctoAwesome/OctoAwesome.Network/NetworkIdManager.cs new file mode 100644 index 00000000..97b2231d --- /dev/null +++ b/OctoAwesome/OctoAwesome.Network/NetworkIdManager.cs @@ -0,0 +1,81 @@ +using OctoAwesome.Runtime; +using OctoAwesome.Serialization; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OctoAwesome.Network; + +public class NetworkIdManager : IIdManager +{ + private Range firstIds; + private Range secondIds; + + int lastId = 0; + bool useFirstIds = true; + private readonly NetworkPackageManager networkPackageManager; + + public NetworkIdManager(NetworkPackageManager networkPackageManager) + { + this.networkPackageManager = networkPackageManager; + } + + private void NewRangeGotten(RangeRequest rr) + { + if (rr.FirstIds) + { + firstIds = rr.Response; + lastId = firstIds.Start.Value; + } + else + { + secondIds = rr.Response; + } + } + + public void Init() + { + var first = networkPackageManager.SendAndAwait(new RangeRequest { FirstIds = true }, PackageFlags.Request); + var second = networkPackageManager.SendAndAwait(new RangeRequest { FirstIds = false }, PackageFlags.Request); + first.Network = second.Network = true; + + NewRangeGotten(first.WaitOnAndRelease()); + NewRangeGotten(second.WaitOnAndRelease()); + + } + + public int GetNextId() + { + if (lastId == 0) + Init(); + if (useFirstIds) + { + if (firstIds.End.Value <= lastId + 1) + { + useFirstIds = false; + lastId = secondIds.Start.Value; + _ = GetNewIds(true); + } + } + else if (secondIds.End.Value <= lastId + 1) + { + useFirstIds = true; + lastId = firstIds.Start.Value; + _ = GetNewIds(false); + } + + return ++lastId; + } + + private async ValueTask GetNewIds(bool first) + { + await Task.Yield(); + + var second = networkPackageManager.SendAndAwait(new RangeRequest { FirstIds = first }, PackageFlags.Request); + second.Network = true; + NewRangeGotten(second.WaitOnAndRelease()); + } +} diff --git a/OctoAwesome/OctoAwesome.Network/NetworkPackageManager.cs b/OctoAwesome/OctoAwesome.Network/NetworkPackageManager.cs new file mode 100644 index 00000000..23557ad1 --- /dev/null +++ b/OctoAwesome/OctoAwesome.Network/NetworkPackageManager.cs @@ -0,0 +1,210 @@ +using OctoAwesome.Caching; +using OctoAwesome.Logging; +using OctoAwesome.Network.Pooling; +using OctoAwesome.Network.Request; +using OctoAwesome.Notifications; +using OctoAwesome.Pooling; +using OctoAwesome.Rx; +using OctoAwesome.Serialization; +using OctoAwesome.Threading; + +using System; +using System.Buffers; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data.Common; +using System.IO; +using System.IO.Compression; +using System.Net.NetworkInformation; +using System.Reflection.Metadata; + +namespace OctoAwesome.Network +{ + /// + /// Manages updates received and to be sent over network on client side. + /// + public class NetworkPackageManager : IDisposable + { + private readonly Client client; + private readonly ILogger logger; + private readonly IDisposable hubSubscription; + private readonly IDisposable simulationSource; + private readonly IDisposable chunkSource; + private readonly IDisposable clientSubscription; + private readonly IPool entityNotificationPool; + private readonly IPool blockChangedNotificationPool; + private readonly IPool blocksChangedNotificationPool; + private readonly PackagePool packagePool; + private readonly IPool awaiterPool; + private readonly ConcurrentDictionary packages = new(); + private readonly Pool requestPool; + + private readonly Relay simulationRelay; + private readonly Relay chunkChannel; + private readonly PackageActionHub packageActionHub; + + /// + /// Initializes a new instance of the class. + /// + /// The network client that is connected to the remote server. + /// The update hub to receive updates from. + public NetworkPackageManager(Client client, IUpdateHub updateHub, ITypeContainer typeContainer) + { + this.client = client; + + logger = (typeContainer.GetOrNull() ?? NullLogger.Default).As(typeof(NetworkPackageManager)); + entityNotificationPool = typeContainer.Get>(); + blockChangedNotificationPool = typeContainer.Get>(); + blocksChangedNotificationPool = typeContainer.Get>(); + packagePool = typeContainer.Get(); + awaiterPool = TypeContainer.Get>(); + + simulationRelay = new Relay(); + chunkChannel = new Relay(); + packageActionHub = new PackageActionHub(logger, typeContainer); + + hubSubscription + = updateHub + .ListenOnNetwork() + .Subscribe(OnNext, error => logger.Error(error.Message, error)); + + simulationSource = updateHub.AddSource(simulationRelay, DefaultChannels.Simulation); + chunkSource = updateHub.AddSource(chunkChannel, DefaultChannels.Chunk); + + clientSubscription = client.Packages.Subscribe(OnNext, OnError); + requestPool = new(); + + } + + /// + /// Gets called when a new package is received. + /// + /// The received package. + public void OnNext(Package package) + { + + if ((package.PackageFlags & PackageFlags.Response) > 0) + { + logger.Trace($"Package with id:{package.UId} and Flags: {package.PackageFlags}"); + + if (packages.TryRemove(package.UId, out var awaiter)) + { + if (!awaiter.TrySetResult(package.Payload)) + logger.Warn($"Awaiter can not set result package {package.UId}"); + } + else + { + logger.Error($"Got response for package without having a request with id {package.UId}"); + } + } + else + { + packageActionHub.Dispatch(package, client); + } + } + + private void OnNext(PushInfo value) + { + if (value.Notification is not SerializableNotification notification) + { + SendGeneric(value); + return; + } + + if (notification.SenderId == client.Id) + return; + SendGeneric(value); + } + + private void SendGeneric(PushInfo value) + { + if (value.Notification is not ISerializable ser) + return; + + var package = packagePool.Rent(); + + using (var memoryStream = Serializer.Manager.GetStream()) + using (var binaryWriter = new BinaryWriter(memoryStream)) + { + binaryWriter.Write(ser.GetType().SerializationId()); + binaryWriter.Write(value.Channel); + ser.Serialize(binaryWriter); + + package.PackageFlags = PackageFlags.Notification; + package.Payload = memoryStream.ToArray(); + _ = client.SendPackageAndRelease(package); + } + } + + + /// + /// Gets called when an error occured while receiving. + /// + /// The error that occured. + public void OnError(Exception error) + { + logger.Error(error.Message, error); + } + + /// + public void Dispose() + { + client.Dispose(); + hubSubscription.Dispose(); + simulationSource.Dispose(); + chunkSource.Dispose(); + chunkChannel.Dispose(); + simulationRelay.Dispose(); + clientSubscription.Dispose(); + } + + public Awaiter SendAndAwait(Stream stream, int offset, int length, PackageFlags flags = PackageFlags.None) + { + var package = packagePool.Rent(); + var buffer = ArrayPool.Shared.Rent(length); + stream.ReadExactly(buffer, offset, length); + package.Payload = buffer; + package.Length = length; + + package.PackageFlags = flags; + + var awaiter = GetAwaiter(package.UId); + + _ = client.SendPackageAndRelease(package); + return awaiter; + } + + public Awaiter SendAndAwait(byte[] serialized, PackageFlags flags = PackageFlags.None) + { + var package = packagePool.Rent(); + package.Payload = serialized; + package.PackageFlags = flags; + + var awaiter = GetAwaiter(package.UId); + _ = client.SendPackageAndRelease(package); + return awaiter; + } + /// + /// Serializes the serializable with see method and therefore write the SerializationId itself + /// + /// + /// + /// + public Awaiter SendAndAwait(ISerializable serializable, PackageFlags flags = PackageFlags.None) + { + return SendAndAwait(Serializer.SerializeNetwork(serializable), flags); + } + + private Awaiter GetAwaiter(uint packageUId) + { + var awaiter = awaiterPool.Rent(); + + if (!packages.TryAdd(packageUId, awaiter)) + { + logger.Error($"Awaiter for package {packageUId} could not be added"); + } + + return awaiter; + } + } +} \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Network/NetworkPersistenceManager.cs b/OctoAwesome/OctoAwesome.Network/NetworkPersistenceManager.cs index e8f5cfb0..c2c3d8d7 100644 --- a/OctoAwesome/OctoAwesome.Network/NetworkPersistenceManager.cs +++ b/OctoAwesome/OctoAwesome.Network/NetworkPersistenceManager.cs @@ -5,12 +5,16 @@ using System.Linq; using System.Text; +using OctoAwesome.Caching; + using OctoAwesome.Chunking; using OctoAwesome.Components; using OctoAwesome.Database; using OctoAwesome.Location; using OctoAwesome.Logging; using OctoAwesome.Network.Pooling; +using OctoAwesome.Network.Request; +using OctoAwesome.Notifications; using OctoAwesome.Pooling; using OctoAwesome.Rx; using OctoAwesome.Serialization; @@ -22,32 +26,24 @@ namespace OctoAwesome.Network /// /// Persists game data to a remote server. /// - public class NetworkPersistenceManager : IPersistenceManager, IDisposable + public class NetworkPersistenceManager : IPersistenceManager { - private readonly Client client; - private readonly IDisposable subscription; - private readonly ConcurrentDictionary packages; - private readonly ILogger logger; - private readonly IPool awaiterPool; - private readonly PackagePool packagePool; private readonly ITypeContainer typeContainer; + private readonly NetworkPackageManager networkPackageManager; + private readonly Pool requestPool; /// /// Initializes a new instance of the class. /// /// The type container to manage types. - /// The network client that is connected to the remote server. - public NetworkPersistenceManager(ITypeContainer typeContainer, Client client) + /// The network package manager. + public NetworkPersistenceManager(ITypeContainer typeContainer, NetworkPackageManager networkPackageManager) { - this.client = client; - subscription = client.Packages.Subscribe(OnNext, OnError); this.typeContainer = typeContainer; + this.networkPackageManager = networkPackageManager; + requestPool = new(); - packages = new ConcurrentDictionary(); - logger = (TypeContainer.GetOrNull() ?? NullLogger.Default).As(typeof(NetworkPersistenceManager)); - awaiterPool = TypeContainer.Get>(); - packagePool = TypeContainer.Get(); } /// @@ -62,68 +58,113 @@ public void DeleteUniverse(Guid universeGuid) /// public Awaiter Load(out IChunkColumn column, Guid universeGuid, IPlanet planet, Index2 columnIndex) { - var package = packagePool.Rent(); - package.Command = (ushort)OfficialCommand.LoadColumn; + column = null; - using (var memoryStream = new MemoryStream()) - using (var binaryWriter = new BinaryWriter(memoryStream)) - { - binaryWriter.Write(universeGuid.ToByteArray()); - binaryWriter.Write(planet.Id); - binaryWriter.Write(columnIndex.X); - binaryWriter.Write(columnIndex.Y); + using var memoryStream = Serializer.Manager.GetStream(); + using var binaryWriter = new BinaryWriter(memoryStream); - package.Payload = memoryStream.ToArray(); - } - column = new ChunkColumn(planet); - var awaiter = GetAwaiter(column, package.UId); + binaryWriter.Write(universeGuid.ToByteArray()); + binaryWriter.Write(planet.Id); + binaryWriter.Write(columnIndex.X); + binaryWriter.Write(columnIndex.Y); - client.SendPackageAndRelease(package); + var request = requestPool.Rent(); + request.Data = memoryStream.ToArray(); + request.Command = OfficialCommand.LoadColumn; + var awaiter = networkPackageManager.SendAndAwait(Serializer.Serialize(request), PackageFlags.Request); + + awaiter.SetDesializeFunc(GetDesializerFunc()); + request.Release(); return awaiter; } /// public Awaiter Load(out IPlanet planet, Guid universeGuid, int planetId) { - var package = packagePool.Rent(); - package.Command = (ushort)OfficialCommand.GetPlanet; - planet = typeContainer.Get(); - var awaiter = GetAwaiter(planet, package.UId); - client.SendPackageAndRelease(package); + var planetInstance = planet = typeContainer.Get(); - return awaiter; + using (var memoryStream = Serializer.Manager.GetStream()) + using (var binaryWriter = new BinaryWriter(memoryStream)) + { + Span guid = stackalloc byte[16]; + universeGuid.TryWriteBytes(guid); + + binaryWriter.Write(guid); + binaryWriter.Write(planetId); + + var request = requestPool.Rent(); + + request.Data = memoryStream.ToArray(); + request.Command = OfficialCommand.GetPlanet; + + var awaiter = networkPackageManager.SendAndAwait(Serializer.Serialize(request), PackageFlags.Request); + awaiter.SetDesializeFunc( + (b) => + { + using var memoryStream = Serializer.Manager.GetStream(b.AsSpan(sizeof(long)..)); + using var binaryReader = new BinaryReader(memoryStream); + var dto = OfficialCommandDTO.DeserializeAndCreate(binaryReader); + return Serializer.Deserialize(planetInstance, dto.Data); + }); + request.Release(); + return awaiter; + } } /// public Awaiter? Load(out Player player, Guid universeGuid, string playerName) { - var playerNameBytes = Encoding.UTF8.GetBytes(playerName); + player = null; + using var memoryStream = Serializer.Manager.GetStream(); + using var binaryWriter = new BinaryWriter(memoryStream); + binaryWriter.Write(playerName); + + var request = requestPool.Rent(); - var package = packagePool.Rent(); - package.Command = (ushort)OfficialCommand.Whoami; - package.Payload = playerNameBytes; + request.Data = memoryStream.ToArray(); + request.Command = OfficialCommand.Whoami; - player = new Player(); - var awaiter = GetAwaiter(player, package.UId); - client.SendPackageAndRelease(package); + var awaiter = networkPackageManager.SendAndAwait(Serializer.Serialize(request), PackageFlags.Request); + awaiter.SetDesializeFunc(GetDesializerFunc()); + request.Release(); return awaiter; } /// public Awaiter Load(out IUniverse universe, Guid universeGuid) { - var package = packagePool.Rent(); - package.Command = (ushort)OfficialCommand.GetUniverse; + universe = null; + using var memoryStream = Serializer.Manager.GetStream(); + using var binaryWriter = new BinaryWriter(memoryStream); + Span guid = stackalloc byte[16]; + universeGuid.TryWriteBytes(guid); - universe = new Universe(); - var awaiter = GetAwaiter(universe, package.UId); - client.SendPackageAndRelease(package); + binaryWriter.Write(guid); + var request = requestPool.Rent(); + + request.Data = memoryStream.ToArray(); + request.Command = OfficialCommand.GetUniverse; + + var awaiter = networkPackageManager.SendAndAwait(Serializer.Serialize(request), PackageFlags.Request); + awaiter.SetDesializeFunc(GetDesializerFunc()); + request.Release(); return awaiter; } + private static Func GetDesializerFunc() where T : IConstructionSerializable + { + return (b) => + { + using var memoryStream = Serializer.Manager.GetStream(b.AsSpan(sizeof(long)..)); + using var binaryReader = new BinaryReader(memoryStream); + var dto = OfficialCommandDTO.DeserializeAndCreate(binaryReader); + return Serializer.DeserializeSpecialCtor(dto.Data); + }; + } + /// public Awaiter? Load(out Entity? entity, Guid universeGuid, Guid entityId) { @@ -136,13 +177,7 @@ public Awaiter Load(out IUniverse universe, Guid universeGuid) where TContainer : ComponentContainer where TComponent : IComponent { - var package = packagePool.Rent(); - package.Command = (ushort)OfficialCommand.GetUniverse; - componentContainer = null; - //var awaiter = GetAwaiter(universe, package.UId); - client.SendPackageAndRelease(package); - return null; } @@ -158,18 +193,6 @@ public IEnumerable GetEntityIds(Guid universeGuid) public IEnumerable<(Guid Id, T Component)> GetAllComponents(Guid universeGuid) where T : IComponent, new() => Enumerable.Empty<(Guid Id, T Component)>(); - private Awaiter GetAwaiter(ISerializable serializable, uint packageUId) - { - var awaiter = awaiterPool.Rent(); - awaiter.Result = serializable; - - if (!packages.TryAdd(packageUId, awaiter)) - { - logger.Error($"Awaiter for package {packageUId} could not be added"); - } - - return awaiter; - } /// public void SaveColumn(Guid universeGuid, IPlanet planet, IChunkColumn column) @@ -202,71 +225,6 @@ public void Save(TContainer container, Guid universe) { } - /// - /// Sends a changed chunk column to the remote server. - /// - /// The changed chunk column. - public void SendChangedChunkColumn(IChunkColumn chunkColumn) - { - //var package = new Package((ushort)OfficialCommand.SaveColumn, 0); - - //using (var ms = new MemoryStream()) - //using (var bw = new BinaryWriter(ms)) - //{ - // chunkColumn.Serialize(bw, definitionManager); - // package.Payload = ms.ToArray(); - //} - - - //client.SendPackage(package); - } - - /// - /// Gets called when a package is received. - /// - /// The received package. - public void OnNext(Package package) - { - logger.Trace($"Package with id:{package.UId} for Command: {package.OfficialCommand}"); - - switch (package.OfficialCommand) - { - case OfficialCommand.Whoami: - case OfficialCommand.GetUniverse: - case OfficialCommand.GetPlanet: - case OfficialCommand.LoadColumn: - case OfficialCommand.SaveColumn: - if (packages.TryRemove(package.UId, out var awaiter)) - { - if (!awaiter.TrySetResult(package.Payload)) - logger.Warn($"Awaiter can not set result package {package.UId}"); - } - else - { - logger.Error($"No Awaiter found for Package: {package.UId}[{package.OfficialCommand}]"); - } - break; - default: - logger.Warn($"Cant handle Command: {package.OfficialCommand}"); - break; - } - } - - /// - /// Gets called when an error occured while receiving. - /// - /// The error that occured. - public void OnError(Exception error) - { - logger.Error(error.Message, error); - } - - /// - public void Dispose() - { - subscription.Dispose(); - } - /// public T GetComponent(Guid universeGuid, Guid id) where T : IComponent, new() { diff --git a/OctoAwesome/OctoAwesome.Network/NetworkUpdateManager.cs b/OctoAwesome/OctoAwesome.Network/NetworkUpdateManager.cs deleted file mode 100644 index b044760c..00000000 --- a/OctoAwesome/OctoAwesome.Network/NetworkUpdateManager.cs +++ /dev/null @@ -1,145 +0,0 @@ -using OctoAwesome.Logging; -using OctoAwesome.Network.Pooling; -using OctoAwesome.Notifications; -using OctoAwesome.Pooling; -using OctoAwesome.Rx; -using OctoAwesome.Serialization; -using System; - -namespace OctoAwesome.Network -{ - /// - /// Manages updates received and to be sent over network. - /// - public class NetworkUpdateManager : IDisposable - { - private readonly Client client; - private readonly ILogger logger; - private readonly IDisposable hubSubscription; - private readonly IDisposable simulationSource; - private readonly IDisposable chunkSource; - private readonly IDisposable clientSubscription; - private readonly IPool entityNotificationPool; - private readonly IPool blockChangedNotificationPool; - private readonly IPool blocksChangedNotificationPool; - private readonly PackagePool packagePool; - - private readonly Relay simulation; - private readonly Relay chunk; - - /// - /// Initializes a new instance of the class. - /// - /// The network client that is connected to the remote server. - /// The update hub to receive updates from. - public NetworkUpdateManager(Client client, IUpdateHub updateHub) - { - this.client = client; - - logger = (TypeContainer.GetOrNull() ?? NullLogger.Default).As(typeof(NetworkUpdateManager)); - entityNotificationPool = TypeContainer.Get>(); - blockChangedNotificationPool = TypeContainer.Get>(); - blocksChangedNotificationPool = TypeContainer.Get>(); - packagePool = TypeContainer.Get(); - - simulation = new Relay(); - chunk = new Relay(); - - hubSubscription - = updateHub - .ListenOn(DefaultChannels.Network) - .Subscribe(OnNext, error => logger.Error(error.Message, error)); - - simulationSource = updateHub.AddSource(simulation, DefaultChannels.Simulation); - chunkSource = updateHub.AddSource(chunk, DefaultChannels.Chunk); - - clientSubscription = client.Packages.Subscribe(package => OnNext(package), err => OnError(err)); - - } - - /// - /// Gets called when a new package is received. - /// - /// The received package. - public void OnNext(Package package) - { - switch (package.OfficialCommand) - { - case OfficialCommand.EntityNotification: - var entityNotification = Serializer.DeserializePoolElement(entityNotificationPool, package.Payload); - simulation.OnNext(entityNotification); - entityNotification.Release(); - break; - case OfficialCommand.ChunkNotification: - var notificationType = (BlockNotificationType)package.Payload[0]; - Notification chunkNotification; - - switch (notificationType) - { - case BlockNotificationType.BlockChanged: - chunkNotification = Serializer.DeserializePoolElement(blockChangedNotificationPool, package.Payload); - break; - case BlockNotificationType.BlocksChanged: - chunkNotification = Serializer.DeserializePoolElement(blocksChangedNotificationPool, package.Payload); - break; - default: - throw new NotSupportedException($"This Type is not supported: {notificationType}"); - } - - chunk.OnNext(chunkNotification); - chunkNotification.Release(); - break; - default: - break; - } - - } - - /// - /// Gets called when a new notification is received. - /// - /// The received notification. - public void OnNext(Notification value) - { - ushort command; - byte[] payload; - switch (value) - { - case EntityNotification entityNotification: - command = (ushort)OfficialCommand.EntityNotification; - payload = Serializer.Serialize(entityNotification); - break; - case BlockChangedNotification chunkNotification: - command = (ushort)OfficialCommand.ChunkNotification; - payload = Serializer.Serialize(chunkNotification); - break; - default: - return; - } - var package = packagePool.Rent(); - package.Command = command; - package.Payload = payload; - client.SendPackageAndRelease(package); - } - - /// - /// Gets called when an error occured while receiving. - /// - /// The error that occured. - public void OnError(Exception error) - { - logger.Error(error.Message, error); - } - - /// - public void Dispose() - { - hubSubscription.Dispose(); - simulationSource.Dispose(); - chunkSource.Dispose(); - chunk.Dispose(); - simulation.Dispose(); - clientSubscription.Dispose(); - } - } -} \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Network/OctoAwesome.Network.csproj b/OctoAwesome/OctoAwesome.Network/OctoAwesome.Network.csproj index 63258aa8..74a27b9d 100644 --- a/OctoAwesome/OctoAwesome.Network/OctoAwesome.Network.csproj +++ b/OctoAwesome/OctoAwesome.Network/OctoAwesome.Network.csproj @@ -1,33 +1,39 @@  - - net7.0 - enable - True - + + net7.0 + enable + True + - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + - - - + + + - - - - <_ReferencePathToRemove Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='System.Memory'" /> - <_ReferencePathToRemove Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='System.Buffers'" /> - - - - + + + <_ReferencePathToRemove Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='System.Memory'" /> + <_ReferencePathToRemove Include="@(ReferencePath)" Condition="'%(ReferencePath.NuGetPackageId)'=='System.Buffers'" /> + + + + + + + + \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Network/OctoNetworkStream.cs b/OctoAwesome/OctoAwesome.Network/OctoNetworkStream.cs deleted file mode 100644 index d1df7a1a..00000000 --- a/OctoAwesome/OctoAwesome.Network/OctoNetworkStream.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System; - -namespace OctoAwesome.Network -{ - /// - /// Double buffered network stream implementation. - /// - public class OctoNetworkStream - { - private byte[] readBuffer; - private byte[] writeBuffer; - - private readonly byte[] bufferA; - private readonly byte[] bufferB; - - private readonly object readLock; - private readonly object writeLock; - - private readonly int writeLength; - private readonly int readLength; - - private int maxReadCount; - - private int readPosition; - private int writePosition; - - private bool writingProcess; - - /// - /// Initializes a new instance of the class. - /// - /// The buffer capacity per buffer. - public OctoNetworkStream(int capacity = 1024) - { - bufferA = new byte[capacity]; - bufferB = new byte[capacity]; - readBuffer = bufferA; - writeBuffer = bufferB; - readLength = capacity; - writeLength = capacity; - readPosition = 0; - writePosition = 0; - readLock = new object(); - writeLock = new object(); - } - - /// - /// Writes a given range from a byte buffer to the stream. - /// - /// The buffer array to write to the stream. - /// The buffer slice offset to get from . - /// The buffer slice count to get from . - /// The number of bytes written. - public int Write(byte[] buffer, int offset, int count) - { - writingProcess = true; - - SwapBuffer(); - - var maxCopy = writeLength - writePosition; - - if (maxCopy < count) - count = maxCopy; - - if (maxCopy < 1) - { - writingProcess = false; - return maxCopy; - } - - lock (writeLock) - Buffer.BlockCopy(buffer, offset, writeBuffer, writePosition, count); - - writePosition += count; - - writingProcess = false; - - return count; - } - - /// - /// Writes a single byte to the stream. - /// - /// The single byte to write. - /// The number of bytes that where written. - public int Write(byte data) - { - writingProcess = true; - - SwapBuffer(); - - if (writeLength == writePosition) - { - writingProcess = false; - return 0; - } - - lock (writeLock) - writeBuffer[writePosition++] = data; - - writingProcess = false; - - return 1; - } - - /// - /// Reads from the stream into a buffer. - /// - /// The buffer to read into. - /// The slice buffer offset to start reading into. - /// The number of bytes to read. - /// The actually read number of bytes. - public int Read(byte[] buffer, int offset, int count) - { - if (!writingProcess) - SwapBuffer(); - - var maxCopy = maxReadCount - readPosition; - - if (maxCopy < 1) - return maxCopy; - - if (maxCopy < count) - count = maxCopy; - - lock (readLock) - Buffer.BlockCopy(readBuffer, readPosition, buffer, offset, count); - - readPosition += count; - - return count; - } - - /// - /// Gets the number of bytes available with a maximum value of . - /// - /// The maximum data to make available. - /// The available number of readable bytes in the stream. - public int DataAvailable(int count) - { - if (!writingProcess) - SwapBuffer(); - - var maxCopy = maxReadCount - readPosition; - - if (maxCopy < 1) - return maxCopy; - - if (maxCopy < count) - count = maxCopy; - - return count; - } - - private void SwapBuffer() - { - lock (readLock) - lock (writeLock) - { - if (readPosition > maxReadCount) - throw new IndexOutOfRangeException("ReadPosition is greater than MaxReadCount in OctoNetworkStream"); - if (readPosition < maxReadCount) - return; - - (writeBuffer, readBuffer) = (readBuffer, writeBuffer); - maxReadCount = writePosition; - writePosition = 0; - readPosition = 0; - } - } - - - } -} diff --git a/OctoAwesome/OctoAwesome.Network/OfficialCommand.cs b/OctoAwesome/OctoAwesome.Network/OfficialCommand.cs index 8973e32e..24489167 100644 --- a/OctoAwesome/OctoAwesome.Network/OfficialCommand.cs +++ b/OctoAwesome/OctoAwesome.Network/OfficialCommand.cs @@ -23,25 +23,14 @@ public enum OfficialCommand : ushort GetPlanet = 103, /// - /// For requesting a chunk column. + /// For requesting a chunkChannel column. /// LoadColumn = 104, /// - /// For sending a chunk column. + /// For sending a chunkChannel column. /// SaveColumn = 105, - - //400 - 500 Notifications - /// - /// For notification of entity changes. - /// - EntityNotification = 401, - - /// - /// For notification of chunk changes. - /// - ChunkNotification = 402 } } diff --git a/OctoAwesome/OctoAwesome.Network/Package.cs b/OctoAwesome/OctoAwesome.Network/Package.cs index ca45c4ba..04d5b794 100644 --- a/OctoAwesome/OctoAwesome.Network/Package.cs +++ b/OctoAwesome/OctoAwesome.Network/Package.cs @@ -5,17 +5,32 @@ namespace OctoAwesome.Network { + [Flags] + public enum PackageFlags : ushort + { + None = 1<<0, + Request = 1<<1, + Response = 1<<2, + Notification = 1<<3, + Compressed = 1<<4, + Array = 1<<5, + + Reserved = 1<<15 + } + /// /// OctoAwesome network package. /// public sealed class Package : IPoolElement { + //TODO add Type information for deserialize in npm + /// /// Byte size of Header. /// public const int HEAD_LENGTH = sizeof(ushort) + sizeof(int) + sizeof(uint); - private static uint nextUid; + private static uint nextUid = 1; /// /// Gets the next available package UID. /// @@ -41,25 +56,25 @@ public BaseClient BaseClient } /// - /// Gets the official command id for this package. - /// - /// - public OfficialCommand OfficialCommand => (OfficialCommand)Command; - - /// - /// Gets or sets the command id for this package. + /// The used for serialization and deserialization distinguishing. /// - public ushort Command { get; set; } + public PackageFlags PackageFlags { get; set; } /// /// Gets or sets the raw payload for the package. /// - public byte[] Payload + public byte[]? Payload { - get => NullabilityHelper.NotNullAssert(payload, $"{nameof(IPoolElement)} was not initialized!"); - set => payload = NullabilityHelper.NotNullAssert(value, $"{nameof(Payload)} cannot be initialized with null!"); + get => payload; + set + { + payload = value; + Length = payload?.Length ?? 0; + } } + public int Length {get;set;} + /// /// Gets or sets the UId of the package. /// @@ -68,28 +83,27 @@ public byte[] Payload /// /// Gets a value indicating whether the package payload is complete. /// - public bool IsComplete => internalOffset == Payload.Length; + public bool IsComplete => internalOffset == Length; /// /// Gets a value indicating the number of bytes missing for the package to be completed. /// - public int PayloadRest => Payload.Length - internalOffset; + public int PayloadRest => Length - internalOffset; /// /// Initializes a new instance of the class. /// /// The command id for this package. /// The number of bytes for the raw . - public Package(ushort command, int size) : this() + public Package(int size) : this() { - Command = command; Payload = new byte[size]; } /// /// Initializes a new instance of the class. /// - public Package() : this(true) + public Package() : this(false) { } /// @@ -106,9 +120,9 @@ public Package(bool setUid) /// Initializes a new instance of the class. /// /// The package payload. - public Package(byte[] data) : this(0, data.Length) + public Package(byte[] data) : this(true) { - // TODO: actually use data + Payload = data; } /// @@ -117,14 +131,14 @@ public Package(byte[] data) : this(0, data.Length) /// The buffer to deserialize data from. /// The offset to start deserialization from. /// Whether the header deserialization was successful. - public bool TryDeserializeHeader(byte[] buffer, int offset) + public bool TryDeserializeHeader(Span buffer, int offset) { if (buffer.Length - offset < HEAD_LENGTH) return false; - Command = (ushort)((buffer[offset] << 8) | buffer[offset + 1]); - Payload = new byte[BitConverter.ToInt32(buffer, offset + 2)]; - UId = BitConverter.ToUInt32(buffer, offset + 6); + PackageFlags = (PackageFlags)((buffer[offset] << 8) | buffer[offset + 1]); + Payload = new byte[BitConverter.ToInt32(buffer[(offset + 2)..])]; + UId = BitConverter.ToUInt32(buffer[(offset + 6)..]); internalOffset = 0; return true; } @@ -136,12 +150,15 @@ public bool TryDeserializeHeader(byte[] buffer, int offset) /// The offset to start deserialization from. /// The number of bytes that are allowed to be taken from the buffer. /// The number of bytes that was deserialized. - public int DeserializePayload(byte[] buffer, int offset, int count) + public int DeserializePayload(Span buffer, int offset, int count) { - if (internalOffset + count > Payload.Length) + if(count == 0) + return 0; + + if (internalOffset + count > Length) count = PayloadRest; - Buffer.BlockCopy(buffer, offset, Payload, internalOffset, count); + buffer[offset..(offset + count)].CopyTo(Payload.AsSpan(internalOffset)); internalOffset += count; return count; @@ -155,14 +172,14 @@ public int DeserializePayload(byte[] buffer, int offset, int count) /// The number of bytes that where serialized into the buffer. public int SerializePackage(byte[] buffer, int offset) { - buffer[offset] = (byte)(Command >> 8); - buffer[offset + 1] = (byte)(Command & 0xFF); - var bytes = BitConverter.GetBytes(Payload.Length); + buffer[offset] = (byte)((ushort)PackageFlags >> 8); + buffer[offset + 1] = (byte)((ushort)PackageFlags & 0xFF); + var bytes = BitConverter.GetBytes(Length); Buffer.BlockCopy(bytes, 0, buffer, offset + 2, 4); bytes = BitConverter.GetBytes(UId); Buffer.BlockCopy(bytes, 0, buffer, offset + 6, 4); - Buffer.BlockCopy(Payload, 0, buffer, offset + HEAD_LENGTH, Payload.Length); - return Payload.Length + HEAD_LENGTH; + Buffer.BlockCopy(Payload, 0, buffer, offset + HEAD_LENGTH, Length); + return Length + HEAD_LENGTH; } /// @@ -172,11 +189,16 @@ public void Init(IPool pool) Pool = pool; } + public override string ToString() + { + return $"Id: {UId}, Complete: {IsComplete}, Len: {Length}, Flags: {PackageFlags}"; + } + /// public void Release() { baseClient = default; - Command = default; + PackageFlags = default; payload = default; UId = default; internalOffset = default; diff --git a/OctoAwesome/OctoAwesome.Network/PackageActionHub.cs b/OctoAwesome/OctoAwesome.Network/PackageActionHub.cs new file mode 100644 index 00000000..0e594d49 --- /dev/null +++ b/OctoAwesome/OctoAwesome.Network/PackageActionHub.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using OctoAwesome.Serialization; +using System.Buffers; +using System.IO.Compression; +using System.IO; +using OctoAwesome.Logging; +using OctoAwesome.Pooling; +using System.Reflection; +using OctoAwesome.Notifications; +using System.Linq.Expressions; +using OctoAwesome.Caching; + +namespace OctoAwesome.Network +{ + public class PackageActionHub + { + private readonly ILogger logger; + private readonly ITypeContainer typeContainer; + private readonly IUpdateHub updateHub; + private readonly Dictionary> notificationDeserializationMethodCache; + + private readonly Dictionary> registeredStuff = new(); + private readonly Dictionary> registeredStuffDic = new(); + + + public PackageActionHub(ILogger logger, ITypeContainer tc) + { + this.logger = logger.As(nameof(PackageActionHub)); + typeContainer = tc; + updateHub = tc.Get(); + notificationDeserializationMethodCache = new(); + } + + /// + /// + /// + /// + /// + /// 0 when the should be used + public void Register(Action, RequestContext> action, ulong id = 0) where T : IConstructionSerializable + { + if (id == 0) + id = typeof(T).SerializationId(); + + var deserializeAction = (RequestContext package) => + { + var length = package.Reader.ReadInt32(); + var writeTo = ArrayPool.Shared.Rent(length); + for (int i = 0; i < length; i++) + writeTo[i] = T.DeserializeAndCreate(package.Reader); + + action(writeTo.AsMemory(0..length), package); + ArrayPool.Shared.Return(writeTo); + }; + registeredStuffDic.Add(id, deserializeAction); + } + + /// + /// + /// + /// + /// + /// 0 when the should be used + public void RegisterPoolable(Action, RequestContext> action, ulong id = 0) where T : IConstructionSerializable, IPoolElement + { + if (id == 0) + id = typeof(T).SerializationId(); + var pool = typeContainer.Get>(); + + var deserializeAction = (RequestContext package) => + { + var length = package.Reader.ReadInt32(); + var writeTo = ArrayPool.Shared.Rent(length); + for (int i = 0; i < length; i++) + { + var t = pool.Rent(); + t.Deserialize(package.Reader); + writeTo[i] = t; + } + + action(writeTo.AsMemory(0..length), package); + foreach (var item in writeTo) + item.Release(); + + ArrayPool.Shared.Return(writeTo); + }; + registeredStuffDic.Add(id, deserializeAction); + } + + /// + /// + /// + /// + /// + /// 0 when the should be used + public void Register(Action action, ulong id = 0) where T : IConstructionSerializable + { + if (id == 0) + id = typeof(T).SerializationId(); + + var deserializeAction = (RequestContext package) => + { + var t = T.DeserializeAndCreate(package.Reader); + action(t, package); + }; + registeredStuff.Add(id, deserializeAction); + } + + + /// + /// + /// + /// + /// + /// 0 when the should be used + public void RegisterPoolable(Action action, ulong id = 0) where T : IConstructionSerializable, IPoolElement + { + if (id == 0) + id = typeof(T).SerializationId(); + var pool = typeContainer.Get>(); + var deserializeAction = (RequestContext package) => + { + var t = pool.Rent(); + t.Deserialize(package.Reader); + action(t, package); + t.Release(); + }; + registeredStuff.Add(id, deserializeAction); + } + + /// + /// + /// + /// + /// + /// 0 when the should be used + public void RegisterPoolable(Action action, ulong id = 0) where T : IConstructionSerializable, IPoolElement + { + if (id == 0) + id = typeof(T).SerializationId(); + var pool = typeContainer.Get>(); + var deserializeAction = (RequestContext package) => + { + var t = pool.Rent(); + t.Deserialize(package.Reader); + action(t); + t.Release(); + }; + registeredStuff.Add(id, deserializeAction); + } + + public void Dispatch(Package package, BaseClient client) + { + var packageId = package.UId; + logger.Trace($"Entered Dispatch logic for {packageId}"); + using var ms = Serializer.Manager.GetStream(package.Payload); + using Stream s = (package.PackageFlags & PackageFlags.Compressed) > 0 ? new GZipStream(ms, CompressionLevel.Optimal, leaveOpen: true) : ms; + using var br = new BinaryReader(s, System.Text.Encoding.Default, leaveOpen: true); + + bool isNotification = (package.PackageFlags & PackageFlags.Notification) > 0; + + var desId = br.ReadUInt64(); + string channel = ""; + if (isNotification) + channel = br.ReadString(); + var startOfObjectPos = ms.Position; + logger.Trace($"Got {(isNotification ? "Notification" : "Package")} with des id (Mod:{desId>>32}, Type:{desId & 0xFFFFFFF}) {(isNotification ? $"for channel {channel}" : "")} package {packageId}"); + if (channel == "planet") + ; + var rc = new RequestContext(br, package); + Action? val = null; + if ((package.PackageFlags & PackageFlags.Array) > 0 && registeredStuffDic.TryGetValue(desId, out val)) + val.Invoke(rc); + else if ((package.PackageFlags & PackageFlags.Array) == 0 && registeredStuff.TryGetValue(desId, out val)) + val.Invoke(rc); + + logger.Trace($"All invocations succesful, now dispatching to updatehub or sending response to client for {packageId}"); + + if (isNotification) + { + ms.Seek(startOfObjectPos, SeekOrigin.Begin); + + if (!notificationDeserializationMethodCache.TryGetValue(desId, out var expression)) + { + var notificationType = SerializationIdTypeProvider.Get(desId); + if (notificationType.IsAssignableTo(typeof(IPoolElement))) + { + var type = typeof(IPool<>).MakeGenericType(notificationType); + var objectPool = typeContainer.Get(type); + + var pool = GenericCaster.Cast(objectPool); + + notificationDeserializationMethodCache[desId] + = expression + = (BinaryReader reader) => + { + var element = pool.RentElement(); + if (element is ISerializable des) + { + try + { + des.Deserialize(reader); + } + catch (Exception ex) + { + + throw; + } + return des; + } + return default!; + }; + } + else + { + var brParam = Expression.Parameter(typeof(BinaryReader)); + MethodInfo deserializationMethodInfo; + deserializationMethodInfo = notificationType.GetMethod(nameof(IConstructionSerializable.DeserializeAndCreate), BindingFlags.Public | BindingFlags.Static, new[] { typeof(BinaryReader) })!; + notificationDeserializationMethodCache[desId] + = expression + = Expression.Lambda>(Expression.Call(deserializationMethodInfo, brParam), brParam).Compile(); + } + logger.Trace($"Cached expression for {packageId}"); + } + + var notification = expression(br); + updateHub.Push(notification, channel); + + if (notification is IPoolElement poolElement) + poolElement.Release(); + } + else if (val is not null + && (package.PackageFlags & PackageFlags.Response) > 0 + && package.Payload is not null + && package.Payload.Length > 0) + { + + logger.Trace($"Sen: Package with id:{packageId} and Flags: {package.PackageFlags}"); + _ = client.SendPackageAndReleaseAsync(package); + } + + if (val is null && !isNotification) + { + logger.Warn($"Received invalid desId ({desId}) with flags: {package.PackageFlags}, send help"); + } + + logger.Trace($"Finished Dispatch logic for {packageId}"); + } + } + public record struct RequestContext(BinaryReader Reader, Package Package) + { + public void SetResult(T instance) where T : ISerializable + { + using var ms = Serializer.Manager.GetStream(); + using var bw = new BinaryWriter(ms, System.Text.Encoding.Default, leaveOpen: true); + bw.Write(typeof(T).SerializationId()); + instance.Serialize(bw); + Package.Payload = ms.ToArray(); + Package.PackageFlags &= ~(PackageFlags.Array | PackageFlags.Request); + Package.PackageFlags |= PackageFlags.Response; + } + public void SetResult(Span instance) where T : ISerializable + { + using var ms = Serializer.Manager.GetStream(); + using var bw = new BinaryWriter(ms, System.Text.Encoding.Default, leaveOpen: true); + bw.Write(typeof(T).SerializationId()); + bw.Write(instance.Length); + foreach (var item in instance) + item.Serialize(bw); + + Package.Payload = ms.ToArray(); + Package.PackageFlags &= ~PackageFlags.Request; + Package.PackageFlags |= (PackageFlags.Array | PackageFlags.Response); + } + + public void SetResult(byte[] res) + { + Package.Payload = res; + Package.PackageFlags &= ~PackageFlags.Request; + Package.PackageFlags |= PackageFlags.Response; + } + + } + + +} diff --git a/OctoAwesome/OctoAwesome.Network/Pooling/PackagePool.cs b/OctoAwesome/OctoAwesome.Network/Pooling/PackagePool.cs index 9df03405..dc7bbd27 100644 --- a/OctoAwesome/OctoAwesome.Network/Pooling/PackagePool.cs +++ b/OctoAwesome/OctoAwesome.Network/Pooling/PackagePool.cs @@ -39,6 +39,12 @@ public Package Rent() obj.UId = Package.NextUId; return obj; } + + /// + public IPoolElement RentElement() + { + return Rent(); + } /// /// Gets a blank package without any assigned to the package. /// diff --git a/OctoAwesome/OctoAwesome.Network/Request/OfficialCommandDTO.cs b/OctoAwesome/OctoAwesome.Network/Request/OfficialCommandDTO.cs new file mode 100644 index 00000000..a6594dcd --- /dev/null +++ b/OctoAwesome/OctoAwesome.Network/Request/OfficialCommandDTO.cs @@ -0,0 +1,44 @@ +using OctoAwesome.Pooling; +using OctoAwesome.Serialization; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NonSucking.Framework.Serialization; + +namespace OctoAwesome.Network.Request; + +[SerializationId()] +[Nooson] +public partial class OfficialCommandDTO : IPoolElement, IConstructionSerializable +{ + [NoosonCustom(SerializeMethodName = nameof(WriteTypeId), DeserializeMethodName = nameof(ReadTypeId))] + [NoosonOrder(0)] + [NoosonInclude] + private static readonly ulong SerializationId = typeof(OfficialCommandDTO).SerializationId(); + + public OfficialCommand Command { get; set; } + public byte[] Data { get; set; } + + private IPool pool; + + public void Init(IPool pool) + { + this.pool = pool; + } + + public void Release() + { + pool.Return(this); + } + + private static ulong ReadTypeId(BinaryReader _) => 0; + private void WriteTypeId(BinaryWriter writer) + { + if (writer.BaseStream.Position == 0) //required because we sometimes have it already written on the outside + writer.Write(SerializationId); + } +} \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Network/Server.cs b/OctoAwesome/OctoAwesome.Network/Server.cs index 194d9dbb..bb62bc48 100644 --- a/OctoAwesome/OctoAwesome.Network/Server.cs +++ b/OctoAwesome/OctoAwesome.Network/Server.cs @@ -13,12 +13,19 @@ namespace OctoAwesome.Network public class Server //TODO: Should use a base class or interface { /// - /// Called when a new client has connected to the server. + /// Called when a new client has connected to the server. Packages could arrive before this event. /// public event EventHandler? OnClientConnected; + /// + /// Called when a new client started the connection procedure. + /// + public event EventHandler? OnClientConnecting; + /// + /// Called when a client has been disconnected from the server. + /// + public event EventHandler? OnClientDisconnected; - private readonly Socket ipv4Socket; - private readonly Socket ipv6Socket; + private TcpListener tcpListener = default!; private readonly List connectedClients; private readonly object lockObj; @@ -27,8 +34,7 @@ public class Server //TODO: Should use a base class or interface /// public Server() { - ipv4Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - ipv6Socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); + connectedClients = new List(); lockObj = new object(); @@ -42,23 +48,15 @@ public void Start(params IPEndPoint[] endpoints) { connectedClients.Clear(); - if (endpoints.Any(x => x.AddressFamily == AddressFamily.InterNetwork)) - { - foreach (var endpoint in endpoints) - if (endpoint.AddressFamily == AddressFamily.InterNetwork) - ipv4Socket.Bind(endpoint); + tcpListener = new TcpListener(endpoints.First()); + tcpListener.Server.DualMode = true; + tcpListener.Server.NoDelay = true; - ipv4Socket.Listen(1024); - ipv4Socket.BeginAccept(OnClientAccepted, ipv4Socket); - } - if (endpoints.Any(x => x.AddressFamily == AddressFamily.InterNetworkV6)) - { - foreach (var endpoint in endpoints.Where(e => e.AddressFamily == AddressFamily.InterNetworkV6)) - ipv6Socket.Bind(endpoint); + foreach (var endpoint in endpoints.Skip(1)) + tcpListener.Server.Bind(endpoint); + tcpListener.Start(); + tcpListener.BeginAcceptTcpClient(OnClientAccepted, tcpListener); - ipv6Socket.Listen(1024); - ipv6Socket.BeginAccept(OnClientAccepted, ipv6Socket); - } } /// /// Starts listening on the specified host and port. @@ -68,29 +66,60 @@ public void Start(params IPEndPoint[] endpoints) public void Start(string host, ushort port) { var address = Dns.GetHostAddresses(host).Where( - a => a.AddressFamily == ipv4Socket.AddressFamily || a.AddressFamily == ipv6Socket.AddressFamily); + a => a.AddressFamily == AddressFamily.InterNetwork || a.AddressFamily == AddressFamily.InterNetworkV6); Start(address.Select(a => new IPEndPoint(a, port)).ToArray()); } + public void Stop() + { + foreach (var item in connectedClients) + { + item.Stop(); + } + tcpListener.Stop(); + connectedClients.Clear(); + } + private void OnClientAccepted(IAsyncResult ar) { Debug.Assert(ar.AsyncState != null, "ar.AsyncState != null"); - var socket = (Socket)ar.AsyncState; - - var tmpSocket = socket.EndAccept(ar); + var listener = (TcpListener)ar.AsyncState; - tmpSocket.NoDelay = true; + try + { + var tmpClient = listener.EndAcceptTcpClient(ar); + tmpClient.NoDelay = true; - var client = new ConnectedClient(tmpSocket); - client.Start(); + var client = new ConnectedClient(tmpClient); + client.ClientDisconnected += Client_ClientDisconnected; + OnClientConnecting?.Invoke(this, client); + client.Start(); + OnClientConnected?.Invoke(this, client); - OnClientConnected?.Invoke(this, client); + lock (lockObj) + connectedClients.Add(client); + } + catch (Exception) + { + if (listener.Server.IsBound) + listener.BeginAcceptTcpClient(OnClientAccepted, listener); + return; + } - lock (lockObj) - connectedClients.Add(client); + listener.BeginAcceptTcpClient(OnClientAccepted, listener); + } - socket.BeginAccept(OnClientAccepted, socket); + private void Client_ClientDisconnected(object? sender, EventArgs e) + { + if (sender is ConnectedClient cc) + { + OnClientDisconnected?.Invoke(this, cc); + connectedClients.Remove(cc); + cc.Dispose(); + //Socket.Select() + } } + } } diff --git a/OctoAwesome/OctoAwesome.Network/ServerHandler.cs b/OctoAwesome/OctoAwesome.Network/ServerHandler.cs new file mode 100644 index 00000000..ba99086d --- /dev/null +++ b/OctoAwesome/OctoAwesome.Network/ServerHandler.cs @@ -0,0 +1,192 @@ +using OctoAwesome.Logging; +using OctoAwesome.Network; +using OctoAwesome.Notifications; +using System.Net; +using OctoAwesome.Rx; +using System.Collections.Generic; +using System.Collections.Concurrent; +using OctoAwesome.Network.Commands; +using System.Linq; +using OctoAwesome.Pooling; +using OctoAwesome.Network.Request; +using System.Xml; +using OctoAwesome.Database; +using System; +using OctoAwesome.Serialization; +using dotVariant; +using OctoAwesome.Runtime; +using OctoAwesome.Chunking; + +namespace OctoAwesome.Network +{ + [Variant] + public partial class Invocation + { + static partial void VariantOf( + Func WithReturn, + Action VoidReturn); + } + + + /// + /// Handler for server connection and simulation. + /// + public class ServerHandler + { + /// + /// Gets the simulation manager. + /// + public SimulationManager SimulationManager { get; } + + /// + /// Gets the update hub. + /// + public IUpdateHub UpdateHub { get; } + + private readonly ILogger logger; + private readonly Server server; + private readonly PackageActionHub packageActionHub; + public readonly ConcurrentDictionary CommandFunctions; + private readonly ITypeContainer typeContainer; + private readonly NotificationCommands notCommands; + + /// + /// Initializes a new instance of the class. + /// + public ServerHandler(ITypeContainer typeContainer) + { + logger = (typeContainer.GetOrNull() ?? NullLogger.Default).As(typeof(ServerHandler)); + + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + + SimulationManager = typeContainer.Get(); + UpdateHub = typeContainer.Get(); + server = typeContainer.Get(); + packageActionHub = new PackageActionHub(logger, typeContainer); + typeContainer.Register(packageActionHub); + var pool = new Pool(); + typeContainer.Register(pool); + typeContainer.Register>(pool); + + CommandFunctions = new(); + + Register(OfficialCommand.Whoami, PlayerCommands.Whoami); + Register(OfficialCommand.GetUniverse, GeneralCommands.GetUniverse); + Register(OfficialCommand.GetPlanet, GeneralCommands.GetPlanet); + Register(OfficialCommand.SaveColumn, ChunkCommands.SaveColumn); + Register(OfficialCommand.LoadColumn, ChunkCommands.LoadColumn); + + packageActionHub.RegisterPoolable((OfficialCommandDTO req, RequestContext cont) => + { + logger.Debug($"Got Official command with id {req.Command}"); + var invocation = CommandFunctions[req.Command]; + var commandParameter = new CommandParameter(cont.Package.BaseClient.Id, req.Data); + var res = invocation.Visit( + with => with(commandParameter), + @void => + { + @void(commandParameter); + return null; + }); + + if (res is not null) + { + req.Data = Serializer.Serialize(res); + cont.SetResult(req); + } + }); + packageActionHub.Register((RangeRequest range, RequestContext cont) => + { + range.Response = IdRangeProvider.Provide(); + cont.SetResult(range); + }); + this.typeContainer = typeContainer; + + notCommands = new NotificationCommands(); //TODO Should not be needed, when a better structure is in place for these + } + + private void Register(OfficialCommand command, Func func) + where T : IConstructionSerializable + { + Invocation deserializeAction = new((CommandParameter parameter) => + { + var t = Serializer.DeserializeSpecialCtor(parameter.Data); + return func(typeContainer, parameter, t); + }); + CommandFunctions.TryAdd(command, deserializeAction); + } + private void Register(OfficialCommand command, Func func) + { + Invocation deserializeAction = new((CommandParameter parameter) => + { + return func(typeContainer, parameter); + }); + CommandFunctions.TryAdd(command, deserializeAction); + } + private void Register(OfficialCommand command, Action func) + where T : IConstructionSerializable + { + Invocation deserializeAction = new((CommandParameter parameter) => + { + var t = Serializer.DeserializeSpecialCtor(parameter.Data); + func(typeContainer, parameter, t); + }); + CommandFunctions.TryAdd(command, deserializeAction); + } + private void Register(OfficialCommand command, Action func) + { + Invocation deserializeAction = new((CommandParameter parameter) => + { + func(typeContainer, parameter); + }); + CommandFunctions.TryAdd(command, deserializeAction); + } + + /// + /// Start the game server simulation and connection. + /// + public void Start(ushort port) + { + SimulationManager.Start(); //Temp + server.Start(new IPEndPoint(IPAddress.IPv6Any, port)); + server.OnClientConnecting += ServerOnClientConnecting; + server.OnClientDisconnected += ServerOnClientDisconnected; + } + + /// + /// Start the game server simulation and connection. + /// + public void Stop() + { + SimulationManager.Stop(); //Temp + server.Stop(); + server.OnClientConnected -= ServerOnClientConnecting; + } + + private void ServerOnClientConnecting(object? sender, ConnectedClient e) + { + logger.Debug("Hurra ein neuer Spieler"); + e.ServerSubscription = e.Packages.Subscribe(OnNext, ex => logger.Error(ex.Message, ex)); + } + + private void ServerOnClientDisconnected(object? sender, ConnectedClient e) + { + logger.Debug("Ciao Spieler"); + e.ServerSubscription?.Dispose(); + } + + /// + /// Gets called when a new package is received. + /// + /// The received package. + public void OnNext(Package package) + { + logger.Trace($"Rec: Package with id:{package.UId} and Flags: {package.PackageFlags}"); + packageActionHub.Dispatch(package, package.BaseClient); + } + } + +} diff --git a/OctoAwesome/OctoAwesome.Network/SimulationManager.cs b/OctoAwesome/OctoAwesome.Network/SimulationManager.cs index 6f6a87fa..ab8e2d58 100644 --- a/OctoAwesome/OctoAwesome.Network/SimulationManager.cs +++ b/OctoAwesome/OctoAwesome.Network/SimulationManager.cs @@ -10,23 +10,24 @@ using OctoAwesome.Runtime; using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace OctoAwesome.Network { /// - /// Manager for an OctoAwesome game simulation. + /// Manager for an OctoAwesome game simulationRelay. /// public class SimulationManager { /// - /// Gets a value indicating whether the simulation is currently running. + /// Gets a value indicating whether the simulationRelay is currently running. /// public bool IsRunning { get; private set; } /// - /// Gets the simulation. + /// Gets the simulationRelay. /// public Simulation Simulation => simulation; @@ -40,10 +41,6 @@ public class SimulationManager /// public ResourceManager ResourceManager { get; } - /// - /// Gets the game service. - /// - public GameService Service { get; } private Simulation simulation; @@ -53,11 +50,12 @@ public class SimulationManager private readonly ISettings settings; private readonly UpdateHub updateHub; private readonly object mainLock; + private readonly Stopwatch watchUpdate = new Stopwatch(); /// /// Initializes a new instance of the class. /// - /// The game settings for the simulation. + /// The game settings for the simulationRelay. /// The update hub. public SimulationManager(ISettings settings, UpdateHub updateHub) { @@ -68,28 +66,29 @@ public SimulationManager(ISettings settings, UpdateHub updateHub) var typeContainer = TypeContainer.Get(); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); var extensionLoader = typeContainer.Get(); extensionLoader.LoadExtensions(); + extensionLoader.RegisterExtensions(); + extensionLoader.InstantiateExtensions(); var extensionService = typeContainer.Get(); ResourceManager = typeContainer.Get(); + ResourceManager.PersistenceManager = typeContainer.Get(); + ResourceManager.IdManager = new LocalIdManager(); - Service = typeContainer.Get(); - simulation = new Simulation(ResourceManager, extensionService, Service) + simulation = new Simulation(ResourceManager, extensionService) { IsServerSide = true }; @@ -100,11 +99,12 @@ public SimulationManager(ISettings settings, UpdateHub updateHub) } /// - /// Start the game simulation. + /// Start the game simulationRelay. /// public void Start() { IsRunning = true; + watchUpdate.Restart(); GameTime = new GameTime(); //TODO: Load and Save logic for Server (Multiple games etc.....) @@ -112,14 +112,16 @@ public void Start() if (string.IsNullOrWhiteSpace(universe)) { - var guid = Simulation.NewGame("melmack", new Random().Next().ToString()); + var guid = Simulation.NewGame(ResourceManager, "melmack", new Random().Next().ToString()); + simulation.TryLoadGame(guid); settings.Set("LastUniverse", guid.ToString()); } else { if (!Simulation.TryLoadGame(new Guid(universe))) { - var guid = Simulation.NewGame("melmack", new Random().Next().ToString()); + var guid = Simulation.NewGame(ResourceManager, "melmack", new Random().Next().ToString()); + simulation.TryLoadGame(guid); settings.Set("LastUniverse", guid.ToString()); } } @@ -128,7 +130,7 @@ public void Start() } /// - /// Stop the game simulation. + /// Stop the game simulationRelay. /// public void Stop() { @@ -136,6 +138,7 @@ public void Stop() Simulation.ExitGame(); cancellationTokenSource.Cancel(); cancellationTokenSource.Dispose(); + watchUpdate.Stop(); } /// @@ -163,7 +166,7 @@ public IUniverse NewUniverse() public IPlanet GetPlanet(int planetId) => ResourceManager.GetPlanet(planetId); /// - /// Loads a chunk column at a given location for a specified planet. + /// Loads a chunkChannel column at a given location for a specified planet. /// /// The planet to load the chunk column from. /// The location to load the chunk column at. @@ -173,7 +176,7 @@ public IChunkColumn LoadColumn(IPlanet planet, Index2 index2) => planet.GlobalChunkCache.Subscribe(index2); /// - /// Loads a chunk column at a given location for a specified planet. + /// Loads a chunkChannel column at a given location for a specified planet. /// /// The id of the planet to load the chunk column from. /// The location to load the chunk column at. @@ -186,10 +189,15 @@ private void SimulationLoop(object? state) { var token = state is CancellationToken stateToken ? stateToken : CancellationToken.None; + TimeSpan lastUpdate = watchUpdate.Elapsed; while (true) { + token.ThrowIfCancellationRequested(); - Simulation.Update(GameTime); + var total = watchUpdate.Elapsed; + Simulation.Update(new GameTime(total, total - lastUpdate)); + lastUpdate = total; + } } } diff --git a/OctoAwesome/OctoAwesome.Network/Startup.cs b/OctoAwesome/OctoAwesome.Network/Startup.cs index ed77f416..a8496171 100644 --- a/OctoAwesome/OctoAwesome.Network/Startup.cs +++ b/OctoAwesome/OctoAwesome.Network/Startup.cs @@ -13,7 +13,7 @@ public static class Startup /// The type container to register on. public static void Register(ITypeContainer typeContainer) { - typeContainer.Register(InstanceBehavior.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); } } } diff --git a/OctoAwesome/OctoAwesome.PoC/Progam.cs b/OctoAwesome/OctoAwesome.PoC/Progam.cs index f41221c0..9391ec3d 100644 --- a/OctoAwesome/OctoAwesome.PoC/Progam.cs +++ b/OctoAwesome/OctoAwesome.PoC/Progam.cs @@ -1,5 +1,7 @@ using engenious; +using NonSucking.Framework.Extension.IoC; + using OctoAwesome.Basics; using OctoAwesome.Basics.Definitions.Items.Food; using OctoAwesome.Definitions; diff --git a/OctoAwesome/OctoAwesome.Runtime/AssemblyInfo.cs b/OctoAwesome/OctoAwesome.Runtime/AssemblyInfo.cs new file mode 100644 index 00000000..d1f90a64 --- /dev/null +++ b/OctoAwesome/OctoAwesome.Runtime/AssemblyInfo.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using NonSucking.Framework.Serialization; + +using OctoAwesome; +using OctoAwesome.Runtime; + +[assembly: NoosonConfiguration( + GenerateDeserializeExtension = false, + DisableWarnings = true, + GenerateStaticDeserializeWithCtor = true, + GenerateDeserializeOnInstance = true, + GenerateStaticSerialize = true, + GenerateStaticDeserializeIntoInstance = true, + NameOfStaticDeserializeWithCtor = "DeserializeAndCreate", + NameOfDeserializeOnInstance = "Deserialize", + NameOfStaticDeserializeIntoInstance = "Deserialize", + NameOfStaticDeserializeWithOutParams = "DeserializeOut")] + +[assembly: SerializationId(4, 1)] \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Runtime/DiskPersistenceManager.cs b/OctoAwesome/OctoAwesome.Runtime/DiskPersistenceManager.cs index a220f28c..c2b8b034 100644 --- a/OctoAwesome/OctoAwesome.Runtime/DiskPersistenceManager.cs +++ b/OctoAwesome/OctoAwesome.Runtime/DiskPersistenceManager.cs @@ -66,14 +66,16 @@ private string GetRoot() if (!string.IsNullOrEmpty(appConfig)) { root = new DirectoryInfo(appConfig); - if (!root.Exists) root.Create(); + if (!root.Exists) + root.Create(); return root.FullName; } else { var exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); root = new DirectoryInfo(exePath + Path.DirectorySeparatorChar + "OctoMap"); - if (!root.Exists) root.Create(); + if (!root.Exists) + root.Create(); return root.FullName; } } @@ -135,8 +137,7 @@ public void SavePlayer(Guid universeGuid, Player player) string path = Path.Combine(GetRoot(), universeGuid.ToString()); Directory.CreateDirectory(path); - // TODO: consider player name - string file = Path.Combine(path, "player.info"); + string file = Path.Combine(path, $"player_{player.Name}.info"); using (Stream stream = File.Open(file, FileMode.Create, FileAccess.Write)) { using (BinaryWriter writer = new BinaryWriter(stream)) @@ -162,7 +163,6 @@ public Awaiter Load(out SerializableCollection universes) string root = GetRoot(); var awaiter = awaiterPool.Rent(); universes = new SerializableCollection(); - awaiter.Result = universes; foreach (var folder in Directory.GetDirectories(root)) { string id = Path.GetFileNameWithoutExtension(folder);//folder.Replace(root + "\\", ""); @@ -174,7 +174,7 @@ public Awaiter Load(out SerializableCollection universes) continue; } - universeLoader.WaitOnAndRelease(); + universeLoader.WaitOnAndRelease(universe); universes.Add(universe); } } @@ -285,8 +285,7 @@ public Awaiter Load(out TContainer componentContainer, G /// public Awaiter? Load(out Player player, Guid universeGuid, string playerName) { - //TODO: Replace with player name later on. - string file = Path.Combine(GetRoot(), universeGuid.ToString(), "player.info"); + string file = Path.Combine(GetRoot(), universeGuid.ToString(), $"player_{playerName}.info"); player = new Player(); if (!File.Exists(file)) return null; @@ -298,13 +297,13 @@ public Awaiter Load(out TContainer componentContainer, G try { var awaiter = awaiterPool.Rent(); - awaiter.Result = player; player.Deserialize(reader); awaiter.SetResult(player); return awaiter; } catch (Exception) { + //TODO Should we delete the File if the data is invalid? // File.Delete(file); } } @@ -357,12 +356,35 @@ public void Dispose() /// Gets called when a new notification is received. /// /// The received notification. - public void OnNext(Notification notification) + public void OnNext(object notification) { - if (notification is BlockChangedNotification blockChanged) - SaveChunk(blockChanged); - else if (notification is BlocksChangedNotification blocksChanged) - SaveChunk(blocksChanged); + switch (notification) + { + case BlockChangedNotification blockChanged: + SaveChunk(blockChanged); + break; + case BlocksChangedNotification blocksChanged: + SaveChunk(blocksChanged); + break; + } + } + + public void SaveGlobally(T tag, ISerializable value, bool fixedSize) where T : ITag, new() + { + var provider = databaseProvider.GetDatabase( fixedSize); + provider.AddOrUpdate(tag, new Value(Serializer.Serialize(value))); + } + + public TSerializeable? LoadGlobally(TTag tag, bool fixedSize) + where TTag : ITag, new() + where TSerializeable : ISerializable, new() + { + var provider = databaseProvider.GetDatabase(fixedSize); + if (!provider.ContainsKey(tag)) + return default; + + var value = provider.GetValue(tag); + return Serializer.Deserialize(value.Content); } private void SaveChunk(BlockChangedNotification chunkNotification) diff --git a/OctoAwesome/OctoAwesome.Runtime/Extension.cs b/OctoAwesome/OctoAwesome.Runtime/Extension.cs new file mode 100644 index 00000000..52763379 --- /dev/null +++ b/OctoAwesome/OctoAwesome.Runtime/Extension.cs @@ -0,0 +1,43 @@ +using engenious; + +using OctoAwesome.EntityComponents; +using OctoAwesome.Extension; +using OctoAwesome.Serialization; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OctoAwesome.Runtime +{ + /// + /// The base extension implementation. + /// + internal sealed class RuntimeExtension : IExtension + { + /// + public string Description => "OctoAwesome Runtime"; + + /// + public string Name => "OctoAwesome.Runtime"; + + /// + public void Register(OctoAwesome.Extension.ExtensionService extensionLoader) + { + + } + + /// + public void Register(ITypeContainer typeContainer) + { + } + + /// + public void RegisterTypes(ExtensionService extensionLoader) + { + extensionLoader.RegisterTypesWithSerializationId(typeof(RuntimeExtension).Assembly); + } + } +} diff --git a/OctoAwesome/OctoAwesome.Runtime/ExtensionLoader.cs b/OctoAwesome/OctoAwesome.Runtime/ExtensionLoader.cs index 09ba7bf4..b97f8325 100644 --- a/OctoAwesome/OctoAwesome.Runtime/ExtensionLoader.cs +++ b/OctoAwesome/OctoAwesome.Runtime/ExtensionLoader.cs @@ -32,6 +32,7 @@ public sealed class ExtensionLoader private readonly ISettings settings; private readonly ExtensionService extensionService; private readonly ITypeContainer typeContainer; + private List assemblies; /// /// Initializes a new instance of the class. @@ -54,7 +55,7 @@ public ExtensionLoader(ExtensionService extensionService, ITypeContainer typeCon /// public void LoadExtensions() { - List assemblies = new(); + assemblies = new List(); var tempAssembly = Assembly.GetEntryAssembly(); if (tempAssembly == null) @@ -67,9 +68,6 @@ public void LoadExtensions() if (plugins.Exists) assemblies.AddRange(LoadAssemblies(plugins)); - var disabledExtensions = settings.KeyExists(SETTINGSKEY) - ? settings.GetArray(SETTINGSKEY) - : Array.Empty(); foreach (var assembly in assemblies) { @@ -102,6 +100,14 @@ public void LoadExtensions() } } + } + + public void RegisterExtensions() + { + + var disabledExtensions = settings.KeyExists(SETTINGSKEY) + ? settings.GetArray(SETTINGSKEY) + : Array.Empty(); foreach (var assembly in assemblies) { var types = assembly @@ -116,25 +122,46 @@ public void LoadExtensions() IExtension extension = (IExtension)typeContainer.GetUnregistered(type); extension.Register(typeContainer); - extension.Register(extensionService); + extension.RegisterTypes(extensionService); if (disabledExtensions.Contains(type.FullName)) LoadedExtensions.Add(extension); else ActiveExtensions.Add(extension); - var extensionInformation = new ExtensionInformation(extension); - extensionService.AddExtensionLoader(extensionInformation); } catch { // TODO: Logging + ; } } } } } + /// + /// Instantiated all loaded Plugins, except for those which are disabled. + /// + public void InstantiateExtensions() + { + foreach (var extension in ActiveExtensions) + { + try + { + extension.Register(extensionService); + + var extensionInformation = new ExtensionInformation(extension); + extensionService.AddExtensionLoader(extensionInformation); + } + catch + { + // TODO: Logging + ; + } + } + } + /// /// Activate the Extenisons /// diff --git a/OctoAwesome/OctoAwesome.Runtime/GameService.cs b/OctoAwesome/OctoAwesome.Runtime/GameService.cs deleted file mode 100644 index a3348005..00000000 --- a/OctoAwesome/OctoAwesome.Runtime/GameService.cs +++ /dev/null @@ -1,151 +0,0 @@ -using engenious; - -using OctoAwesome.Chunking; -using OctoAwesome.Common; -using OctoAwesome.Definitions; -using OctoAwesome.Location; - -using System; -using System.Diagnostics; - -namespace OctoAwesome.Runtime -{ - // sealed -> prevent abuse of third party´s - // TODO: These calculations should not be left to the extensions. - /// - /// Game service for common game functions. - /// - public sealed class GameService : IGameService - { - /// - public IDefinitionManager DefinitionManager => manager.DefinitionManager; - /// - /// GAP. - /// - public const float GAP = 0.01f; - private readonly IResourceManager manager; - /// - /// Initializes a new instance of the class. - /// - /// ResourceManger - public GameService(IResourceManager resourceManager) - { - manager = resourceManager; - } - /// - /// Creates a . - /// - /// A value indicating whether the local chunk cache should be passive. - /// Dimensions of the local chunk cache in dualistic logarithmic scale. - /// The range of the chunk cache in all axis directions. - /// The created local chunk cache. - public ILocalChunkCache GetLocalCache(bool passive, int dimensions, int range) - { - //new LocalChunkCache(manager.GlobalChunkCache, false, 2, 1); - throw new NotImplementedException(); - } - - /// - public Vector3 WorldCollision(GameTime gameTime, Coordinate position, ILocalChunkCache cache, float radius, float height, - Vector3 deltaPosition, Vector3 velocity) - { - Debug.Assert(cache != null, nameof(cache) + " != null"); - - Vector3 move = deltaPosition; - - // Find blocks which could cause a collision - int minx = (int)Math.Floor(Math.Min( - position.BlockPosition.X - radius, - position.BlockPosition.X - radius + deltaPosition.X)); - int maxx = (int)Math.Ceiling(Math.Max( - position.BlockPosition.X + radius, - position.BlockPosition.X + radius + deltaPosition.X)); - int miny = (int)Math.Floor(Math.Min( - position.BlockPosition.Y - radius, - position.BlockPosition.Y - radius + deltaPosition.Y)); - int maxy = (int)Math.Ceiling(Math.Max( - position.BlockPosition.Y + radius, - position.BlockPosition.Y + radius + deltaPosition.Y)); - int minz = (int)Math.Floor(Math.Min( - position.BlockPosition.Z, - position.BlockPosition.Z + deltaPosition.Z)); - int maxz = (int)Math.Ceiling(Math.Max( - position.BlockPosition.Z + height, - position.BlockPosition.Z + height + deltaPosition.Z)); - - // Collision planes of the entity - var playerplanes = CollisionPlane.GetEntityCollisionPlanes(radius, height, velocity, position); - - for (int z = minz; z <= maxz; z++) - { - for (int y = miny; y <= maxy; y++) - { - for (int x = minx; x <= maxx; x++) - { - move = velocity * (float)gameTime.ElapsedGameTime.TotalSeconds; - - Index3 pos = new Index3(x, y, z); - Index3 blockPos = pos + position.GlobalBlockIndex; - ushort block = cache.GetBlock(blockPos); - - if (block == 0) - continue; - - var blockplanes = CollisionPlane.GetBlockCollisionPlanes(pos, velocity); - - foreach (var playerPlane in playerplanes) - { - foreach (var blockPlane in blockplanes) - { - if (!CollisionPlane.Intersect(blockPlane, playerPlane)) - continue; - - var distance = CollisionPlane.GetDistance(blockPlane, playerPlane); - if (!CollisionPlane.CheckDistance(distance, move)) - continue; - - var subvelocity = (distance / (float)gameTime.ElapsedGameTime.TotalSeconds); - var diff = velocity - subvelocity; - - float vx; - float vy; - float vz; - - if (blockPlane.normal.X != 0 && (velocity.X > 0 && diff.X >= 0 && subvelocity.X >= 0 || - velocity.X < 0 && diff.X <= 0 && subvelocity.X <= 0)) - vx = subvelocity.X; - else - vx = velocity.X; - - if (blockPlane.normal.Y != 0 && (velocity.Y > 0 && diff.Y >= 0 && subvelocity.Y >= 0 || - velocity.Y < 0 && diff.Y <= 0 && subvelocity.Y <= 0)) - vy = subvelocity.Y; - else - vy = velocity.Y; - - if (blockPlane.normal.Z != 0 && (velocity.Z > 0 && diff.Z >= 0 && subvelocity.Z >= 0 || - velocity.Z < 0 && diff.Z <= 0 && subvelocity.Z <= 0)) - vz = subvelocity.Z; - else - vz = velocity.Z; - - velocity = new Vector3(vx, vy, vz); - if (vx == 0 && vy == 0 && vz == 0) - { - return velocity; - } - } - } - } - } - } - return velocity; - } - /// - /// Retrieves services by type. - /// - /// The type of the service to get. - /// The retrieved service. - public object GetService(Type serviceType) => throw new NotImplementedException(); - } -} diff --git a/OctoAwesome/OctoAwesome.Runtime/IdRangeProvider.cs b/OctoAwesome/OctoAwesome.Runtime/IdRangeProvider.cs new file mode 100644 index 00000000..5b91e5c8 --- /dev/null +++ b/OctoAwesome/OctoAwesome.Runtime/IdRangeProvider.cs @@ -0,0 +1,75 @@ +using OctoAwesome.Database; +using OctoAwesome.Serialization; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OctoAwesome.Runtime; + +[Nooson, SerializationId()] +public partial class RangeRequest : IConstructionSerializable +{ + public Range Response { get; set; } + public bool FirstIds { get; set; } + +} + +public class LocalIdManager : IIdManager +{ + private Range range; + private int lastId = 0; + public int GetNextId() + { + if (range.End.Value <= lastId) + { + range = IdRangeProvider.Provide(); + } + return lastId++; + } + + public void Init() + { + range = IdRangeProvider.Provide(); + } +} + +[Nooson] +internal partial class SerRange : ISerializable +{ + public int Last { get; set; } + public SerRange() + { + } + public SerRange(int last) + { + Last = last; + } +} +public static class IdRangeProvider +{ + private static int lastGiven = 0; + private static DiskPersistenceManager dpm; + private static readonly int rangeRange = 100; + + public static Range Provide() + { + if (lastGiven == 0) + Init(); + var range = lastGiven..(lastGiven + rangeRange); + lastGiven += rangeRange; + dpm.SaveGlobally(new IdTag(), new SerRange(lastGiven), true); + return range; + } + + private static void Init() + { + dpm = TypeContainer.Get(); + var loaded = dpm.LoadGlobally, SerRange>(new IdTag(), true); + lastGiven = loaded?.Last ?? 0; + + } +} \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Runtime/OctoAwesome.Runtime.csproj b/OctoAwesome/OctoAwesome.Runtime/OctoAwesome.Runtime.csproj index 83687972..f160cdb9 100644 --- a/OctoAwesome/OctoAwesome.Runtime/OctoAwesome.Runtime.csproj +++ b/OctoAwesome/OctoAwesome.Runtime/OctoAwesome.Runtime.csproj @@ -3,18 +3,23 @@ net7.0 enable True + - - full - true - - - pdbonly - - + + + + + + + + + + + + \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome.Runtime/ResourceManager.cs b/OctoAwesome/OctoAwesome.Runtime/ResourceManager.cs index 8744142f..de09a820 100644 --- a/OctoAwesome/OctoAwesome.Runtime/ResourceManager.cs +++ b/OctoAwesome/OctoAwesome.Runtime/ResourceManager.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.Linq; using System.Threading; -using System.Threading.Tasks; using OctoAwesome.Caching; using OctoAwesome.Chunking; @@ -16,6 +15,7 @@ using OctoAwesome.Notifications; using OctoAwesome.Serialization; using OctoAwesome.Threading; +using OctoAwesome.Rx; namespace OctoAwesome.Runtime { @@ -30,12 +30,10 @@ public class ResourceManager : IResourceManager /// public IUpdateHub UpdateHub { get; } - private readonly bool disablePersistence; - private readonly IPersistenceManager persistenceManager; - private readonly ILogger logger; - private readonly List populators; - private Player? player; - private readonly LockSemaphore semaphoreSlim; + private readonly ISettings settings; + + /// + public IPersistenceManager PersistenceManager { get; set; } /// /// Gets the currently loaded universe. @@ -47,7 +45,17 @@ public class ResourceManager : IResourceManager /// public ConcurrentDictionary Planets { get; } + /// + public bool LocalPersistance => PersistenceManager is DiskPersistenceManager; + /// + public IIdManager IdManager { get; set; } + + private readonly bool disablePersistence; + private readonly ILogger logger; + private readonly List populators; + private Player? player; + private readonly LockSemaphore semaphoreSlim; private readonly Extension.ExtensionService extensionService; private readonly CountedScopeSemaphore loadingSemaphore; private CancellationToken currentToken; @@ -58,16 +66,14 @@ public class ResourceManager : IResourceManager /// /// The extension service. /// The definition manager. - /// The persistence manager. /// The update hub to use for update notifications. /// The game settings. - public ResourceManager(Extension.ExtensionService extensionService, IDefinitionManager definitionManager, ISettings settings, IPersistenceManager persistenceManager, IUpdateHub updateHub) + public ResourceManager(Extension.ExtensionService extensionService, IDefinitionManager definitionManager, ISettings settings, IUpdateHub updateHub) { semaphoreSlim = new LockSemaphore(1, 1); loadingSemaphore = new CountedScopeSemaphore(); this.extensionService = extensionService; DefinitionManager = definitionManager; - this.persistenceManager = persistenceManager; logger = (TypeContainer.GetOrNull() ?? NullLogger.Default).As(typeof(ResourceManager)); @@ -75,8 +81,17 @@ public ResourceManager(Extension.ExtensionService extensionService, IDefinitionM Planets = new ConcurrentDictionary(); UpdateHub = updateHub; - + this.settings = settings; bool.TryParse(settings.Get("DisablePersistence"), out disablePersistence); + + updateHub.ListenOn(DefaultChannels.Planet).Subscribe(OnNewPlanet); + } + + private void OnNewPlanet(object obj) + { + if (obj is not IPlanet planet) + return; + Planets[planet.Id] = planet; } @@ -94,7 +109,9 @@ public Guid NewUniverse(string name, int seed) Guid guid = Guid.NewGuid(); CurrentUniverse = new Universe(guid, name, seed); - persistenceManager.SaveUniverse(CurrentUniverse); + PersistenceManager ??= new DiskPersistenceManager(extensionService, settings, UpdateHub); + PersistenceManager.SaveUniverse(CurrentUniverse); + return guid; } } @@ -102,12 +119,10 @@ public Guid NewUniverse(string name, int seed) /// public IUniverse[] ListUniverses() { - var awaiter = persistenceManager.Load(out SerializableCollection universes); + var dpm = new DiskPersistenceManager(extensionService, settings, UpdateHub); + var awaiter = dpm.Load(out SerializableCollection universes); - if (awaiter == null) - return Array.Empty(); - else - awaiter.WaitOnAndRelease(); + awaiter.WaitOnAndRelease>(); return universes.ToArray(); } @@ -126,14 +141,13 @@ public bool TryLoadUniverse(Guid universeId) currentToken = tokenSource.Token; // Load/Generate new universe data - var awaiter = persistenceManager.Load(out IUniverse universe, universeId); + var awaiter = PersistenceManager.Load(out _, universeId); if (awaiter == null) return false; - else - awaiter.WaitOnAndRelease(); - CurrentUniverse = universe; + CurrentUniverse = awaiter.WaitOnAndRelease(); + if (CurrentUniverse == null) throw new NullReferenceException(); @@ -152,14 +166,14 @@ public void UnloadUniverse() if (CurrentUniverse == null) return; - persistenceManager.SaveUniverse(CurrentUniverse); + PersistenceManager.SaveUniverse(CurrentUniverse); foreach (var planet in Planets) { - persistenceManager.SavePlanet(CurrentUniverse.Id, planet.Value); + PersistenceManager.SavePlanet(CurrentUniverse.Id, planet.Value); planet.Value.Dispose(); } - // if (persistenceManager is IDisposable disposable) + // if (PersistenceManager is IDisposable disposable) // disposable.Dispose(); Planets.Clear(); @@ -174,7 +188,7 @@ public void DeleteUniverse(Guid id) if (CurrentUniverse != null && CurrentUniverse.Id == id) throw new Exception("Universe is already loaded"); - persistenceManager.DeleteUniverse(id); + PersistenceManager.DeleteUniverse(id); } /// @@ -192,7 +206,7 @@ public IPlanet GetPlanet(int id) if (!Planets.TryGetValue(id, out var planet)) { // Try loading already existing planet - var awaiter = persistenceManager.Load(out planet, CurrentUniverse.Id, id); + var awaiter = PersistenceManager.Load(out _, CurrentUniverse.Id, id); if (awaiter == null) { @@ -202,11 +216,11 @@ public IPlanet GetPlanet(int id) int index = rand.Next(generators.Length - 1); IMapGenerator generator = generators[index]; planet = generator.GeneratePlanet(CurrentUniverse.Id, id, CurrentUniverse.Seed + id); - // persistenceManager.SavePlanet(universe.Id, planet); + // PersistenceManager.SavePlanet(universe.Id, planet); } else { - awaiter.WaitOnAndRelease(); + planet = awaiter.WaitOnAndRelease(planet); Debug.Assert(planet != null, nameof(planet) + " != null"); } @@ -225,11 +239,9 @@ public Player LoadPlayer(string playerName) using (loadingSemaphore.EnterCountScope()) { currentToken.ThrowIfCancellationRequested(); - var awaiter = persistenceManager.Load(out var player, CurrentUniverse.Id, playerName); - - awaiter?.WaitOnAndRelease(); + var awaiter = PersistenceManager.Load(out _, CurrentUniverse.Id, playerName); - return player; + return awaiter?.WaitOnAndRelease() ?? new Player(); } } @@ -240,7 +252,7 @@ public void SavePlayer(Player player) throw new Exception("No Universe loaded"); using (loadingSemaphore.EnterCountScope()) - persistenceManager.SavePlayer(CurrentUniverse.Id, player); + PersistenceManager.SavePlayer(CurrentUniverse.Id, player); } /// @@ -258,7 +270,7 @@ public void SavePlayer(Player player) if (currentToken.IsCancellationRequested) return null; - awaiter = persistenceManager.Load(out var loadedColumn, CurrentUniverse.Id, planet, index); + awaiter = PersistenceManager.Load(out var loadedColumn, CurrentUniverse.Id, planet, index); if (awaiter == null) { IChunkColumn column = planet.Generator.GenerateColumn(DefinitionManager, planet, new Index2(index.X, index.Y)); @@ -266,7 +278,7 @@ public void SavePlayer(Player player) } else { - awaiter.WaitOnAndRelease(); + loadedColumn = awaiter.WaitOnAndRelease(); Debug.Assert(loadedColumn != null, "loadedColumn != null"); column11 = loadedColumn; } @@ -295,7 +307,7 @@ public void SavePlayer(Player player) column11.Populated = true; column11.FlagDirty(); - SaveChunkColumn(column11); + SaveChunkColumn(column11, planet); } // Top left chunk column neighbour @@ -306,7 +318,7 @@ public void SavePlayer(Player player) column00.Populated = true; column00.FlagDirty(); - SaveChunkColumn(column00); + SaveChunkColumn(column00, planet); } // Top chunk column neighbour @@ -316,7 +328,7 @@ public void SavePlayer(Player player) populator.Populate(this, planet, column10, column20, column11, column21); column10.Populated = true; column10.FlagDirty(); - SaveChunkColumn(column10); + SaveChunkColumn(column10, planet); } // Left chunk column neighbour @@ -326,7 +338,7 @@ public void SavePlayer(Player player) populator.Populate(this, planet, column01, column11, column02, column12); column01.Populated = true; column01.FlagDirty(); - SaveChunkColumn(column01); + SaveChunkColumn(column01, planet); } return column11; @@ -334,14 +346,14 @@ public void SavePlayer(Player player) } /// - public void SaveChunkColumn(IChunkColumn chunkColumn) + public void SaveChunkColumn(IChunkColumn chunkColumn, IPlanet planet) { Debug.Assert(CurrentUniverse != null, nameof(CurrentUniverse) + " not loaded!"); if (disablePersistence) return; using (loadingSemaphore.EnterCountScope()) - persistenceManager.SaveColumn(CurrentUniverse.Id, chunkColumn.Planet, chunkColumn); + PersistenceManager.SaveColumn(CurrentUniverse.Id, planet, chunkColumn); } /// @@ -353,12 +365,12 @@ public void SaveChunkColumn(IChunkColumn chunkColumn) using (loadingSemaphore.EnterCountScope()) { currentToken.ThrowIfCancellationRequested(); - var awaiter = persistenceManager.Load(out var entity, CurrentUniverse.Id, entityId); + var awaiter = PersistenceManager.Load(out var entity, CurrentUniverse.Id, entityId); if (awaiter == null) return null; else - awaiter.WaitOnAndRelease(); + awaiter.WaitOnAndRelease(entity); return entity; } @@ -366,8 +378,8 @@ public void SaveChunkColumn(IChunkColumn chunkColumn) /// public void SaveComponentContainer(TContainer container) - where TContainer : ComponentContainer - where TComponent : IComponent + where TContainer : ComponentContainer + where TComponent : IComponent { if (CurrentUniverse == null) @@ -378,7 +390,7 @@ public void SaveComponentContainer(TContainer container) if (container is Player player) SavePlayer(player); else - persistenceManager.Save(container, CurrentUniverse.Id); + PersistenceManager.Save(container, CurrentUniverse.Id); } } @@ -394,12 +406,12 @@ public void SaveComponentContainer(TContainer container) { currentToken.ThrowIfCancellationRequested(); var awaiter - = persistenceManager + = PersistenceManager .Load(out var container, CurrentUniverse.Id, id); if (awaiter == null) return null; - awaiter.WaitOnAndRelease(); + awaiter.WaitOnAndRelease(container); return container; } @@ -418,13 +430,13 @@ class IdGuid using (loadingSemaphore.EnterCountScope()) { currentToken.ThrowIfCancellationRequested(); - var retValues = persistenceManager.GetAllComponents(CurrentUniverse.Id).ToArray(); //HACK: will be changed + var retValues = PersistenceManager.GetAllComponents(CurrentUniverse.Id).ToArray(); //HACK: will be changed if (typeof(T) == typeof(PositionComponent)) { foreach (var item in retValues) { - GenericCaster.Cast(item.Component).InstanceId = item.Id; + GenericCaster.Cast(item.Component).ParentId = item.Id; } } return retValues; @@ -439,10 +451,10 @@ class IdGuid using (loadingSemaphore.EnterCountScope()) { currentToken.ThrowIfCancellationRequested(); - var component = persistenceManager.GetComponent(CurrentUniverse.Id, id); + var component = PersistenceManager.GetComponent(CurrentUniverse.Id, id); if (component is PositionComponent posComponent) - posComponent.InstanceId = id; + posComponent.ParentId = id; return component; } } diff --git a/OctoAwesome/OctoAwesome.Tests/TestGlobalCache.cs b/OctoAwesome/OctoAwesome.Tests/TestGlobalCache.cs index a509eb8a..40c50eae 100644 --- a/OctoAwesome/OctoAwesome.Tests/TestGlobalCache.cs +++ b/OctoAwesome/OctoAwesome.Tests/TestGlobalCache.cs @@ -53,7 +53,7 @@ public void Release(int planet, Index2 position, bool passiv) SaveCounter++; } - public IChunkColumn Subscribe(IPlanet planet, Index2 position, bool passiv) + public IChunkColumn Subscribe(int planet, Index2 position, bool passiv) { LoadCounter++; return new ChunkColumn(new IChunk[] { new Chunk(new Index3(position, 0), planet), new Chunk(new Index3(position, 1), planet), new Chunk(new Index3(position, 2), planet) }, planet, position); diff --git a/OctoAwesome/OctoAwesome/AssemblyInfo.cs b/OctoAwesome/OctoAwesome/AssemblyInfo.cs new file mode 100644 index 00000000..a1316cc8 --- /dev/null +++ b/OctoAwesome/OctoAwesome/AssemblyInfo.cs @@ -0,0 +1,42 @@ +using OctoAwesome; +using OctoAwesome.EntityComponents; +using OctoAwesome.Location; +using OctoAwesome.Notifications; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +[assembly: NoosonConfiguration( + GenerateDeserializeExtension = false, + DisableWarnings = true, + GenerateStaticDeserializeWithCtor = true, + GenerateDeserializeOnInstance = true, + GenerateStaticSerialize = true, + GenerateStaticDeserializeIntoInstance = true, + NameOfStaticDeserializeWithCtor = "DeserializeAndCreate", + NameOfDeserializeOnInstance = "Deserialize", + NameOfStaticDeserializeIntoInstance = "Deserialize", + NameOfStaticDeserializeWithOutParams = "DeserializeOut")] + +[assembly: SerializationId(1, 1)] +[assembly: SerializationId(1, 2)] +[assembly: SerializationId(1, 3)] +[assembly: SerializationId(1, 4)] +[assembly: SerializationId(1, 5)] +[assembly: SerializationId(1, 6)] +[assembly: SerializationId(1, 7)] +[assembly: SerializationId(1, 8)] +[assembly: SerializationId(1, 9)] +[assembly: SerializationId(1, 10)] +[assembly: SerializationId(1, 11)] +[assembly: SerializationId(1, 12)] +[assembly: SerializationId(1, 13)] +[assembly: SerializationId(1, 14)] +[assembly: SerializationId(1, 15)] +[assembly: SerializationId(1, 16)] +[assembly: SerializationId(1, 17)] +[assembly: SerializationId(1, 18)] +[assembly: SerializationId(1, 19)] \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome/Caching/Cache.cs b/OctoAwesome/OctoAwesome/Caching/Cache.cs index 7728042e..95d8a231 100644 --- a/OctoAwesome/OctoAwesome/Caching/Cache.cs +++ b/OctoAwesome/OctoAwesome/Caching/Cache.cs @@ -1,4 +1,5 @@ using OctoAwesome.Threading; + using System; using System.Collections.Generic; using System.Diagnostics; @@ -36,6 +37,8 @@ public abstract class Cache public abstract TValue? Get(TKey key, LoadingMode loadingMode = LoadingMode.LoadIfNotExists) where TKey : notnull; + public abstract void AddOrUpdate(TKey key, TValue value) where TKey : notnull; + internal virtual void Start() { IsStarted = true; @@ -99,9 +102,9 @@ public abstract class Cache : Cache } if (result - && cacheItem!.LastAccessTime.Add(ClearTime) < DateTime.Now) + && cacheItem!.LastAccessTime.Add(ClearTime) < DateTime.UtcNow) { - cacheItem.LastAccessTime = DateTime.Now; + cacheItem.LastAccessTime = DateTime.UtcNow; } if (result) @@ -142,12 +145,21 @@ public abstract class Cache : Cache /// The key to identify the cache item by. /// The new value to cache. /// The cache item created for the cached value. - protected CacheItem AddOrUpdate(TKey key, TValue value) + protected virtual CacheItem AddOrUpdateInternal(TKey key, TValue value) { using var @lock = lockSemaphore.EnterExclusiveScope(); return valueCache[key] = new(value); } + /// + /// Add or update a cache item identified by the given key. + /// + /// The key to identify the cache item by. + /// The new value to cache. + public override void AddOrUpdate(TK key, TV value) + { + _ = AddOrUpdateInternal(GenericCaster.Cast(key), GenericCaster.Cast(value)); + } internal override void CollectGarbage() { @@ -156,7 +168,7 @@ internal override void CollectGarbage() using var @lock = lockSemaphore.EnterExclusiveScope(); var element = valueCache.ElementAt(i); - if (element.Value.LastAccessTime.Add(ClearTime) < DateTime.Now) + if (element.Value.LastAccessTime.Add(ClearTime) < DateTime.UtcNow) valueCache.Remove(element.Key, out _); } } @@ -197,7 +209,7 @@ protected class CacheItem /// /// The cached value. public CacheItem(TValue value) - : this(DateTime.Now, value) + : this(DateTime.UtcNow, value) { } diff --git a/OctoAwesome/OctoAwesome/Caching/CacheService.cs b/OctoAwesome/OctoAwesome/Caching/CacheService.cs index 49404485..84653d87 100644 --- a/OctoAwesome/OctoAwesome/Caching/CacheService.cs +++ b/OctoAwesome/OctoAwesome/Caching/CacheService.cs @@ -77,7 +77,7 @@ public void Start() item.Start(); } - + garbageCollectionTask = new Task(async () => await GarbageCollection(token), token, TaskCreationOptions.LongRunning); garbageCollectionTask.Start(); @@ -134,6 +134,23 @@ public void Dispose() return default; } + /// + /// Gets a value from the cache service. + /// + /// The type of the identifying key to get the value by. + /// The type of the value to get the value of. + /// The identifying key to get the value by. + + /// The value from the cache service. + public void AddOrUpdate(TKey key, TValue value) + where TKey : notnull + { + if (caches.TryGetValue(typeof(TValue), out var cache)) + { + cache.AddOrUpdate(key, value); + } + } + private async Task GarbageCollection(CancellationToken cancellationToken) { try diff --git a/OctoAwesome/OctoAwesome/Caching/ChunkColumnCache.cs b/OctoAwesome/OctoAwesome/Caching/ChunkColumnCache.cs index 8ff36dbc..4dd19580 100644 --- a/OctoAwesome/OctoAwesome/Caching/ChunkColumnCache.cs +++ b/OctoAwesome/OctoAwesome/Caching/ChunkColumnCache.cs @@ -32,7 +32,7 @@ internal sealed override void CollectGarbage() using var @lock = lockSemaphore.EnterExclusiveScope(); var element = valueCache.ElementAt(i); - if (element.Value.LastAccessTime.Add(ClearTime) < DateTime.Now) + if (element.Value.LastAccessTime.Add(ClearTime) < DateTime.UtcNow) { valueCache.Remove(element.Key, out _); } diff --git a/OctoAwesome/OctoAwesome/Caching/ComponentContainerCache.cs b/OctoAwesome/OctoAwesome/Caching/ComponentContainerCache.cs index c68d9813..a1018fcf 100644 --- a/OctoAwesome/OctoAwesome/Caching/ComponentContainerCache.cs +++ b/OctoAwesome/OctoAwesome/Caching/ComponentContainerCache.cs @@ -1,4 +1,6 @@ using OctoAwesome.Components; +using OctoAwesome.Serialization; + using System; namespace OctoAwesome.Caching diff --git a/OctoAwesome/OctoAwesome/Caching/PositionComponentCache.cs b/OctoAwesome/OctoAwesome/Caching/PositionComponentCache.cs index bbbf8701..77ebe3d9 100644 --- a/OctoAwesome/OctoAwesome/Caching/PositionComponentCache.cs +++ b/OctoAwesome/OctoAwesome/Caching/PositionComponentCache.cs @@ -3,7 +3,10 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.InteropServices; namespace OctoAwesome.Caching { @@ -14,7 +17,7 @@ public class PositionComponentCache : Cache { private readonly IResourceManager resourceManager; - private readonly Dictionary positionComponentByCoor; + private readonly Dictionary> positionComponentByCoor; /// /// Initializes a new instance of the class. @@ -28,6 +31,12 @@ public PositionComponentCache(IResourceManager resourceManager) } internal override void Start() + { + LoadAllPositionComponentsFromResourceManager(); + base.Start(); + } + + private void LoadAllPositionComponentsFromResourceManager() { var positionComponents = resourceManager @@ -35,12 +44,28 @@ var positionComponents foreach (var (id, component) in positionComponents) { - var cacheItem = AddOrUpdate(id, component); - using var @lock = lockSemaphore.EnterExclusiveScope(); - positionComponentByCoor.Add(component.Position, cacheItem); + _ = AddOrUpdateInternal(id, component); } - base.Start(); + } + + /// + protected override CacheItem AddOrUpdateInternal(Guid key, PositionComponent value) + { + var ci = base.AddOrUpdateInternal(key, value); + using var @lock = lockSemaphore.EnterExclusiveScope(); + ref var list = ref CollectionsMarshal.GetValueRefOrAddDefault(positionComponentByCoor, value.Position.ChunkIndex.XY, out var exists); + if (!exists) + { + list = new() { { key, ci } }; + } + else + { + + list[key] = ci; + } + + return ci; } internal override bool Remove(Guid key, [MaybeNullWhen(false)] out PositionComponent positionComponent) @@ -49,8 +74,11 @@ internal override bool Remove(Guid key, [MaybeNullWhen(false)] out PositionCompo if (base.Remove(key, out positionComponent)) { - return positionComponentByCoor - .Remove(positionComponent.Position); + foreach (var (_, dict) in positionComponentByCoor) + { + if (dict.Remove(key)) + return true; + } } return false; @@ -67,11 +95,24 @@ protected override PositionComponent Load(Guid key) /// The with the exact coordinate. protected PositionComponent GetBy(Coordinate position) { - using var @lock = lockSemaphore.EnterExclusiveScope(); - - var cacheItem = positionComponentByCoor[position]; - cacheItem.LastAccessTime = DateTime.Now; - return cacheItem.Value; + using (var @lock = lockSemaphore.EnterExclusiveScope()) + { + foreach (var (index, dict) in positionComponentByCoor) + { + if (index == position.ChunkIndex.XY) + { + foreach (var (_, ci) in dict) + { + if (ci.Value.Position == position) + { + ci.LastAccessTime = DateTime.UtcNow; + return ci.Value; + } + } + } + } + } + return default!; //TODO Index exception of some sort } /// @@ -79,25 +120,22 @@ protected PositionComponent GetBy(Coordinate position) /// /// The chunk index to query the instances for. /// A list of instances which are withing a specific chunk.. - protected List GetBy(Index3 chunkIndex) + protected List GetBy(Index2 chunkIndex) { using var @lock = lockSemaphore.EnterExclusiveScope(); var list = new List(); - foreach (var component in positionComponentByCoor) + foreach (var (index, dict) in positionComponentByCoor) { - var key = component.Key; - var normalizedChunkIndex = key.ChunkIndex; - normalizedChunkIndex.NormalizeXY(component.Value.Value.Planet.Size); - - - if (key.Planet == chunkIndex.Z - && normalizedChunkIndex.X == chunkIndex.X - && normalizedChunkIndex.Y == chunkIndex.Y) + if (index.X == chunkIndex.X + && index.Y == chunkIndex.Y) { - list.Add(component.Value.Value); - component.Value.LastAccessTime = DateTime.Now; + foreach (var (_, ci) in dict) + { + list.Add(ci.Value); + ci.LastAccessTime = DateTime.UtcNow; + } } } @@ -111,7 +149,7 @@ protected List GetBy(Index3 chunkIndex) { Guid guid => GenericCaster.Cast(GetBy(guid, loadingMode)), Coordinate coordinate => GenericCaster.Cast(GetBy(coordinate)), - Index3 chunkColumnIndex => GenericCaster, TValue>.Cast(GetBy(chunkColumnIndex)), + Index2 chunkColumnIndex => GenericCaster, TValue>.Cast(GetBy(chunkColumnIndex)), //(IPlanet, Index2) index => GenericCaster>.Cast(GetBy(index)), _ => throw new NotSupportedException() }; diff --git a/OctoAwesome/OctoAwesome/Chunking/Chunk.cs b/OctoAwesome/OctoAwesome/Chunking/Chunk.cs index c6f25d0a..d05471b8 100644 --- a/OctoAwesome/OctoAwesome/Chunking/Chunk.cs +++ b/OctoAwesome/OctoAwesome/Chunking/Chunk.cs @@ -55,14 +55,13 @@ public sealed class Chunk : IChunk /// The size of a chunk as in blocks. /// public static readonly Index3 CHUNKSIZE = new Index3(CHUNKSIZE_X, CHUNKSIZE_Y, CHUNKSIZE_Z); - private IChunkColumn? chunkColumnField; - private IPlanet? planet; + private IChunkColumn? chunkColumn; private Index3 index; private IChunkColumn ChunkColumn { - get => NullabilityHelper.NotNullAssert(chunkColumnField, $"{nameof(ChunkColumn)} was not initialized!"); - set => chunkColumnField = NullabilityHelper.NotNullAssert(value, $"{nameof(ChunkColumn)} cannot be initialized with null!"); + get => NullabilityHelper.NotNullAssert(chunkColumn, $"{nameof(ChunkColumn)} was not initialized!"); + set => chunkColumn = NullabilityHelper.NotNullAssert(value, $"{nameof(ChunkColumn)} cannot be initialized with null!"); } /// @@ -76,17 +75,16 @@ public Index3 Index { get { - Debug.Assert(planet is not null, $"{nameof(IPoolElement)} was not initialized!"); return index; } private set => index = value; } /// - public IPlanet Planet + public int PlanetId { - get => NullabilityHelper.NotNullAssert(planet, $"{nameof(IPoolElement)} was not initialized!"); - private set => planet = NullabilityHelper.NotNullAssert(value, $"{nameof(Planet)} cannot be initialized with null!"); + get; + private set; } /// @@ -96,14 +94,14 @@ public IPlanet Planet /// Initializes a new instance of the class. /// /// The position of the chunk. - /// The planet the chunk is part of - public Chunk(Index3 pos, IPlanet planet) + /// The planet the chunk is part of + public Chunk(Index3 pos, int planetId) { Blocks = new ushort[CHUNKSIZE_X * CHUNKSIZE_Y * CHUNKSIZE_Z]; MetaData = new int[CHUNKSIZE_X * CHUNKSIZE_Y * CHUNKSIZE_Z]; Index = pos; - Planet = planet; + PlanetId = planetId; } /// @@ -194,7 +192,7 @@ public void SetColumn(IChunkColumn chunkColumn) /// public void OnUpdate(SerializableNotification notification) - => chunkColumnField?.OnUpdate(notification); + => chunkColumn?.OnUpdate(notification); /// public void Update(SerializableNotification notification) @@ -224,7 +222,7 @@ private void BlockChanged(BlockInfo blockInfo) var notification = TypeContainer.Get>().Rent(); notification.BlockInfo = blockInfo; notification.ChunkPos = Index; - notification.Planet = Planet.Id; + notification.Planet = PlanetId; OnUpdate(notification); @@ -235,7 +233,7 @@ private void BlocksChanged(params BlockInfo[] blockInfos) var notification = TypeContainer.Get>().Rent(); notification.BlockInfos = blockInfos; notification.ChunkPos = Index; - notification.Planet = Planet.Id; + notification.Planet = PlanetId; OnUpdate(notification); @@ -272,10 +270,10 @@ public static int GetFlatIndex(Index3 position) | position.X & CHUNKSIZE_X - 1; } - internal void Init(Index3 position, IPlanet planet) + internal void Init(Index3 position, int planetId) { Index = position; - Planet = planet; + PlanetId = planetId; for (int i = 0; i < Blocks.Length; i++) Blocks[i] = 0; @@ -294,7 +292,6 @@ public void Init(IPool pool) public void Release() { Index = default; - planet = default; } } } diff --git a/OctoAwesome/OctoAwesome/Chunking/ChunkColumn.cs b/OctoAwesome/OctoAwesome/Chunking/ChunkColumn.cs index e663ee8b..2295c9a5 100644 --- a/OctoAwesome/OctoAwesome/Chunking/ChunkColumn.cs +++ b/OctoAwesome/OctoAwesome/Chunking/ChunkColumn.cs @@ -12,13 +12,14 @@ using System.IO; using System.Linq; using OctoAwesome.Extension; +using OctoAwesome.Serialization; namespace OctoAwesome.Chunking { /// /// Chunk column implementation containing in a column. /// - public class ChunkColumn : IChunkColumn + public class ChunkColumn : IChunkColumn, IConstructionSerializable { /// @@ -60,9 +61,9 @@ private IGlobalChunkCache GlobalChunkCache /// Initializes a new instance of the class. /// /// The chunks for the column. - /// The planet to generate the column for. + /// The planet to generate the column for. /// The column position on the planet. - public ChunkColumn(IChunk[] chunks, IPlanet planet, Index2 columnIndex) : this(planet) + public ChunkColumn(IChunk[] chunks, int planetId, Index2 columnIndex) : this(planetId) { Chunks = chunks; Index = columnIndex; @@ -88,12 +89,11 @@ public ChunkColumn() /// /// Initializes a new instance of the class. /// - /// The planet to generate the chunk column for. - public ChunkColumn(IPlanet planet) + /// The planet to generate the chunk column for. + public ChunkColumn(int planetId) : this() { - Planet = planet; - GlobalChunkCache = planet.GlobalChunkCache; + PlanetId = planetId; } private void OnChunkChanged(IChunk arg1) @@ -144,10 +144,10 @@ public bool Populated } /// - public IPlanet Planet + public int PlanetId { - get => NullabilityHelper.NotNullAssert(planet, $"{nameof(Planet)} was not initialized!"); - private set => planet = NullabilityHelper.NotNullAssert(value, $"{nameof(Planet)} cannot be initialized with null!"); + get; + private set; } @@ -249,7 +249,7 @@ public void Serialize(BinaryWriter writer) writer.Write(Populated); // Populated writer.Write(Index.X); writer.Write(Index.Y); - writer.Write(Planet.Id); + writer.Write(PlanetId); for (var y = 0; y < Chunk.CHUNKSIZE_Y; y++) // Heightmap for (var x = 0; x < Chunk.CHUNKSIZE_X; x++) @@ -315,10 +315,8 @@ public void Deserialize(BinaryReader reader) Populated = reader.ReadBoolean(); // Populated Index = new Index2(reader.ReadInt32(), reader.ReadInt32()); - int planetId = reader.ReadInt32(); + PlanetId = reader.ReadInt32(); - var resManager = TypeContainer.Get(); - Planet = resManager.GetPlanet(planetId); for (var y = 0; y < Chunk.CHUNKSIZE_Y; y++) // Heightmap for (var x = 0; x < Chunk.CHUNKSIZE_X; x++) @@ -348,7 +346,7 @@ public void Deserialize(BinaryReader reader) // Phase 3 (Chunk Infos) for (var c = 0; c < Chunks.Length; c++) { - IChunk chunk = Chunks[c] = ChunkPool.Rent(new Index3(Index, c), Planet); + IChunk chunk = Chunks[c] = ChunkPool.Rent(new Index3(Index, c), PlanetId); chunk.Version = reader.ReadInt32(); chunk.Changed += OnChunkChanged; chunk.SetColumn(this); @@ -375,6 +373,13 @@ public void Deserialize(BinaryReader reader) /// public void OnUpdate(SerializableNotification notification) { + if (globalChunkCache is null) + { + var manager = TypeContainer.Get(); + var planet = manager.GetPlanet(PlanetId); + globalChunkCache = planet.GlobalChunkCache; + } + GlobalChunkCache.OnUpdate(notification); } @@ -415,13 +420,6 @@ public void Remove(Entity entity) entities.Remove(entity); } - /// - public IEnumerable FailChunkEntity() - { - using (entitySemaphore.Wait()) - return entities.FailChunkEntity().ToList(); - } - /// public void FlagDirty() { @@ -430,5 +428,25 @@ public void FlagDirty() chunk.FlagDirty(); } } + + /// + public static ChunkColumn DeserializeAndCreate(BinaryReader reader) + { + var column = new ChunkColumn(); + column.Deserialize(reader); + return column; + } + + /// + public static void Serialize(ChunkColumn that, BinaryWriter writer) + { + that.Serialize(writer); + } + + /// + public static void Deserialize(ChunkColumn that, BinaryReader reader) + { + that.Deserialize(reader); + } } } diff --git a/OctoAwesome/OctoAwesome/Chunking/GlobalChunkCache.cs b/OctoAwesome/OctoAwesome/Chunking/GlobalChunkCache.cs index be8527c6..0f313546 100644 --- a/OctoAwesome/OctoAwesome/Chunking/GlobalChunkCache.cs +++ b/OctoAwesome/OctoAwesome/Chunking/GlobalChunkCache.cs @@ -21,11 +21,11 @@ public sealed class GlobalChunkCache : IGlobalChunkCache, IDisposable { //public event EventHandler ChunkColumnChanged; - private readonly AutoResetEvent _autoResetEvent = new AutoResetEvent(false); + private readonly AutoResetEvent autoResetEvent = new AutoResetEvent(false); private readonly CancellationTokenSource tokenSource; private readonly IResourceManager resourceManager; - private readonly SerializationIdTypeProvider typeProvider; + private readonly IUpdateHub updateHub; private readonly LockSemaphore semaphore = new LockSemaphore(1, 1); private readonly LockSemaphore updateSemaphore = new LockSemaphore(1, 1); @@ -33,10 +33,8 @@ public sealed class GlobalChunkCache : IGlobalChunkCache, IDisposable private readonly ILogger logger; private readonly ChunkPool chunkPool; private readonly IDisposable chunkSubscription; - private readonly IDisposable networkSource; private readonly IDisposable chunkSource; private readonly IDisposable simulationSource; - private readonly Relay networkRelay; private readonly Relay chunkRelay; private readonly Relay simulationRelay; @@ -58,6 +56,9 @@ public int LoadedChunkColumns /// public IPlanet Planet { get; } + /// + public CacheService CacheService => cacheService; + private readonly CacheService cacheService; /// @@ -66,16 +67,15 @@ public int LoadedChunkColumns /// The planet the global chunk cache manages chunks for. /// The current to load resources. /// The update hub to propagate updates. - /// The type provider used to manage serialization types.. - public GlobalChunkCache(IPlanet planet, IResourceManager resourceManager, IUpdateHub updateHub, SerializationIdTypeProvider typeProvider) + /// The type provider used to manage serialization types.. + public GlobalChunkCache(IPlanet planet, IResourceManager resourceManager, IUpdateHub updateHub) { cacheService = new CacheService(planet, resourceManager, updateHub); cacheService.Start(); Planet = planet ?? throw new ArgumentNullException(nameof(planet)); this.resourceManager = resourceManager ?? throw new ArgumentNullException(nameof(resourceManager)); - this.typeProvider = typeProvider; - networkRelay = new Relay(); + this.updateHub = updateHub; chunkRelay = new Relay(); simulationRelay = new Relay(); @@ -86,7 +86,6 @@ public GlobalChunkCache(IPlanet planet, IResourceManager resourceManager, IUpdat chunkPool = TypeContainer.Get(); chunkSubscription = updateHub.ListenOn(DefaultChannels.Chunk).Subscribe(OnNext); - networkSource = updateHub.AddSource(networkRelay, DefaultChannels.Network); chunkSource = updateHub.AddSource(chunkRelay, DefaultChannels.Chunk); simulationSource = updateHub.AddSource(simulationRelay, DefaultChannels.Simulation); } @@ -95,31 +94,38 @@ public GlobalChunkCache(IPlanet planet, IResourceManager resourceManager, IUpdat public IChunkColumn Subscribe(Index2 position) { var column = cacheService.Get(position)!; - var chunkIndex = new Index3(position, Planet.Id); var positionComponents = cacheService - .Get>(chunkIndex)!; + .Get>(position)!; - foreach (var positionComponent in positionComponents) + if (resourceManager.LocalPersistance) { - if (!typeProvider.TryGet(positionComponent.InstanceTypeId, out var type)) - continue; - - if (type.IsAssignableTo(typeof(Entity))) + foreach (var positionComponent in positionComponents) { - var entity - = cacheService - .Get(positionComponent.InstanceId)!; - - positionComponent.SetInstance(entity); - var notification = new EntityNotification + if (!SerializationIdTypeProvider.TryGet(positionComponent.ParentTypeId, out var type)) { - Entity = entity, - Type = EntityNotification.ActionType.Add - }; + continue; + } - simulationRelay.OnNext(notification); + if (type.IsAssignableTo(typeof(Entity))) + { + var entity + = cacheService + .Get(positionComponent.ParentId)!; + if (entity is null) + continue; + positionComponent.Parent = entity; + + logger.Debug($"Send {entity.GetType().Name} with id {entity.Id} to simulation"); + var notification = new EntityNotification + { + Entity = entity, + Type = EntityNotification.ActionType.Add + }; + + simulationRelay.OnNext(notification); + } } } @@ -168,10 +174,10 @@ public void BeforeSimulationUpdate(Simulation simulation) /// - /// Called when chunk notification occured. + /// Called when chunk notification occurred. /// /// The notification. - public void OnNext(Notification value) + public void OnNext(object value) { switch (value) { @@ -189,7 +195,7 @@ public void OnNext(Notification value) /// public void OnUpdate(SerializableNotification notification) { - networkRelay.OnNext(notification); + updateHub.PushNetwork(notification, DefaultChannels.Chunk); if (notification is IChunkNotification) chunkRelay.OnNext(notification); @@ -212,12 +218,10 @@ public void Dispose() { semaphore.Dispose(); updateSemaphore.Dispose(); - _autoResetEvent.Dispose(); + autoResetEvent.Dispose(); chunkSubscription.Dispose(); - networkSource.Dispose(); chunkSource.Dispose(); tokenSource.Dispose(); - networkRelay.Dispose(); chunkRelay.Dispose(); cacheService.Dispose(); diff --git a/OctoAwesome/OctoAwesome/Chunking/IChunk.cs b/OctoAwesome/OctoAwesome/Chunking/IChunk.cs index b650920a..7dc4fc56 100644 --- a/OctoAwesome/OctoAwesome/Chunking/IChunk.cs +++ b/OctoAwesome/OctoAwesome/Chunking/IChunk.cs @@ -15,7 +15,7 @@ public interface IChunk : IPoolElement /// /// Gets the planet the chunk is part of. /// - IPlanet Planet { get; } + int PlanetId { get; } /// /// Gets the index of the chunk. diff --git a/OctoAwesome/OctoAwesome/Chunking/IChunkColumn.cs b/OctoAwesome/OctoAwesome/Chunking/IChunkColumn.cs index 93ffeabf..14de83c2 100644 --- a/OctoAwesome/OctoAwesome/Chunking/IChunkColumn.cs +++ b/OctoAwesome/OctoAwesome/Chunking/IChunkColumn.cs @@ -20,7 +20,7 @@ public interface IChunkColumn : ISerializable /// /// Gets the planet the chunk column is part of. /// - IPlanet Planet { get; } + int PlanetId { get; } /// /// Gets the index position of the chunk column. @@ -129,12 +129,6 @@ public interface IChunkColumn : ISerializable /// The action to execute for each entity. void ForEachEntity(Action action); - /// - /// Gets an enumeration of - /// depicting entities that are not part of this chunk column anymore. - /// - /// The entities that are not part of this chunk column anymore. - IEnumerable FailChunkEntity(); /// /// Remove an entity from this chunk column. diff --git a/OctoAwesome/OctoAwesome/Chunking/IGlobalChunkCache.cs b/OctoAwesome/OctoAwesome/Chunking/IGlobalChunkCache.cs index fabcb4cc..ad6608c7 100644 --- a/OctoAwesome/OctoAwesome/Chunking/IGlobalChunkCache.cs +++ b/OctoAwesome/OctoAwesome/Chunking/IGlobalChunkCache.cs @@ -1,4 +1,5 @@ -using OctoAwesome.Location; +using OctoAwesome.Caching; +using OctoAwesome.Location; using OctoAwesome.Notifications; namespace OctoAwesome.Chunking @@ -24,6 +25,7 @@ public interface IGlobalChunkCache /// Gets the planet the global chunk cache manages the chunks for. /// IPlanet Planet { get; } + CacheService CacheService { get; } /// /// Subscribes to a chunk column. diff --git a/OctoAwesome/OctoAwesome/Collections/EnumerationmodifiableConcurrentList.cs b/OctoAwesome/OctoAwesome/Collections/EnumerationmodifiableConcurrentList.cs index b0219c18..3fefc638 100644 --- a/OctoAwesome/OctoAwesome/Collections/EnumerationmodifiableConcurrentList.cs +++ b/OctoAwesome/OctoAwesome/Collections/EnumerationmodifiableConcurrentList.cs @@ -278,6 +278,10 @@ public Enumerator Rent() gottem.Add(e); return e; } + public IPoolElement RentElement() + { + return Rent(); + } public void Return(Enumerator obj) { @@ -293,6 +297,7 @@ public void Return(IPoolElement obj) else throw new ArgumentException(nameof(obj)); } + } } diff --git a/OctoAwesome/OctoAwesome/Collections/EnumerationmodifiableList.cs b/OctoAwesome/OctoAwesome/Collections/EnumerationmodifiableList.cs index 13e938e7..82f864db 100644 --- a/OctoAwesome/OctoAwesome/Collections/EnumerationmodifiableList.cs +++ b/OctoAwesome/OctoAwesome/Collections/EnumerationmodifiableList.cs @@ -251,6 +251,10 @@ public Enumerator Rent() gottem.Add(e); return e; } + public IPoolElement RentElement() + { + return Rent(); + } public void Return(Enumerator obj) { @@ -265,6 +269,7 @@ public void Return(IPoolElement obj) else throw new ArgumentException(nameof(obj)); } + } } diff --git a/OctoAwesome/OctoAwesome/Common/IGameService.cs b/OctoAwesome/OctoAwesome/Common/IGameService.cs deleted file mode 100644 index 7ca2e345..00000000 --- a/OctoAwesome/OctoAwesome/Common/IGameService.cs +++ /dev/null @@ -1,36 +0,0 @@ -using engenious; - -using OctoAwesome.Chunking; -using OctoAwesome.Definitions; -using OctoAwesome.Location; - -using System; - -namespace OctoAwesome.Common -{ - /// - /// Common services for extensions. - /// - public interface IGameService : IServiceProvider - { - /// - /// Gets the of the local data. - /// - IDefinitionManager DefinitionManager { get; } - - /// - /// Calculates an entity's velocity after collision with the world. (Original Lassi) - /// - /// The simulation time. - /// Position of the . - /// to calculate the collisions in. - /// The bounding collision radius of . - /// The height of the . - /// The position change between two simulation steps. - /// The velocity of the . - /// Calculated velocity after the collision test. - Vector3 WorldCollision(GameTime gameTime, Coordinate position, ILocalChunkCache cache, float radius, float height, - Vector3 deltaPosition, Vector3 velocity); - - } -} diff --git a/OctoAwesome/OctoAwesome/ComponentContainer.cs b/OctoAwesome/OctoAwesome/ComponentContainer.cs index 5cf93f2a..1c89230d 100644 --- a/OctoAwesome/OctoAwesome/ComponentContainer.cs +++ b/OctoAwesome/OctoAwesome/ComponentContainer.cs @@ -1,5 +1,7 @@ using engenious; +using NonSucking.Framework.Serialization; + using OctoAwesome.Chunking; using OctoAwesome.Components; using OctoAwesome.EntityComponents; @@ -16,29 +18,44 @@ namespace OctoAwesome /// /// Base class for classes containing components. /// - public abstract class ComponentContainer : ISerializable, IIdentification, IComponentContainer, INotificationSubject + public abstract partial class ComponentContainer : IIdentification, IComponentContainer, ISerializable { /// /// Gets the Id of the container. /// - public Guid Id { get; internal set; } + public Guid Id { get; internal protected set; } /// /// Gets the reference to the active simulation; or null when no simulation is active. /// - public Simulation? Simulation { get; internal set; } + [NoosonIgnore] + public Simulation? Simulation + { + get => simulation; + internal set + { + if (value is null) + return; + simulation = value; + foreach (var item in Components) + { + if (item is Component c) + c.OnParentSetting(this); + } + } + } /// - /// List of components with notification interface implementation. + /// Gets a list of all components this container holds. /// - protected readonly List> notificationComponents; + public ComponentList Components { get; protected set; } + private Simulation? simulation; /// /// Initializes a new instance of the class. /// protected ComponentContainer() { - notificationComponents = new(); Id = Guid.Empty; } @@ -63,55 +80,118 @@ public override bool Equals(object? obj) return ReferenceEquals(this, obj); } + ///// + //public abstract void Serialize(BinaryWriter writer); + + ///// + //public abstract void Deserialize(BinaryReader reader); + /// - public virtual void OnNotification(SerializableNotification notification) - { - } + public bool ContainsComponent() + => Components.Contains(); - /// - /// Used to interact with this component container - /// - /// The current game time when the event happened - /// The that interacted with us - public void Interact(GameTime gameTime, Entity entity) => OnInteract(gameTime, entity); + /// + public T? GetComponent() + => Components.Get(); + + /// + public T? GetComponent(int id) + => Components.Get(id); /// - /// Called when this component container got interacted with + /// Tries to get the component of the component container /// - /// The current game time when the event happened - /// The that interacted with us - protected abstract void OnInteract(GameTime gameTime, Entity entity); + /// The Type of the component to search for + /// The component to be returned + /// True if the component was found, otherwise false + public bool TryGetComponent([MaybeNullWhen(false)] out T component) where T : IComponent + => Components.TryGet(out component); - /// - public virtual void Push(SerializableNotification notification) + + } + + partial class ComponentContainer + { + /// + ///Serializes the given instance. + /// + ///The instance to serialize. + ///The to serialize to. + public static void Serialize(OctoAwesome.ComponentContainer that, System.IO.BinaryWriter writer) { - foreach (var component in notificationComponents) - component.OnNotification(notification); + that.Serialize(writer); } - /// - public abstract void Serialize(BinaryWriter writer); + /// + ///Serializes this instance. + /// + ///The to serialize to. + public virtual void Serialize(System.IO.BinaryWriter writer) + { +#if !(NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER) + var buffer = Id.ToByteArray(); +#else + var buffer = (System.Span)stackalloc byte[16]; + _ = Id.TryWriteBytes(buffer); +#endif + writer.Write(buffer); + Components.Serialize(writer); + } - /// - public abstract void Deserialize(BinaryReader reader); + /// + ///Deserializes the properties of a type. + /// + ///The to deserialize from. + ///The deserialized instance of the property . + ///The deserialized instance of the property . + public static void DeserializeOut(System.IO.BinaryReader reader, out Guid id, out ComponentList components) + { + id = default(System.Guid)!; +#if !(NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER) + var buffer_id = reader.ReadBytes(16); +#else + var buffer_id = (System.Span)stackalloc byte[16]; + reader.ReadBytes(buffer_id); +#endif + id = new System.Guid(buffer_id); + components = ComponentList.DeserializeStatic(reader); + } - /// - public abstract bool ContainsComponent(); - /// - public abstract T? GetComponent(); + /// + ///Deserializes into instance. + /// + ///The instance to deserialize into. + ///The to deserialize from. + public static void Deserialize(OctoAwesome.ComponentContainer that, System.IO.BinaryReader reader) + { + DeserializeOut(reader, out var id, out var components); + that.Id = id; + that.Components = components; + components.Parent = that; + } + /// + ///Deserializes into instance. + /// + ///The to deserialize from. + public virtual void Deserialize(System.IO.BinaryReader reader) + { + Deserialize(this, reader); + } } + /// /// Base class for classes containing components of a specific type. /// /// The type of the components to contain. - public abstract class ComponentContainer : ComponentContainer, IUpdateable where TComponent : IComponent + public abstract partial class ComponentContainer : + ComponentContainer, IUpdateable + where TComponent : IComponent, ISerializable { /// - /// Gets a list of all components this container holds. + /// The resource manager for loading resource assets. /// - public ComponentList Components { get; } - + public IResourceManager? ResourceManager { get; internal set; } private List updateables = new(); /// @@ -119,14 +199,21 @@ public abstract class ComponentContainer : ComponentContainer, IUpda /// protected ComponentContainer() { - Components = new(ValidateAddComponent, ValidateRemoveComponent, OnAddComponent, OnRemoveComponent); + Components = new(ValidateAddComponent, ValidateRemoveComponent, OnAddComponent, OnRemoveComponent, this); + } + + protected ComponentContainer(Guid id, ComponentList components) + { + Id = id; + Components = components; + Components.Parent = this; } /// /// Gets called when a component was removed from this container. /// /// The component that was removed. - protected void OnRemoveComponent(TComponent component) + protected void OnRemoveComponent(IComponent component) { } @@ -135,27 +222,10 @@ protected void OnRemoveComponent(TComponent component) /// Gets called when a component was added to this container. /// /// The component that was added. - protected virtual void OnAddComponent(TComponent component) + protected virtual void OnAddComponent(IComponent component) { - if (component is InstanceComponent instanceComponent) - instanceComponent.SetInstance(this); - - //HACK: Remove PositionComponent Dependency - //if (component is LocalChunkCacheComponent cacheComponent) - //{ - // if (cacheComponent.LocalChunkCache != null) - // return; - - // var positionComponent = Components.GetComponent(); + component.Parent = this; - // if (positionComponent == null) - // return; - - //cacheComponent.LocalChunkCache = new LocalChunkCache(positionComponent.Planet.GlobalChunkCache, 4, 2); - //} - - if (component is INotificationSubject nofiticationComponent) - notificationComponents.Add(nofiticationComponent); if (component is IUpdateable updateable) updateables.Add(updateable); } @@ -167,7 +237,7 @@ protected virtual void OnAddComponent(TComponent component) /// /// Thrown when the component can not be added in the current state. E.g. during simulation. /// - protected virtual void ValidateAddComponent(TComponent component) + protected virtual void ValidateAddComponent(IComponent component) { } @@ -178,27 +248,26 @@ protected virtual void ValidateAddComponent(TComponent component) /// /// Thrown when the component can not be removed in the current state. E.g. during simulation. /// - protected virtual void ValidateRemoveComponent(TComponent component) + protected virtual void ValidateRemoveComponent(IComponent component) { } /// /// Initializes the component container. /// - /// The resource manager for loading resource assets. - public void Initialize(IResourceManager manager) + public void Initialize() { - OnInitialize(manager); + OnInitialize(); } /// /// Gets called when the component container is initializes. /// - /// The resource manager for loading resource assets. - protected virtual void OnInitialize(IResourceManager manager) + protected virtual void OnInitialize() { foreach (var component in Components) { + if (component is LocalChunkCacheComponent localChunkCache) { if (!localChunkCache.Enabled) @@ -215,45 +284,12 @@ protected virtual void OnInitialize(IResourceManager manager) } } - /// - public override void Serialize(BinaryWriter writer) - { - writer.Write(Id.ToByteArray()); - - Components.Serialize(writer); - } - - /// - public override void Deserialize(BinaryReader reader) - { - Id = new Guid(reader.ReadBytes(16)); - Components.Deserialize(reader); - } - - /// - public override bool ContainsComponent() - => Components.Contains(); - - /// - public override T? GetComponent() where T : default - => Components.Get(); - - /// - /// Tries to get the component of the component container - /// - /// The Type of the component to search for - /// The component to be returned - /// True if the component was found, otherwise false - public bool TryGetComponent([MaybeNullWhen(false)] out T component) where T : TComponent - => Components.TryGet(out component); - - /// public virtual void Update(GameTime gameTime) { foreach (var item in updateables) item.Update(gameTime); - + } } } diff --git a/OctoAwesome/OctoAwesome/Components/Component.cs b/OctoAwesome/OctoAwesome/Components/Component.cs index 74cbf0a3..e6974c7a 100644 --- a/OctoAwesome/OctoAwesome/Components/Component.cs +++ b/OctoAwesome/OctoAwesome/Components/Component.cs @@ -1,21 +1,69 @@ using OctoAwesome.Serialization; +using OctoAwesome.Components; +using System; +using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; +using System.Threading; namespace OctoAwesome.Components { /// /// Base Class for all components. /// - public abstract class Component : IComponent + [Nooson] + public abstract partial class Component : IComponent, ISerializable, IEquatable { + /// + public int Id { get; set; } = -1; //Maybe only setable by ComponentContainer somehow somewhere + /// public bool Enabled { get; set; } /// public bool Sendable { get; set; } + [NoosonIgnore] + /// + public IComponentContainer Parent + { + get => parent; set + { + + if (value == parent || value is null || parent is not null) + return; + + var type = value.GetType(); + + var serId = type.SerializationId(); + if (serId > 0) + ParentTypeId = serId; + OnParentSetting(value); + ParentId = value.Id; + parent = value; + } + } + + /// + /// Gets the unique identifier for the . + /// + public Guid ParentId { get; set; } + + /// + /// Gets the instance type id. + /// + /// + public ulong ParentTypeId { get; protected set; } + + /// + /// Current version of the component, starts at 1, because 0 means invalid + /// + public uint Version => version; + + private uint version = 1; + private IComponentContainer parent; + /// /// Initializes a new instance of the class. /// @@ -25,47 +73,51 @@ protected Component() Sendable = false; } - /// - public virtual void Serialize(BinaryWriter writer) + public void IncrementVersion(bool preventSend = false) { - writer.Write(Enabled); + Interlocked.Increment(ref version); + if (!preventSend && Sendable) + Parent?.Simulation?.FooBbqSimulationComponent.Add(this); } - /// - public virtual void Deserialize(BinaryReader reader) + protected void ChangeProperty(ref T field, T value) { - Enabled = reader.ReadBoolean(); + field = value; + Interlocked.Increment(ref version); } /// - /// Called when a value is changed. + /// Gets called onec, when the parent is about to be set /// - /// The new value of the property. - /// The name of the property that was changed. - /// The type of the property that was changed. - protected virtual void OnPropertyChanged(T value, string propertyName) + protected internal virtual void OnParentSetting(IComponentContainer newParent) { } - /// - /// Sets a field to a new value and propagates property changes. - /// - /// The reference to the field whose value to change. - /// The new value. - /// The name of the property to set the value of. - /// The type of the property to set the value of. - protected void SetValue(ref T field, T value, [CallerMemberName] string propertyName = "") + public override bool Equals(object? obj) { - if (field != null) - { - if (field.Equals(value)) - return; - } + return Equals(obj as Component); + } - field = value; + public bool Equals(Component? other) + { + return other is not null && + Id == other.Id; + } + + public override int GetHashCode() + { + return HashCode.Combine(Id); + } - OnPropertyChanged(field, propertyName); + public static bool operator ==(Component? left, Component? right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(Component? left, Component? right) + { + return !(left == right); } } } diff --git a/OctoAwesome/OctoAwesome/Components/ComponentList.cs b/OctoAwesome/OctoAwesome/Components/ComponentList.cs index dce81670..f0e09500 100644 --- a/OctoAwesome/OctoAwesome/Components/ComponentList.cs +++ b/OctoAwesome/OctoAwesome/Components/ComponentList.cs @@ -7,6 +7,10 @@ using System.IO; using System.Linq; using OctoAwesome.Caching; +using NonSucking.Framework.Serialization; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; namespace OctoAwesome.Components; @@ -14,6 +18,7 @@ namespace OctoAwesome.Components; /// Base Class for all component based entities. /// /// Type of the components contained in the list. +[NoosonCustom(DeserializeMethodName = nameof(DeserializeStatic), SerializeMethodName = nameof(Serialize))] public class ComponentList : IEnumerable where T : IComponent, ISerializable { @@ -25,14 +30,28 @@ public class ComponentList : IEnumerable where T : IComponent, ISerializab public IList this[Type type] => componentsByType.TryGetValue(type, out var result) ? result : Array.Empty(); + [NoosonIgnore] private IReadOnlyCollection TypeKeys => componentsByType.Keys; + internal IComponentContainer Parent + { + get => parent; set + { + parent = value; + foreach (var item in flatComponents) + { + item.Value.Parent = value; + } + } + } + private IComponentContainer? parent; + private readonly IResourceManager resourceManager; private readonly Action? insertValidator; private readonly Action? removeValidator; private readonly Action? onInserter; private readonly Action? onRemover; - private readonly HashSet flatComponents = new(); + private readonly Dictionary flatComponents = new(); private readonly Dictionary> componentsByType = new(); /// @@ -40,6 +59,8 @@ public class ComponentList : IEnumerable where T : IComponent, ISerializab /// public ComponentList() { + parent = default; + resourceManager = TypeContainer.Get(); } /// @@ -49,20 +70,21 @@ public ComponentList() /// The validator for removals. /// The method to call on insertion. /// The method to call on removal. - public ComponentList(Action? insertValidator, Action? removeValidator, Action? onInserter, Action? onRemover) + public ComponentList(Action? insertValidator, Action? removeValidator, Action? onInserter, Action? onRemover, IComponentContainer parent) : this() { this.insertValidator = insertValidator; this.removeValidator = removeValidator; this.onInserter = onInserter; this.onRemover = onRemover; + this.parent = parent; } /// public IEnumerator GetEnumerator() - => flatComponents.GetEnumerator(); + => flatComponents.Values.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() - => flatComponents.GetEnumerator(); + => flatComponents.Values.GetEnumerator(); /// /// Adds a component if the type is not already present. @@ -72,9 +94,7 @@ IEnumerator IEnumerable.GetEnumerator() public void AddIfTypeNotExists(V component) where V : T { Type type = component.GetType(); - - if (flatComponents.Contains(component)) - return; + AssignId(component); if (!componentsByType.TryGetValue(type, out var existing)) { @@ -86,8 +106,11 @@ public void AddIfTypeNotExists(V component) where V : T return; } + flatComponents[component.Id] = component; + existing.Add(component); - flatComponents.Add(component); + if (parent is not null) + component.Parent = parent; insertValidator?.Invoke(component); onInserter?.Invoke(component); } @@ -100,8 +123,10 @@ public void AddIfTypeNotExists(V component) where V : T public void Add(V component) where V : T { Type type = component.GetType(); + AssignId(component); - if (flatComponents.Contains(component)) + ref var comp = ref CollectionsMarshal.GetValueRefOrAddDefault(flatComponents, component.Id, out var exists); + if (exists) return; @@ -114,22 +139,25 @@ public void Add(V component) where V : T componentsByType[type] = new() { component }; } - flatComponents.Add(component); + if (parent is not null) + component.Parent = parent; + comp = component; insertValidator?.Invoke(component); onInserter?.Invoke(component); } /// - /// Adds a component. + /// Adds a component, if the id of the component is not already contained in this component list. /// - /// The component to add if no component of same type is already present. + /// The component to add if no component of same id is already present. /// The type of the component to add. public void AddIfNotExists(V component) where V : T { Type type = component.GetType(); - - if (flatComponents.Contains(component)) + AssignId(component); + ref var comp = ref CollectionsMarshal.GetValueRefOrAddDefault(flatComponents, component.Id, out var exists); + if (exists) return; @@ -137,18 +165,24 @@ public void AddIfNotExists(V component) where V : T { if (!existing.Contains(component)) existing.Add(component); - } else { componentsByType[type] = new() { component }; } - flatComponents.Add(component); + if (parent is not null) + component.Parent = parent; + comp = component; insertValidator?.Invoke(component); onInserter?.Invoke(component); } + private void AssignId(IComponent component) + { + if (component.Id == -1) + component.Id = resourceManager.IdManager.GetNextId(); + } /// /// Checks whether the component of is present. @@ -171,12 +205,25 @@ public bool Contains() return false; } + /// + /// Checks whether the component with is present. + /// + /// + /// + /// if the component was found + /// if the component was not found + /// + /// + public bool Contains(int id) + => flatComponents.ContainsKey(id); + + /// /// Tries to get the component of the given type. /// /// The component type to search for. /// if the component was found; otherwise . - public bool TryGet([MaybeNullWhen(false)] out V component) where V : T + public bool TryGet([MaybeNullWhen(false)] out V component) where V : IComponent { var contains = componentsByType.TryGetValue(typeof(V), out var result); if (!contains || result is null || result.Count < 1) @@ -202,6 +249,30 @@ public bool TryGet([MaybeNullWhen(false)] out V component) where V : T return default; } + /// + /// Get the component of the given type and id. + /// + /// The unique identifier for the component + /// The component type to search for. + /// The component if found; otherwise null. + public V? Get(int id) + { + if (flatComponents.TryGetValue(id, out var result)) + { + return GenericCaster.Cast(result); + } + + return default; + } + + /// + /// Get the component of the given type and id. + /// + /// The unique identifier for the component + /// The component if found; otherwise null. + public IComponent? Get(int id) => flatComponents.TryGetValue(id, out T? result) ? result : (IComponent?)default; + + /// /// Gets a list of components of the given type. /// @@ -222,11 +293,11 @@ public bool TryGet([MaybeNullWhen(false)] out V component) where V : T /// A value indicating whether the remove was successful or not. public bool Remove(V component) where V : T { - if (!flatComponents.Contains(component)) + if (!flatComponents.ContainsKey(component.Id)) return false; removeValidator?.Invoke(component); - if (flatComponents.Remove(component)) + if (flatComponents.Remove(component.Id)) { onRemover?.Invoke(component); @@ -249,6 +320,15 @@ public bool Remove(V component) where V : T /// if was found, otherwise public virtual bool Replace(V toReplace, V replacement, [MaybeNullWhen(false)] out V replaced) where V : T { + AssignId(replacement); + + ref var comp = ref CollectionsMarshal.GetValueRefOrNullRef(flatComponents, toReplace.Id); + if (Unsafe.IsNullRef(ref comp)) + { + replaced = default; + return false; + } + if (!componentsByType.TryGetValue(typeof(V), out var components)) { @@ -265,6 +345,7 @@ public virtual bool Replace(V toReplace, V replacement, [MaybeNullWhen(false) replaced = GenericCaster.Cast(components[index])!; components[index] = replacement; + comp = replacement; return true; } @@ -279,6 +360,14 @@ public virtual bool Replace(V toReplace, V replacement, [MaybeNullWhen(false) public virtual bool ReplaceOrAdd(V? toReplace, V replacement, [MaybeNullWhen(false)] out V replaced) where V : T { replaced = default; + AssignId(replacement); + + replacement.Parent = parent; + + if (toReplace is not null) + flatComponents.Remove(toReplace.Id); + flatComponents[replacement.Id] = replacement; + if (componentsByType.TryGetValue(typeof(V), out var components)) { @@ -308,40 +397,29 @@ public virtual bool ReplaceOrAdd(V? toReplace, V replacement, [MaybeNullWhen( /// The value to replace with. public virtual void ReplaceAllWith(V replacement) where V : T { + AssignId(replacement); + replacement.Parent = parent; if (componentsByType.TryGetValue(typeof(V), out var components)) { if (components.Count > 0) { - components[0] = replacement; - for (int i = components.Count - 1; i >= 1; i--) + for (int i = components.Count - 1; i >= 0; i--) { + var comp = components[i]; + flatComponents.Remove(comp.Id); components.RemoveAt(i); } } - else - components.Add(replacement); + components.Add(replacement); } else { componentsByType[typeof(V)] = new List { replacement }; } + flatComponents[replacement.Id] = replacement; } - /// - /// Serializes the component list to a binary writer. - /// - /// The binary writer to serialize the component list to. - public virtual void Serialize(BinaryWriter writer) - { - writer.Write(flatComponents.Count); - foreach (var component in flatComponents) - { - writer.Write(component.GetType().AssemblyQualifiedName!); - component.Serialize(writer); - } - } - /// /// Deserializes the component list from a binary reader. /// @@ -352,16 +430,50 @@ public virtual void Deserialize(BinaryReader reader) for (int i = 0; i < count; i++) { var name = reader.ReadString(); - var type = Type.GetType(name); + var serId = reader.ReadUInt64(); + + var type = SerializationIdTypeProvider.Get(serId); - Debug.Assert(type != null, nameof(type) + " != null"); + Debug.Assert(type != null, $"{nameof(type)} is null for serid: {serId}"); - if (!componentsByType.TryGetValue(type, out var _)) - componentsByType[type] = new(); + ref var list = ref CollectionsMarshal.GetValueRefOrAddDefault(componentsByType, type, out var exists); + if (!exists || list is null) + list = new(); var component = (T)TypeContainer.GetUnregistered(type); component.Deserialize(reader); - AddIfTypeNotExists(component); + list.Add(component); + flatComponents[component.Id] = component; } } -} + + + /// + /// Serializes the component list to a binary writer. + /// + /// The binary writer to serialize the component list to. + public virtual void Serialize(BinaryWriter writer) + { + writer.Write(flatComponents.Count); + foreach (var keyValuePair in flatComponents) + { + var comp = keyValuePair.Value; + writer.Write(comp.GetType().Name); + writer.Write(comp.GetType().SerializationId()); + comp.Serialize(writer); + } + } + + + /// + /// Deserializes the component list from a binary reader. + /// + /// The binary reader to deserialize the component list from. + public static ComponentList DeserializeStatic(BinaryReader reader) + { + var ret = new ComponentList(); + ret.Deserialize(reader); + return ret; + } + +} \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome/Components/IComponent.cs b/OctoAwesome/OctoAwesome/Components/IComponent.cs index 607cf5c2..62a7e35e 100644 --- a/OctoAwesome/OctoAwesome/Components/IComponent.cs +++ b/OctoAwesome/OctoAwesome/Components/IComponent.cs @@ -7,6 +7,10 @@ namespace OctoAwesome.Components /// public interface IComponent : ISerializable { + /// + /// Gets the globally unique identifier of this component. -1 means unset id + /// + int Id { get; set; } /// /// Gets or sets a value indicating whether this component can be sent. /// @@ -16,5 +20,10 @@ public interface IComponent : ISerializable /// Gets or sets a value indicating whether this component is enabled. /// bool Enabled { get; set; } + + /// + /// Gets or sets the parent that owns this component + /// + IComponentContainer Parent { get; set; } } } diff --git a/OctoAwesome/OctoAwesome/Components/IComponentContainer.cs b/OctoAwesome/OctoAwesome/Components/IComponentContainer.cs index c9a35785..77753c65 100644 --- a/OctoAwesome/OctoAwesome/Components/IComponentContainer.cs +++ b/OctoAwesome/OctoAwesome/Components/IComponentContainer.cs @@ -1,10 +1,22 @@ -namespace OctoAwesome.Components +using System; + +namespace OctoAwesome.Components { /// /// Interface for component containers. /// public interface IComponentContainer { + /// + /// Gets the Id of the container. + /// + Guid Id { get; } + + /// + /// Gets the reference to the active simulation; or null when no simulation is active. + /// + Simulation? Simulation { get; } + /// /// Gets a value indicating whether a component of the given type exists in this container. /// @@ -18,5 +30,12 @@ public interface IComponentContainer /// The type of the component to get. /// The matching component; or null when no matching component was found. T? GetComponent(); + /// + /// Gets a component of the specified type with a known id. + /// + /// The unique identifier for the component + /// The type of the component to get. + /// The matching component; or null when no matching component was found. + T? GetComponent(int id); } } diff --git a/OctoAwesome/OctoAwesome/Components/IEntityComponent.cs b/OctoAwesome/OctoAwesome/Components/IEntityComponent.cs index 65e57b8a..e135250b 100644 --- a/OctoAwesome/OctoAwesome/Components/IEntityComponent.cs +++ b/OctoAwesome/OctoAwesome/Components/IEntityComponent.cs @@ -1,9 +1,11 @@ -namespace OctoAwesome.Components +using OctoAwesome.Serialization; + +namespace OctoAwesome.Components { /// /// Interface for entity components. /// - public interface IEntityComponent : IComponent + public interface IEntityComponent : IComponent, ISerializable { } } diff --git a/OctoAwesome/OctoAwesome/Components/IEntityNotificationComponent.cs b/OctoAwesome/OctoAwesome/Components/IEntityNotificationComponent.cs index 2ac656c0..719680e5 100644 --- a/OctoAwesome/OctoAwesome/Components/IEntityNotificationComponent.cs +++ b/OctoAwesome/OctoAwesome/Components/IEntityNotificationComponent.cs @@ -5,7 +5,7 @@ namespace OctoAwesome.Components /// /// Interface for components for entities to receive notifications. /// - public interface IEntityNotificationComponent : IEntityComponent, INotificationSubject + public interface IEntityNotificationComponent : IEntityComponent { } } diff --git a/OctoAwesome/OctoAwesome/Components/SimulationComponent.cs b/OctoAwesome/OctoAwesome/Components/SimulationComponent.cs index 12260ba5..77344694 100644 --- a/OctoAwesome/OctoAwesome/Components/SimulationComponent.cs +++ b/OctoAwesome/OctoAwesome/Components/SimulationComponent.cs @@ -6,20 +6,21 @@ using System.Collections.Generic; using System.Diagnostics; using System.Threading; +using OctoAwesome.Serialization; +using NonSucking.Framework.Extension.Collections; namespace OctoAwesome.Components { /// /// Baseclass of all SimulationComponents who extend the . /// - public abstract class SimulationComponent : Component + public abstract partial class SimulationComponent : Component { /// /// Update method of this . /// /// The current game time. public abstract void Update(GameTime gameTime); - } /// @@ -181,7 +182,7 @@ public abstract class SimulationComponent /// Entities simulated by this . /// - protected readonly List values = new(); + protected readonly EnumerationModifiableConcurrentList values = new(); /// public void Add(TContainer value) @@ -211,7 +212,14 @@ protected virtual TCachedContainer OnAdd(TContainer value) public void Remove(TContainer value) { OnRemove(value); - values.RemoveAll(c => Compare(c, value)); + IList list = values; + for (int i = list.Count - 1; i >= 0; i--) + { + var c = list[i]; + if (Compare(c, value)) + values.RemoveAt(i); + + } } /// @@ -242,7 +250,6 @@ protected virtual bool Match(TContainer value) /// public override void Update(GameTime gameTime) { - //TODO: Change (Collection was modified in Multiplayer) foreach (var value in values) UpdateValue(gameTime, value); } diff --git a/OctoAwesome/OctoAwesome/Definitions/Items/Item.cs b/OctoAwesome/OctoAwesome/Definitions/Items/Item.cs index 732d5eaa..4a8782bf 100644 --- a/OctoAwesome/OctoAwesome/Definitions/Items/Item.cs +++ b/OctoAwesome/OctoAwesome/Definitions/Items/Item.cs @@ -118,13 +118,7 @@ protected void InternalSerialize(BinaryWriter writer) writer.Write(Position.HasValue); if (Position.HasValue) { - writer.Write(Position.Value.Planet); - writer.Write(Position.Value.GlobalBlockIndex.X); - writer.Write(Position.Value.GlobalBlockIndex.Y); - writer.Write(Position.Value.GlobalBlockIndex.Z); - writer.Write(Position.Value.BlockPosition.X); - writer.Write(Position.Value.BlockPosition.Y); - writer.Write(Position.Value.BlockPosition.Z); + writer.WriteUnmanaged(Position.Value); } } @@ -152,15 +146,7 @@ protected void InternalDeserialize(BinaryReader reader) if (reader.ReadBoolean()) { // Position - int planet = reader.ReadInt32(); - int blockX = reader.ReadInt32(); - int blockY = reader.ReadInt32(); - int blockZ = reader.ReadInt32(); - float posX = reader.ReadSingle(); - float posY = reader.ReadSingle(); - float posZ = reader.ReadSingle(); - - Position = new Coordinate(planet, new Index3(blockX, blockY, blockZ), new Vector3(posX, posY, posZ)); + Position = reader.ReadUnmanaged(); } } diff --git a/OctoAwesome/OctoAwesome/Entity.cs b/OctoAwesome/OctoAwesome/Entity.cs index 105b4088..1524e723 100644 --- a/OctoAwesome/OctoAwesome/Entity.cs +++ b/OctoAwesome/OctoAwesome/Entity.cs @@ -14,14 +14,26 @@ namespace OctoAwesome /// /// Base class for all entities. /// - public abstract class Entity : ComponentContainer + public partial class Entity : ComponentContainer { + + public Entity() : base() + { + + } + public Entity(Guid id, ComponentList components) : base(id, components) + { + + } + /// public override void Update(GameTime gameTime) { base.Update(gameTime); - + Debug.Assert(Simulation != null, nameof(Simulation) + " != null. Entity not part of a simulation."); } + + } } diff --git a/OctoAwesome/OctoAwesome/EntityComponents/AnimationComponent.cs b/OctoAwesome/OctoAwesome/EntityComponents/AnimationComponent.cs index 9c6af846..4c11a69c 100644 --- a/OctoAwesome/OctoAwesome/EntityComponents/AnimationComponent.cs +++ b/OctoAwesome/OctoAwesome/EntityComponents/AnimationComponent.cs @@ -2,6 +2,7 @@ using engenious.Graphics; using OctoAwesome.Components; +using OctoAwesome.Serialization; using System; using System.IO; @@ -11,8 +12,10 @@ namespace OctoAwesome.EntityComponents /// /// Component for animated models. /// - public class AnimationComponent : Component, IEntityComponent + [Nooson, SerializationId()] + public partial class AnimationComponent : Component, IEntityComponent { + /// /// Gets or sets the currently elapsed time for the animation. /// @@ -36,24 +39,6 @@ public AnimationComponent() Sendable = true; } - /// - public override void Serialize(BinaryWriter writer) - { - writer.Write(CurrentTime); - writer.Write(MaxTime); - writer.Write(AnimationSpeed); - base.Serialize(writer); - } - - /// - public override void Deserialize(BinaryReader reader) - { - CurrentTime = reader.ReadSingle(); - MaxTime = reader.ReadSingle(); - AnimationSpeed = reader.ReadSingle(); - base.Deserialize(reader); - } - private float NextSmallerValue(float value) { if (value < 0) @@ -73,6 +58,8 @@ public void Update(GameTime gameTime, Model model) if (model.CurrentAnimation is null) return; + var oldTime = CurrentTime; + CurrentTime = Math.Clamp(CurrentTime + AnimationSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds, 0, NextSmallerValue(MaxTime)); model.UpdateAnimation(CurrentTime); diff --git a/OctoAwesome/OctoAwesome/EntityComponents/BodyComponent.cs b/OctoAwesome/OctoAwesome/EntityComponents/BodyComponent.cs index 009ddf9c..a229c248 100644 --- a/OctoAwesome/OctoAwesome/EntityComponents/BodyComponent.cs +++ b/OctoAwesome/OctoAwesome/EntityComponents/BodyComponent.cs @@ -1,4 +1,5 @@ using OctoAwesome.Components; +using OctoAwesome.Serialization; using System; using System.Collections.Generic; @@ -9,7 +10,8 @@ namespace OctoAwesome.EntityComponents /// /// Component describing the body properties of an entity. /// - public sealed class BodyComponent : Component, IEntityComponent, IEquatable + [Nooson, SerializationId()] + public sealed partial class BodyComponent : Component, IEntityComponent, IEquatable { /// /// Gets or sets the body entity mass. @@ -31,30 +33,12 @@ public sealed class BodyComponent : Component, IEntityComponent, IEquatable public BodyComponent() { + Sendable = true; Mass = 1; //1kg Radius = 1; Height = 1; } - /// - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - - writer.Write(Mass); - writer.Write(Radius); - writer.Write(Height); - } - - /// - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - - Mass = reader.ReadSingle(); - Radius = reader.ReadSingle(); - Height = reader.ReadSingle(); - } /// public override bool Equals(object? obj) diff --git a/OctoAwesome/OctoAwesome/EntityComponents/BoxCollisionComponent.cs b/OctoAwesome/OctoAwesome/EntityComponents/BoxCollisionComponent.cs index a100a182..b82c85e1 100644 --- a/OctoAwesome/OctoAwesome/EntityComponents/BoxCollisionComponent.cs +++ b/OctoAwesome/OctoAwesome/EntityComponents/BoxCollisionComponent.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using OctoAwesome.Database; +using OctoAwesome.Serialization; namespace OctoAwesome.EntityComponents { @@ -12,13 +13,16 @@ namespace OctoAwesome.EntityComponents /// /// Component for entities with box collision. /// - public sealed class BoxCollisionComponent : CollisionComponent, IEquatable + [Nooson, SerializationId()] + public sealed partial class BoxCollisionComponent : CollisionComponent, IEquatable { /// /// Gets the collision bounding boxes of the entity. /// + [NoosonIgnore] public ReadOnlySpan BoundingBoxes => new(boundingBoxes); + [NoosonInclude] private BoundingBox[] boundingBoxes; /// @@ -36,44 +40,9 @@ public BoxCollisionComponent() public BoxCollisionComponent(BoundingBox[] boundingBoxes) { this.boundingBoxes = boundingBoxes; + Sendable = true; } - /// - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - writer.Write(boundingBoxes.Length); - foreach (BoundingBox box in boundingBoxes) - { - writer.Write(box.Min.X); - writer.Write(box.Min.Y); - writer.Write(box.Min.Z); - writer.Write(box.Max.X); - writer.Write(box.Max.Y); - writer.Write(box.Max.Z); - } - } - - /// - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - var len = reader.ReadInt32(); - var boxes = new BoundingBox[len]; - for (int i = 0; i < len; i++) - { - boxes[i] = new BoundingBox( - reader.ReadSingle(), - reader.ReadSingle(), - reader.ReadSingle(), - reader.ReadSingle(), - reader.ReadSingle(), - reader.ReadSingle() - ); - - } - - this.boundingBoxes = boxes; - } + /// public override bool Equals(object? obj) diff --git a/OctoAwesome/OctoAwesome/EntityComponents/ControllableComponent.cs b/OctoAwesome/OctoAwesome/EntityComponents/ControllableComponent.cs index 2f3c7a74..cf307edc 100644 --- a/OctoAwesome/OctoAwesome/EntityComponents/ControllableComponent.cs +++ b/OctoAwesome/OctoAwesome/EntityComponents/ControllableComponent.cs @@ -1,6 +1,7 @@ using engenious; using OctoAwesome.Components; +using OctoAwesome.Serialization; using OctoAwesome.Location; using OctoAwesome.SumTypes; @@ -9,6 +10,7 @@ namespace OctoAwesome.EntityComponents /// /// Component for a controllable entity. /// + [SerializationId()] public class ControllableComponent : Component, IEntityComponent { /// diff --git a/OctoAwesome/OctoAwesome/EntityComponents/HeadComponent.cs b/OctoAwesome/OctoAwesome/EntityComponents/HeadComponent.cs index c0f42adb..4487d153 100644 --- a/OctoAwesome/OctoAwesome/EntityComponents/HeadComponent.cs +++ b/OctoAwesome/OctoAwesome/EntityComponents/HeadComponent.cs @@ -1,13 +1,15 @@ using engenious; using System.IO; using OctoAwesome.Components; +using OctoAwesome.Serialization; namespace OctoAwesome.EntityComponents { /// /// Component describing the head properties of an entity. /// - public sealed class HeadComponent : Component, IEntityComponent + [Nooson, SerializationId()] + public sealed partial class HeadComponent : Component, IEntityComponent { /// /// Gets or sets the offset the head is located at relative to the entity position. @@ -33,32 +35,5 @@ public HeadComponent() } - /// - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - - writer.Write(Offset.X); - writer.Write(Offset.Y); - writer.Write(Offset.Z); - - writer.Write(Tilt); - writer.Write(Angle); - } - - /// - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - - var x = reader.ReadSingle(); - var y = reader.ReadSingle(); - var z = reader.ReadSingle(); - Offset = new Vector3(x, y, z); - - - Tilt = reader.ReadSingle(); - Angle = reader.ReadSingle(); - } } } diff --git a/OctoAwesome/OctoAwesome/EntityComponents/InstanceComponent.cs b/OctoAwesome/OctoAwesome/EntityComponents/InstanceComponent.cs index 39be57e3..3266b77b 100644 --- a/OctoAwesome/OctoAwesome/EntityComponents/InstanceComponent.cs +++ b/OctoAwesome/OctoAwesome/EntityComponents/InstanceComponent.cs @@ -1,10 +1,14 @@ -using OctoAwesome.Notifications; + using OctoAwesome.Serialization; using OctoAwesome.Components; using OctoAwesome.Extension; using System; using System.IO; +using OctoAwesome.Notifications; +using OctoAwesome.Pooling; +using OctoAwesome.Rx; +using System.Diagnostics; namespace OctoAwesome.EntityComponents { @@ -12,36 +16,33 @@ namespace OctoAwesome.EntityComponents /// Base Class for components that need to interact with a component container. /// /// The component container that needs to be interacted with. - public abstract class InstanceComponent : Component, INotificationSubject + [Nooson] + public abstract partial class InstanceComponent : Component where T : ComponentContainer { private T? instance; + private readonly IUpdateHub updateHub; /// /// Gets the reference to the . /// + [NoosonIgnore] public T Instance { get => NullabilityHelper.NotNullAssert(instance, $"{nameof(Instance)} was not initialized!"); private set => instance = NullabilityHelper.NotNullAssert(value, $"{nameof(Instance)} cannot be initialized with null!"); } - /// - /// Gets the unique identifier for the . - /// - public Guid InstanceId { get; set; } - /// - /// Gets the instance type id. - /// - /// - public ulong InstanceTypeId { get; private set; } + private ServerManagedComponent? managedComponent; /// /// Initializes a new instance of the class. /// public InstanceComponent() { + var typeContainer = TypeContainer.Get(); + updateHub = typeContainer.Get(); } /// @@ -52,60 +53,41 @@ public InstanceComponent() /// Thrown if the was already set. public void SetInstance(T value) { - if (value is null) - throw new ArgumentNullException(nameof(value)); - - if (instance?.Id == value.Id) - return; - - var type = value.GetType(); - if (instance != null) - { - throw new NotSupportedException("Can not change the " + type.Name); - } - - InstanceTypeId = type.SerializationId(); - InstanceId = value.Id; - Instance = value; - OnSetInstance(); - } - - /// - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - writer.Write(InstanceId.ToByteArray()); - writer.Write(InstanceTypeId); - } - - /// - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - - InstanceId = new Guid(reader.ReadBytes(16)); - InstanceTypeId = reader.ReadUInt64(); } - /// - /// Gets called when the instance was set to a new value. - /// - protected virtual void OnSetInstance() - { - } - /// - public virtual void OnNotification(SerializableNotification notification) + private void OnSimulationMessage(object obj) { + if (obj is not EntityNotification entityNotification) + return; + if (ParentId == Guid.Empty) + return; + if (entityNotification.EntityId != ParentId) + return; + switch (entityNotification.Type) + { + case EntityNotification.ActionType.Update: + EntityUpdate(entityNotification); + break; + } } - - /// - public virtual void Push(SerializableNotification notification) + private void EntityUpdate(EntityNotification notification) { - instance?.OnNotification(notification); + if (notification.Notification is PropertyChangedNotification changedNotification) + { + if (changedNotification.Issuer == GetType().SerializationId()) + { + managedComponent ??= Instance.GetComponent() ?? new(); + if (!managedComponent.OnServer && Instance is not Player) + _ = Serializer.Deserialize(this, changedNotification.Value); + if (managedComponent.OnServer) + { + updateHub.PushNetwork(notification, DefaultChannels.Simulation); + } + } + } } - } } diff --git a/OctoAwesome/OctoAwesome/EntityComponents/InteractKeyComponent.cs b/OctoAwesome/OctoAwesome/EntityComponents/InteractKeyComponent.cs new file mode 100644 index 00000000..65437170 --- /dev/null +++ b/OctoAwesome/OctoAwesome/EntityComponents/InteractKeyComponent.cs @@ -0,0 +1,19 @@ +using OctoAwesome.Components; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OctoAwesome.EntityComponents; +[Nooson, SerializationId()] +public partial class InteractKeyComponent : Component, IEntityComponent +{ + public string Key { get; set; } + + public InteractKeyComponent() : base() + { + Sendable = true; + } +} \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome/EntityComponents/InventoryComponent.cs b/OctoAwesome/OctoAwesome/EntityComponents/InventoryComponent.cs index 368d4350..dd758de6 100644 --- a/OctoAwesome/OctoAwesome/EntityComponents/InventoryComponent.cs +++ b/OctoAwesome/OctoAwesome/EntityComponents/InventoryComponent.cs @@ -1,4 +1,7 @@ -using OctoAwesome.Components; +using NonSucking.Framework.Extension.Collections; +using NonSucking.Framework.Extension.Threading; + +using OctoAwesome.Components; using OctoAwesome.Database; using OctoAwesome.Definitions; using OctoAwesome.Definitions.Items; @@ -13,12 +16,16 @@ namespace OctoAwesome.EntityComponents { - + /// /// Component for inventories of entities/functional blocks. /// - public class InventoryComponent : Component, IEntityComponent + [SerializationId()] + public partial class InventoryComponent : Component, IEntityComponent, IConstructionSerializable { + /* + TODO Threadsafety? + */ /// /// Gets a list of inventory slots this inventory consists of. /// @@ -65,12 +72,6 @@ public class InventoryComponent : Component, IEntityComponent protected bool HasLimitedVolume => maxVolume != int.MaxValue; - /// - /// Get the current version of the inventory, which increases with every update of any slot - /// - public int Version => version; - - private int version; /// /// Gets or sets if the inventory should be allowed to resize dynamically @@ -90,21 +91,23 @@ public class InventoryComponent : Component, IEntityComponent /// protected int maxVolume = int.MaxValue; - private readonly List inventory; + private readonly EnumerationModifiableConcurrentList inventory; private readonly IDefinitionManager definitionManager; - private int currentWeight = 0; private int currentVolume = 0; - + private ScopedSemaphore serializeSemaphore = new(1, 1); /// /// Initializes a new instance of the class. /// public InventoryComponent() { - inventory = new List(); - definitionManager = TypeContainer.Get(); + inventory = new EnumerationModifiableConcurrentList(); + var tc = TypeContainer.Get(); + definitionManager = tc.Get(); + + Sendable = true; } /// @@ -129,13 +132,14 @@ public InventoryComponent(bool isFixedSlotSize = false, int maxSlots = int.MaxVa /// public override void Deserialize(BinaryReader reader) { + using var l = serializeSemaphore.Wait(); base.Deserialize(reader); - maxWeight = reader.ReadInt32(); maxSlots = reader.ReadInt32(); maxVolume = reader.ReadInt32(); isFixedSlotSize = reader.ReadBoolean(); var count = reader.ReadInt32(); + inventory.Clear(); for (int i = 0; i < count; i++) { var emptySlot = reader.ReadBoolean(); @@ -180,7 +184,6 @@ public override void Deserialize(BinaryReader reader) } } - if (instance is IInventoryable inventoryObject) { inventoryItem = inventoryObject; @@ -195,11 +198,13 @@ public override void Deserialize(BinaryReader reader) inventory.Add(slot); } CalcCurrentInventoryUsage(); + IncrementVersion(true); } /// public override void Serialize(BinaryWriter writer) { + using var l = serializeSemaphore.Wait(); base.Serialize(writer); writer.Write(maxWeight); writer.Write(maxSlots); @@ -231,12 +236,6 @@ public override void Serialize(BinaryWriter writer) } } - /* - TODO Can a slot be in multiple inventories at the same time? Complicated limits, threadsafe and and and - Add some of these methods as a wrapper on the IInventorySlot itself - ✓ TODO Remove all occurences of item? - TODO Threadsafety? - */ /// /// Removes all occurences of the item @@ -290,7 +289,7 @@ public int Remove(IInventoryable item, int quantity) break; } } - Interlocked.Increment(ref version); + IncrementVersion(); return quantity - left; } @@ -325,13 +324,13 @@ public int Remove(IInventorySlot slot, int quantity) switch (invSlot.Amount) { case 0 when isFixedSlotSize: - Interlocked.Increment(ref version); + IncrementVersion(); return quantity; case 0: inventory.Remove(invSlot); break; } - Interlocked.Increment(ref version); + IncrementVersion(); return quantity; } @@ -418,7 +417,7 @@ public int Add(InventorySlot slot, int quantity) quantity = GetQuantityLimitFor(slot, quantity); slot.Amount += quantity; - Interlocked.Increment(ref version); + IncrementVersion(); return quantity; } @@ -514,7 +513,7 @@ private void CalcCurrentInventoryUsage() var slot = new InventorySlot(this); if (Add(slot)) { - Interlocked.Increment(ref version); + IncrementVersion(); return slot; } return null; @@ -530,7 +529,7 @@ public bool Add(InventorySlot slot) if (maxSlots <= inventory.Count || isFixedSlotSize) return false; - Interlocked.Increment(ref version); + IncrementVersion(); inventory.Add(slot); return true; } @@ -573,7 +572,7 @@ public bool Add(InventorySlot slot) { if (index >= inventory.Count) return null; - return inventory[index]; + return inventory.ElementAt(index); } /// @@ -612,5 +611,24 @@ public int RemoveUnit(IInventorySlot slot) return Remove(invSlot, definition.VolumePerUnit); } + /// + public static InventoryComponent DeserializeAndCreate(BinaryReader reader) + { + var ic = new InventoryComponent(); + ic.Deserialize(reader); + return ic; + } + + /// + public static void Serialize(InventoryComponent that, BinaryWriter writer) + { + that.Serialize(writer); + } + + /// + public static void Deserialize(InventoryComponent that, BinaryReader reader) + { + that.Deserialize(reader); + } } } diff --git a/OctoAwesome/OctoAwesome/EntityComponents/LocalChunkCacheComponent.cs b/OctoAwesome/OctoAwesome/EntityComponents/LocalChunkCacheComponent.cs index f3dd43fd..5a446bce 100644 --- a/OctoAwesome/OctoAwesome/EntityComponents/LocalChunkCacheComponent.cs +++ b/OctoAwesome/OctoAwesome/EntityComponents/LocalChunkCacheComponent.cs @@ -7,6 +7,7 @@ namespace OctoAwesome.EntityComponents /// /// Component for the local chunk cache of an entity. /// + [SerializationId()] public sealed class LocalChunkCacheComponent : Component, IEntityComponent { /// diff --git a/OctoAwesome/OctoAwesome/EntityComponents/PositionComponent.cs b/OctoAwesome/OctoAwesome/EntityComponents/PositionComponent.cs index 393b37fc..1b21dc10 100644 --- a/OctoAwesome/OctoAwesome/EntityComponents/PositionComponent.cs +++ b/OctoAwesome/OctoAwesome/EntityComponents/PositionComponent.cs @@ -4,6 +4,9 @@ using OctoAwesome.Location; using OctoAwesome.Notifications; using OctoAwesome.Pooling; +using OctoAwesome.Serialization; +using NonSucking.Framework.Serialization; + using System.IO; namespace OctoAwesome.EntityComponents @@ -11,11 +14,15 @@ namespace OctoAwesome.EntityComponents /// /// Component for entities with an position. /// - public sealed class PositionComponent : InstanceComponent, IEntityComponent + //[NoosonCustom(SerializeMethodName = nameof(Serialize), DeserializeMethodName = nameof(Deserialize))] + [Nooson] + [SerializationId()] + public sealed partial class PositionComponent : Component, IEntityComponent, ISerializable { /// /// Gets or sets the position of the entity. /// + [NoosonIgnore] public Coordinate Position { get => position; @@ -26,10 +33,11 @@ public Coordinate Position var positionBlockX = ((int)(position.BlockPosition.X * 100)) / 100f; var positionBlockY = ((int)(position.BlockPosition.Y * 100)) / 100f; - posUpdate = valueBlockX != positionBlockX || valueBlockY != positionBlockY + posUpdate = valueBlockX != positionBlockX + || valueBlockY != positionBlockY || position.BlockPosition.Z != value.BlockPosition.Z; - - SetValue(ref position, value); + + position = value; planet = TryGetPlanet(value.Planet); } } @@ -37,12 +45,14 @@ public Coordinate Position /// /// Gets or sets the direction the entity is facing. /// + [NoosonOrder(1)] public float Direction { get; set; } /// /// Gets the planet the entity is on. /// + [NoosonIgnore] public IPlanet Planet => planet ??= TryGetPlanet(position.Planet); - + [NoosonInclude, NoosonOrder(0)] private Coordinate position; private bool posUpdate; private IPlanet? planet; @@ -56,39 +66,8 @@ public PositionComponent() { Sendable = true; resourceManager = TypeContainer.Get(); - propertyChangedNotificationPool = TypeContainer.Get>(); } - /// - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - // Position - writer.Write(Position.Planet); - writer.Write(Position.GlobalBlockIndex.X); - writer.Write(Position.GlobalBlockIndex.Y); - writer.Write(Position.GlobalBlockIndex.Z); - writer.Write(Position.BlockPosition.X); - writer.Write(Position.BlockPosition.Y); - writer.Write(Position.BlockPosition.Z); - } - - /// - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - - // Position - int planet = reader.ReadInt32(); - int blockX = reader.ReadInt32(); - int blockY = reader.ReadInt32(); - int blockZ = reader.ReadInt32(); - float posX = reader.ReadSingle(); - float posY = reader.ReadSingle(); - float posZ = reader.ReadSingle(); - - position = new Coordinate(planet, new Index3(blockX, blockY, blockZ), new Vector3(posX, posY, posZ)); - } private IPlanet TryGetPlanet(int planetId) { @@ -97,49 +76,5 @@ private IPlanet TryGetPlanet(int planetId) return resourceManager.GetPlanet(planetId); } - - /// - protected override void OnPropertyChanged(T value, string propertyName) - { - base.OnPropertyChanged(value, propertyName); - - if (propertyName == nameof(Position) && posUpdate) - { - var updateNotification = propertyChangedNotificationPool.Rent(); - - updateNotification.Issuer = nameof(PositionComponent); - updateNotification.Property = propertyName; - - using (var stream = new MemoryStream()) - using (var writer = new BinaryWriter(stream)) - { - Serialize(writer); - updateNotification.Value = stream.ToArray(); - } - - Push(updateNotification); - } - } - - /// - public override void OnNotification(SerializableNotification notification) - { - base.OnNotification(notification); - - if (notification is PropertyChangedNotification changedNotification) - { - if (changedNotification.Issuer == nameof(PositionComponent)) - { - if (changedNotification.Property == nameof(Position)) - { - using (var stream = new MemoryStream(changedNotification.Value)) - using (var reader = new BinaryReader(stream)) - { - Deserialize(reader); - } - } - } - } - } } } diff --git a/OctoAwesome/OctoAwesome/EntityComponents/RenderComponent.cs b/OctoAwesome/OctoAwesome/EntityComponents/RenderComponent.cs index 78a795a4..3625b17f 100644 --- a/OctoAwesome/OctoAwesome/EntityComponents/RenderComponent.cs +++ b/OctoAwesome/OctoAwesome/EntityComponents/RenderComponent.cs @@ -11,7 +11,9 @@ namespace OctoAwesome.EntityComponents /// /// Component for rendering entities. /// - public class RenderComponent : Component, IEntityComponent, IEquatable + [Nooson] + [SerializationId()] + public partial class RenderComponent : Component, IEntityComponent, IEquatable { private string? name, modelName, textureName; @@ -55,26 +57,6 @@ public RenderComponent() Sendable = true; } - /// - public override void Serialize(BinaryWriter writer) - { - writer.Write(Name); - writer.Write(ModelName); - writer.Write(TextureName); - writer.Write(BaseZRotation); - base.Serialize(writer); - } - - /// - public override void Deserialize(BinaryReader reader) - { - Name = reader.ReadString(); - ModelName = reader.ReadString(); - TextureName = reader.ReadString(); - BaseZRotation = reader.ReadSingle(); - base.Deserialize(reader); - } - /// public override bool Equals(object? obj) { diff --git a/OctoAwesome/OctoAwesome/EntityComponents/ServerManagedComponent.cs b/OctoAwesome/OctoAwesome/EntityComponents/ServerManagedComponent.cs new file mode 100644 index 00000000..853f138c --- /dev/null +++ b/OctoAwesome/OctoAwesome/EntityComponents/ServerManagedComponent.cs @@ -0,0 +1,16 @@ +using OctoAwesome.Components; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OctoAwesome.EntityComponents; +[Nooson] +[SerializationId()] +public partial class ServerManagedComponent : Component, IEntityComponent +{ + [NoosonIgnore] + public bool OnServer { get; set; } = false; +} diff --git a/OctoAwesome/OctoAwesome/EntityComponents/TiledPositionComponent.cs b/OctoAwesome/OctoAwesome/EntityComponents/TiledPositionComponent.cs index 18df407e..83b845fa 100644 --- a/OctoAwesome/OctoAwesome/EntityComponents/TiledPositionComponent.cs +++ b/OctoAwesome/OctoAwesome/EntityComponents/TiledPositionComponent.cs @@ -10,6 +10,7 @@ namespace OctoAwesome.EntityComponents; /// /// Restricts an entity to be the only one at a given position component /// +[SerializationId()] public class UniquePositionComponent : Component, IEntityComponent { } diff --git a/OctoAwesome/OctoAwesome/EntityComponents/ToolBarComponent.cs b/OctoAwesome/OctoAwesome/EntityComponents/ToolBarComponent.cs index 4b779af3..708c9481 100644 --- a/OctoAwesome/OctoAwesome/EntityComponents/ToolBarComponent.cs +++ b/OctoAwesome/OctoAwesome/EntityComponents/ToolBarComponent.cs @@ -8,6 +8,7 @@ namespace OctoAwesome.EntityComponents /// /// Component for the toolbar of a player. /// + [SerializationId()] public class ToolBarComponent : Component, IEntityComponent { /// diff --git a/OctoAwesome/OctoAwesome/EntityList.cs b/OctoAwesome/OctoAwesome/EntityList.cs index c323bb72..88ab5b69 100644 --- a/OctoAwesome/OctoAwesome/EntityList.cs +++ b/OctoAwesome/OctoAwesome/EntityList.cs @@ -60,27 +60,6 @@ public bool Remove(Entity item) IEnumerator IEnumerable.GetEnumerator() => entities.GetEnumerator(); - /// - public IEnumerable FailChunkEntity() - { - foreach (var entity in entities) - { - if (entity.Components.Contains()) - { - var position = entity.Components.Get(); - - Debug.Assert(position != null, nameof(position) + " != null"); - if (position.Position.ChunkIndex.X != column.Index.X || position.Position.ChunkIndex.Y != column.Index.Y) - { - yield return new FailEntityChunkArgs( - entity: entity, - currentChunk: column.Index, - currentPlanet: column.Planet, - targetChunk: new Index2(position.Position.ChunkIndex), - targetPlanet: resourceManager.GetPlanet(position.Position.Planet)); - } - } - } - } + } } diff --git a/OctoAwesome/OctoAwesome/Extension.cs b/OctoAwesome/OctoAwesome/Extension.cs index 55fc9a2e..d965abb1 100644 --- a/OctoAwesome/OctoAwesome/Extension.cs +++ b/OctoAwesome/OctoAwesome/Extension.cs @@ -2,6 +2,8 @@ using OctoAwesome.EntityComponents; using OctoAwesome.Extension; +using OctoAwesome.Notifications; +using OctoAwesome.Serialization; namespace OctoAwesome { @@ -31,6 +33,21 @@ public void Register(OctoAwesome.Extension.ExtensionService extensionLoader) } /// - public void Register(ITypeContainer typeContainer) { } + public void Register(ITypeContainer typeContainer) + { + var changedHandler = typeContainer.Get(); + var uh = typeContainer.Get(); + var cc = new ComponentChangeContainer(uh); + + changedHandler.Register("PositionComponent", cc.PositionChanged); + changedHandler.Register("AnimationComponent", cc.AnimationChanged); + changedHandler.Register("InventoryComponent", cc.InventoryChanged); + } + + /// + public void RegisterTypes(ExtensionService extensionLoader) + { + extensionLoader.RegisterTypesWithSerializationId(typeof(CoreExtension).Assembly); + } } } diff --git a/OctoAwesome/OctoAwesome/Extension/DefinitionRegistrar.cs b/OctoAwesome/OctoAwesome/Extension/DefinitionRegistrar.cs index 2b89cd23..b9ce74f1 100644 --- a/OctoAwesome/OctoAwesome/Extension/DefinitionRegistrar.cs +++ b/OctoAwesome/OctoAwesome/Extension/DefinitionRegistrar.cs @@ -31,11 +31,11 @@ public override void Register(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); + if (!type.IsAssignableTo(typeof(IDefinition))) + throw new ArgumentException($"Only types which implement the interface {nameof(IDefinition)} are supported", nameof(type)); var interfaceTypes = type.GetInterfaces(); - //TODO: Check type for idefinition, otherwise throw exception - foreach (var interfaceType in interfaceTypes) { if (definitionsLookup.TryGetValue(interfaceType, out var typeList)) @@ -48,7 +48,7 @@ public override void Register(Type type) } } - definitionTypeContainer.Register(type, type, InstanceBehavior.Singleton); + definitionTypeContainer.Register(type, type, InstanceBehaviour.Singleton); } /// diff --git a/OctoAwesome/OctoAwesome/Extension/ExtensionService.cs b/OctoAwesome/OctoAwesome/Extension/ExtensionService.cs index 6572b73b..023cd24f 100644 --- a/OctoAwesome/OctoAwesome/Extension/ExtensionService.cs +++ b/OctoAwesome/OctoAwesome/Extension/ExtensionService.cs @@ -1,11 +1,15 @@ using Microsoft.Win32; using OctoAwesome.Database; +using OctoAwesome.Serialization; using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -259,4 +263,20 @@ public IEnumerable GetFromRegistrar(string channelName = "") } } + + public void RegisterTypesWithSerializationId(Assembly assembly) + { + var types = assembly.GetCustomAttributes(); + + foreach (var serIdAttribute in types) + { + + var serId = serIdAttribute.CombinedId; + + if (serId > 0) + { + Register(serIdAttribute, ChannelNames.Serialization); + } + } + } } diff --git a/OctoAwesome/OctoAwesome/Extension/NullabilityHelper.cs b/OctoAwesome/OctoAwesome/Extension/NullabilityHelper.cs index 159ecec1..080cc82e 100644 --- a/OctoAwesome/OctoAwesome/Extension/NullabilityHelper.cs +++ b/OctoAwesome/OctoAwesome/Extension/NullabilityHelper.cs @@ -38,7 +38,10 @@ public static T NotNullAssert(T? value, string? message = null, [CallerMember public static T NotNullAssert(T? value, string? message = null, [CallerMemberName] string? parameterName = null) where T : class { - Debug.Assert(value != null, message ?? parameterName + " != null"); + if(value != null) + { + Debug.Assert(true, message ?? parameterName + " != null"); + } return value; } } \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome/Extension/SerializationRegistrar.cs b/OctoAwesome/OctoAwesome/Extension/SerializationRegistrar.cs index cefbae4f..fd7a865c 100644 --- a/OctoAwesome/OctoAwesome/Extension/SerializationRegistrar.cs +++ b/OctoAwesome/OctoAwesome/Extension/SerializationRegistrar.cs @@ -9,47 +9,59 @@ namespace OctoAwesome.Extension /// /// Registrar class for handling types annotated with the . /// - public class SerializationRegistrar : BaseRegistrar + public class SerializationRegistrar : BaseRegistrar { /// public override string ChannelName => ChannelNames.Serialization; - private readonly SerializationIdTypeProvider serializationIdTypeProvider; /// /// Initializes a new instance of the class. /// /// /// The used for registering serialization types. - public SerializationRegistrar(SerializationIdTypeProvider serializationIdTypeProvider) + public SerializationRegistrar() { - this.serializationIdTypeProvider = serializationIdTypeProvider; } /// - /// Registers a Type with the required SerializationId attribute. + /// Registers a Type with the required . /// - /// Type with serialization id attribute - public override void Register(Type type) + /// Type with + public override void Register(BaseSerializationIdAttribute type) { - var serId = type.SerializationId(); + var serId = type.CombinedId; if (serId == 0) - throw new ArgumentException($"Missing {nameof(SerializationIdAttribute)} on type {type.Name}, so it cant be registered."); + throw new ArgumentException($"Missing {nameof(SerializationIdAttribute)} on type {type.Type.Name}, so it cant be registered."); - serializationIdTypeProvider.Register(serId, type); + SerializationIdTypeProvider.Register(serId, type.Type); } + /// + /// Registers a Type without the . + /// + /// Type without + /// The serialization id which normally would be given via + //public void Register(Type type, ulong serializationId) + //{ + + // if (serializationId == 0) + // throw new ArgumentException($"0 is not allowed for a serialization id, because it indicates a missing attribute of {nameof(SerializationIdAttribute)}."); + + // SerializationIdTypeProvider.Register(serializationId, type); + //} + /// /// Not supported /// /// - public override void Unregister(Type value) => throw new NotSupportedException(); + public override void Unregister(BaseSerializationIdAttribute value) => throw new NotSupportedException(); /// /// Not supported, use instead /// /// - public override IReadOnlyCollection Get() => throw new NotSupportedException($"Please use {nameof(SerializationIdTypeProvider)} instead"); + public override IReadOnlyCollection Get() => throw new NotSupportedException($"Please use {nameof(SerializationIdTypeProvider)} instead"); } } diff --git a/OctoAwesome/OctoAwesome/IEntityList.cs b/OctoAwesome/OctoAwesome/IEntityList.cs index a6ab42bb..df6e7362 100644 --- a/OctoAwesome/OctoAwesome/IEntityList.cs +++ b/OctoAwesome/OctoAwesome/IEntityList.cs @@ -7,11 +7,6 @@ namespace OctoAwesome /// public interface IEntityList : ICollection { - /// - /// Gets an enumeration of - /// depicting entities that are not part of this chunk anymore. - /// - /// The entities that are not part of this chunk anymore. - IEnumerable FailChunkEntity(); + } } diff --git a/OctoAwesome/OctoAwesome/IExtension.cs b/OctoAwesome/OctoAwesome/IExtension.cs index 4f070482..c8336f15 100644 --- a/OctoAwesome/OctoAwesome/IExtension.cs +++ b/OctoAwesome/OctoAwesome/IExtension.cs @@ -34,5 +34,6 @@ public interface IExtension /// /// The type container to register the types in. void Register(ITypeContainer typeContainer); + void RegisterTypes(ExtensionService extensionLoader); } } diff --git a/OctoAwesome/OctoAwesome/IIdManager.cs b/OctoAwesome/OctoAwesome/IIdManager.cs new file mode 100644 index 00000000..f96401fb --- /dev/null +++ b/OctoAwesome/OctoAwesome/IIdManager.cs @@ -0,0 +1,7 @@ +namespace OctoAwesome; + +public interface IIdManager +{ + int GetNextId(); + void Init(); +} \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome/IResourceManager.cs b/OctoAwesome/OctoAwesome/IResourceManager.cs index 864851be..1aba4517 100644 --- a/OctoAwesome/OctoAwesome/IResourceManager.cs +++ b/OctoAwesome/OctoAwesome/IResourceManager.cs @@ -3,6 +3,7 @@ using OctoAwesome.Definitions; using OctoAwesome.Location; using OctoAwesome.Notifications; +using OctoAwesome.Serialization; using System; using System.Collections.Concurrent; @@ -15,6 +16,7 @@ namespace OctoAwesome /// public interface IResourceManager { + bool LocalPersistance { get; } /// /// Gets a manager for managing definitions. /// @@ -92,6 +94,16 @@ public interface IResourceManager /// Player CurrentPlayer { get; } + /// + /// Gets the current id manager for managing globally unique ids + /// + IIdManager IdManager { get; } + + /// + /// Manager for game world persistance. + /// + IPersistenceManager PersistenceManager { get; set; } + /// /// Saves the given component container. /// @@ -106,7 +118,7 @@ void SaveComponentContainer(TContainer componentContaine /// Saves the given chunk column. /// /// The chunk column to save. - void SaveChunkColumn(IChunkColumn value); + void SaveChunkColumn(IChunkColumn value, IPlanet planet); /// /// Load a chunk column for a given planet at a location. diff --git a/OctoAwesome/OctoAwesome/ITypeContainer.cs b/OctoAwesome/OctoAwesome/ITypeContainer.cs deleted file mode 100644 index ad87c2c7..00000000 --- a/OctoAwesome/OctoAwesome/ITypeContainer.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; - -namespace OctoAwesome -{ - /// - /// Interface for type containers. - /// - public interface ITypeContainer : IDisposable - { - /// - /// Creates an object of the specified type. - /// - /// The type of the object to create. - /// The created object instance; or null if no instance could be created. - /// - /// The type does not need to be registered, but the constructor parameter types do. - object? CreateObject(Type type); - - /// - /// Creates an instance of the specified type. - /// - /// The type of the object to create. - /// The created object instance; or null if no instance could be created. - /// - /// The type does not need to be registered, but the constructor parameter types do. - T? CreateObject() where T : class; - - /// - /// - /// - /// - /// - /// - void Register(Type registrar, Type type, InstanceBehavior instanceBehavior); - /// - /// - /// - /// - /// - void Register(InstanceBehavior instanceBehavior = InstanceBehavior.Instance) where T : class; - /// - /// - /// - /// - /// - /// - void Register(InstanceBehavior instanceBehavior = InstanceBehavior.Instance) where T : class; - /// - /// - /// - /// - /// - /// - void Register(Type registrar, Type type, object singleton); - /// - /// - /// - /// - /// - void Register(T singleton) where T : class; - /// - /// - /// - /// - /// - /// - void Register(object singleton) where T : class; - - /// - /// - /// - /// - /// - /// - bool TryGet(Type type, [MaybeNullWhen(false)] out object instance); - /// - /// - /// - /// - /// - /// - bool TryGet([MaybeNullWhen(false)] out T instance) where T : class; - - /// - /// - /// - /// - /// - object Get(Type type); - /// - /// - /// - /// - /// - T Get() where T : class; - - /// - /// - /// - /// - /// - object GetUnregistered(Type type); - /// - /// - /// - /// - /// - T GetUnregistered() where T : class; - - /// - /// - /// - /// - /// - object? GetOrNull(Type type); - /// - /// - /// - /// - /// - T? GetOrNull() where T : class; - } -} \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome/InstanceBehavior.cs b/OctoAwesome/OctoAwesome/InstanceBehavior.cs index 8ea3f956..941483df 100644 --- a/OctoAwesome/OctoAwesome/InstanceBehavior.cs +++ b/OctoAwesome/OctoAwesome/InstanceBehavior.cs @@ -1,18 +1,4 @@ namespace OctoAwesome { - /// - /// Enumeration of the type container instance behavior. - /// - public enum InstanceBehavior - { - /// - /// Use instances. - /// - Instance, - /// - /// Use a singleton. - /// - Singleton - } } diff --git a/OctoAwesome/OctoAwesome/InteractService.cs b/OctoAwesome/OctoAwesome/InteractService.cs new file mode 100644 index 00000000..ef557fbc --- /dev/null +++ b/OctoAwesome/OctoAwesome/InteractService.cs @@ -0,0 +1,57 @@ +using engenious; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Markup; + +namespace OctoAwesome; +public class InteractService +{ + Dictionary> registeredActions = new(); + + /// + /// + /// + /// + /// Name of the Class to interact with, first component container is the interactor and second component container the target + public void Register(string key, Action action) + { + ref var val = ref CollectionsMarshal.GetValueRefOrAddDefault(registeredActions, key, out var exists); + if (exists) + { + val += action; + } + else + { + val = action; + } + } + /// + /// + /// + /// + /// Name of the Class to interact with, first component container is the interactor and second component container the target + public void Unregister(string key, Action action) + { + ref var val = ref CollectionsMarshal.GetValueRefOrAddDefault(registeredActions, key, out var exists); + if (exists) + { + val -= action; + } + } + + public void Interact(string key, GameTime gameTime, ComponentContainer interactor, ComponentContainer target) + { + if (registeredActions.TryGetValue(key, out var action)) + { + action(gameTime, interactor, target); + } + } + +} diff --git a/OctoAwesome/OctoAwesome/Location/IMapGenerator.cs b/OctoAwesome/OctoAwesome/Location/IMapGenerator.cs index c0bf7a72..11cbb26a 100644 --- a/OctoAwesome/OctoAwesome/Location/IMapGenerator.cs +++ b/OctoAwesome/OctoAwesome/Location/IMapGenerator.cs @@ -1,5 +1,6 @@ using OctoAwesome.Chunking; using OctoAwesome.Definitions; +using OctoAwesome.Serialization; using System; using System.IO; @@ -9,7 +10,7 @@ namespace OctoAwesome.Location /// /// Interface for OctoAwesome map generators. /// - public interface IMapGenerator + public interface IMapGenerator { /// /// Generates a new planet. diff --git a/OctoAwesome/OctoAwesome/Location/IPlanet.cs b/OctoAwesome/OctoAwesome/Location/IPlanet.cs index d34995d4..d60bc64c 100644 --- a/OctoAwesome/OctoAwesome/Location/IPlanet.cs +++ b/OctoAwesome/OctoAwesome/Location/IPlanet.cs @@ -39,16 +39,19 @@ public interface IPlanet : ISerializable, IDisposable /// /// Gets the climate map for the planet. /// + [NoosonIgnore] IClimateMap ClimateMap { get; } /// /// Gets the map generator for the planet. /// + [NoosonIgnore] IMapGenerator Generator { get; } /// /// Gets the global chunk cache for the planet. /// + [NoosonIgnore] IGlobalChunkCache GlobalChunkCache { get; } } } diff --git a/OctoAwesome/OctoAwesome/Location/Planet.cs b/OctoAwesome/OctoAwesome/Location/Planet.cs index a6e80d3f..856b6022 100644 --- a/OctoAwesome/OctoAwesome/Location/Planet.cs +++ b/OctoAwesome/OctoAwesome/Location/Planet.cs @@ -3,27 +3,31 @@ using System.IO; using System.Text; +using OctoAwesome.Caching; using OctoAwesome.Chunking; using OctoAwesome.Notifications; +using OctoAwesome.Serialization; namespace OctoAwesome.Location { /// /// The default implementation for planets. /// - public class Planet : IPlanet + [Nooson, SerializationId()] + public partial class Planet : IPlanet, IConstructionSerializable { /// /// Backing field for . /// protected IClimateMap? climateMap; /// - public int Id { get; private set; } + public int Id { get; protected set; } /// - public Guid Universe { get; private set; } + public Guid Universe { get; protected set; } /// + [NoosonIgnore] public IClimateMap ClimateMap { get @@ -34,18 +38,20 @@ public IClimateMap ClimateMap } /// - public int Seed { get; private set; } + public int Seed { get; protected set; } /// - public Index3 Size { get; private set; } + public Index3 Size { get; protected set; } /// public float Gravity { get; protected set; } /// + [NoosonCustom(DeserializeMethodName = nameof(DeserializeMapGenerator), SerializeMethodName = nameof(SerializeMapGenerator))] public IMapGenerator Generator { get; } /// + [NoosonIgnore] public IGlobalChunkCache GlobalChunkCache { get; } private bool disposed; @@ -81,33 +87,20 @@ public Planet(int id, Guid universe, Index3 size, IMapGenerator generator, int s public Planet(IMapGenerator generator) { Generator = generator; - - GlobalChunkCache = new GlobalChunkCache(this, TypeContainer.Get(), TypeContainer.Get(), TypeContainer.Get()); + var tc = TypeContainer.Get(); + GlobalChunkCache = new GlobalChunkCache(this, tc.Get(), tc.Get()); } - /// - public virtual void Serialize(BinaryWriter writer) + private void SerializeMapGenerator(BinaryWriter bw) { - writer.Write(Id); - writer.Write(Seed); - writer.Write(Gravity); - writer.Write(Size.X); - writer.Write(Size.Y); - writer.Write(Size.Z); - writer.Write(Universe.ToByteArray()); + bw.Write(Generator.GetType().AssemblyQualifiedName!); } - - /// - public virtual void Deserialize(BinaryReader reader) + private static IMapGenerator DeserializeMapGenerator(BinaryReader br) { - Id = reader.ReadInt32(); - Seed = reader.ReadInt32(); - Gravity = reader.ReadSingle(); - Size = new Index3(reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32()); - Universe = new Guid(reader.ReadBytes(16)); - //var name = reader.ReadString(); + return GenericCaster.Cast(Activator.CreateInstance(Type.GetType(br.ReadString())!)!); } + /// public void Dispose() { diff --git a/OctoAwesome/OctoAwesome/Location/Universe.cs b/OctoAwesome/OctoAwesome/Location/Universe.cs index 52ae59bc..1bab8d8e 100644 --- a/OctoAwesome/OctoAwesome/Location/Universe.cs +++ b/OctoAwesome/OctoAwesome/Location/Universe.cs @@ -2,13 +2,15 @@ using System; using System.IO; using OctoAwesome.Extension; +using OctoAwesome.Serialization; namespace OctoAwesome.Location { /// /// A universe of OctoAwesome. A universe contains multiple planets and is a save state. /// - public class Universe : IUniverse + [Nooson] + public partial class Universe : IUniverse, IConstructionSerializable { private string? name; @@ -45,22 +47,6 @@ public Universe(Guid id, string name, int seed) Seed = seed; } - /// - public void Deserialize(BinaryReader reader) - { - var tmpGuid = reader.ReadString(); - Id = new Guid(tmpGuid); - Name = reader.ReadString(); - Seed = reader.ReadInt32(); - } - - /// - public void Serialize(BinaryWriter writer) - { - writer.Write(Id.ToString()); - writer.Write(Name); - writer.Write(Seed); - } } diff --git a/OctoAwesome/OctoAwesome/NetworkingSimulationComponent.cs b/OctoAwesome/OctoAwesome/NetworkingSimulationComponent.cs new file mode 100644 index 00000000..88593c6d --- /dev/null +++ b/OctoAwesome/OctoAwesome/NetworkingSimulationComponent.cs @@ -0,0 +1,80 @@ +using engenious; + +using NonSucking.Framework.Extension.Collections; + +using OctoAwesome.Rx; +using OctoAwesome.Components; +using OctoAwesome.Notifications; +using OctoAwesome.Pooling; +using OctoAwesome.Serialization; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Security.AccessControl; +using System.IO; + +namespace OctoAwesome +{ + + + public class GlobalComponentContainer : ComponentList + { + public static GlobalComponentContainer Instance { get; } = new GlobalComponentContainer(); + } + + public class NetworkingSimulationComponent + { + EnumerationModifiableConcurrentList dirties = new(); + private readonly IPool entityNotificationPool; + private readonly IUpdateHub updateHub; + private readonly IPool propertyChangedNotificationPool; + + public NetworkingSimulationComponent(IUpdateHub uh, IPool poolEn, IPool poolPcn) + { + entityNotificationPool = poolEn; + updateHub = uh; + propertyChangedNotificationPool = poolPcn; + } + + public void Update(GameTime gameTime) + { + foreach (var item in dirties) + { + if (item.Sendable + && item.Parent is Entity e) + { + SendComponent(e, item); + } + dirties.Remove(item); + } + } + + public void Add(IComponent clean) + { + dirties.Add(clean); + } + + public void Remove(IComponent cleanOrDirty) + { + dirties.Remove(cleanOrDirty); + } + + private void SendComponent(Entity entity, IComponent component) + { + var updateNotification = propertyChangedNotificationPool.Rent(); + + updateNotification.Issuer = component.GetType().SerializationId(); + updateNotification.ComponentId = component.Id; + updateNotification.Value = Serializer.Serialize(component).ToArray(); + + var entityNotification = entityNotificationPool.Rent(); + entityNotification.Entity = entity; + entityNotification.Type = EntityNotification.ActionType.Update; + entityNotification.Notification = updateNotification; + + updateHub.PushNetwork(entityNotification, DefaultChannels.Simulation); + entityNotification.Release(); + } + } +} diff --git a/OctoAwesome/OctoAwesome/Notifications/BlockChangedNotification.cs b/OctoAwesome/OctoAwesome/Notifications/BlockChangedNotification.cs index c1d49562..65564383 100644 --- a/OctoAwesome/OctoAwesome/Notifications/BlockChangedNotification.cs +++ b/OctoAwesome/OctoAwesome/Notifications/BlockChangedNotification.cs @@ -1,4 +1,6 @@ -using OctoAwesome.Information; +using OctoAwesome.Serialization; + +using OctoAwesome.Information; using OctoAwesome.Location; using System; @@ -9,7 +11,8 @@ namespace OctoAwesome.Notifications /// /// Notification for a changed block. /// - public sealed class BlockChangedNotification : SerializableNotification, IChunkNotification + [Nooson, SerializationId()] + public sealed partial class BlockChangedNotification : SerializableNotification, IChunkNotification, IConstructionSerializable { /// /// Gets or sets the block info of the changed block. @@ -22,45 +25,7 @@ public sealed class BlockChangedNotification : SerializableNotification, IChunkN /// public int Planet { get; internal set; } - /// - public override void Deserialize(BinaryReader reader) - { - if (reader.ReadByte() != (byte)BlockNotificationType.BlockChanged)//Read type of the notification - { - throw new InvalidCastException("this is the wrong type of notification"); - } - - BlockInfo = new BlockInfo( - x: reader.ReadInt32(), - y: reader.ReadInt32(), - z: reader.ReadInt32(), - block: reader.ReadUInt16(), - meta: reader.ReadInt32()); - - ChunkPos = new Index3( - reader.ReadInt32(), - reader.ReadInt32(), - reader.ReadInt32()); - - Planet = reader.ReadInt32(); - } - - /// - public override void Serialize(BinaryWriter writer) - { - writer.Write((byte)BlockNotificationType.BlockChanged); //indicate that this is a single Block Notification - - writer.Write(BlockInfo.Position.X); - writer.Write(BlockInfo.Position.Y); - writer.Write(BlockInfo.Position.Z); - writer.Write(BlockInfo.Block); - writer.Write(BlockInfo.Meta); - - writer.Write(ChunkPos.X); - writer.Write(ChunkPos.Y); - writer.Write(ChunkPos.Z); - writer.Write(Planet); - } + /// protected override void OnRelease() diff --git a/OctoAwesome/OctoAwesome/Notifications/BlocksChangedNotification.cs b/OctoAwesome/OctoAwesome/Notifications/BlocksChangedNotification.cs index c37854fc..a251a2ae 100644 --- a/OctoAwesome/OctoAwesome/Notifications/BlocksChangedNotification.cs +++ b/OctoAwesome/OctoAwesome/Notifications/BlocksChangedNotification.cs @@ -7,20 +7,22 @@ using System.Collections.Generic; using System.IO; using OctoAwesome.Extension; +using OctoAwesome.Serialization; namespace OctoAwesome.Notifications { /// /// Notification for changed blocks. /// - public sealed class BlocksChangedNotification : SerializableNotification, IChunkNotification + [Nooson, SerializationId()] + public sealed partial class BlocksChangedNotification : SerializableNotification, IChunkNotification, IConstructionSerializable { - private ICollection? blockInfos; + private BlockInfo[]? blockInfos; /// /// Gets or sets the collection of block info of the changed blocks. /// - public ICollection BlockInfos + public BlockInfo[] BlockInfos { get => NullabilityHelper.NotNullAssert(blockInfos, $"{nameof(BlockInfos)} was not initialized!"); set => blockInfos = NullabilityHelper.NotNullAssert(value, $"{nameof(BlockInfos)} cannot be initialized with null!"); @@ -32,54 +34,6 @@ public ICollection BlockInfos /// public int Planet { get; internal set; } - /// - public override void Deserialize(BinaryReader reader) - { - if (reader.ReadByte() != (byte)BlockNotificationType.BlocksChanged)//Read type of the notification - { - throw new InvalidCastException("this is the wrong type of notification"); - } - - ChunkPos = new Index3( - reader.ReadInt32(), - reader.ReadInt32(), - reader.ReadInt32()); - - Planet = reader.ReadInt32(); - var count = reader.ReadInt32(); - var list = new List(count); - for (int i = 0; i < count; i++) - { - list.Add(new BlockInfo( - x: reader.ReadInt32(), - y: reader.ReadInt32(), - z: reader.ReadInt32(), - block: reader.ReadUInt16(), - meta: reader.ReadInt32())); - } - - BlockInfos = list; - } - - /// - public override void Serialize(BinaryWriter writer) - { - writer.Write((byte)BlockNotificationType.BlocksChanged); //indicate that this is a multi Block Notification - writer.Write(ChunkPos.X); - writer.Write(ChunkPos.Y); - writer.Write(ChunkPos.Z); - writer.Write(Planet); - - writer.Write(BlockInfos.Count); - foreach (var block in BlockInfos) - { - writer.Write(block.Position.X); - writer.Write(block.Position.Y); - writer.Write(block.Position.Z); - writer.Write(block.Block); - writer.Write(block.Meta); - } - } /// protected override void OnRelease() diff --git a/OctoAwesome/OctoAwesome/Notifications/ChatNotification.cs b/OctoAwesome/OctoAwesome/Notifications/ChatNotification.cs new file mode 100644 index 00000000..3bf93999 --- /dev/null +++ b/OctoAwesome/OctoAwesome/Notifications/ChatNotification.cs @@ -0,0 +1,26 @@ +using OctoAwesome.Serialization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OctoAwesome.Notifications; + +[Nooson, SerializationId()] +public partial class ChatNotification : SerializableNotification, IConstructionSerializable +{ + public string Text { get; set; } + public string Username { get; set; } + public DateTimeOffset TimeStamp { get; set; } = DateTimeOffset.Now; + + protected override void OnRelease() + { + Text = ""; + Username = ""; + TimeStamp = DateTimeOffset.MinValue; + + base.OnRelease(); + } +} diff --git a/OctoAwesome/OctoAwesome/Notifications/DefaultChannels.cs b/OctoAwesome/OctoAwesome/Notifications/DefaultChannels.cs index 7dafb657..b77be322 100644 --- a/OctoAwesome/OctoAwesome/Notifications/DefaultChannels.cs +++ b/OctoAwesome/OctoAwesome/Notifications/DefaultChannels.cs @@ -20,13 +20,19 @@ public static class DefaultChannels /// public const string Simulation = "simulation"; - /// - /// The ui channel. - /// - public const string UI = "ui"; /// /// The chunk channel. /// public const string Chunk = "chunk"; + + /// + /// The planet channel. + /// + public const string Planet = "planet"; + + /// + /// The channel for all chat messages + /// + public const string Chat = "chat"; } } diff --git a/OctoAwesome/OctoAwesome/Notifications/EntityNotification.cs b/OctoAwesome/OctoAwesome/Notifications/EntityNotification.cs index 0bc99cf5..9ee33be9 100644 --- a/OctoAwesome/OctoAwesome/Notifications/EntityNotification.cs +++ b/OctoAwesome/OctoAwesome/Notifications/EntityNotification.cs @@ -10,7 +10,8 @@ namespace OctoAwesome.Notifications /// /// Notifications caused by entities. /// - public sealed class EntityNotification : SerializableNotification + [SerializationId()] + public sealed partial class EntityNotification : SerializableNotification, IConstructionSerializable { /// /// Gets or sets the action type that caused the notification. @@ -25,13 +26,13 @@ public sealed class EntityNotification : SerializableNotification /// /// Gets or sets the entity that caused the notification. /// - public Entity Entity + public Entity? Entity { - get => NullabilityHelper.NotNullAssert(entity, $"{nameof(Entity)} was not initialized!"); + get => entity; set { - entity = NullabilityHelper.NotNullAssert(value, $"{nameof(Entity)} cannot be initialized with null!"); - EntityId = value.Id; + entity = value; + EntityId = value?.Id ?? Guid.Empty; } } @@ -80,11 +81,14 @@ public override void Deserialize(BinaryReader reader) { Type = (ActionType)reader.ReadInt32(); - if (Type == ActionType.Add) + { + //Entity = new RemoteEntity(); + //Entity.Deserialize(reader); Entity = Serializer.Deserialize(reader.ReadBytes(reader.ReadInt32())); + } else - EntityId = new Guid(reader.ReadBytes(16)); + EntityId = reader.ReadUnmanaged(); var isNotification = reader.ReadBoolean(); if (isNotification) @@ -105,7 +109,7 @@ public override void Serialize(BinaryWriter writer) } else { - writer.Write(EntityId.ToByteArray()); + writer.WriteUnmanaged(EntityId); } var subNotification = Notification != null; @@ -118,6 +122,7 @@ public override void Serialize(BinaryWriter writer) } } + /// protected override void OnRelease() { @@ -130,6 +135,23 @@ protected override void OnRelease() base.OnRelease(); } + public static EntityNotification DeserializeAndCreate(BinaryReader reader) + { + var entity = new EntityNotification(); + entity.Deserialize(reader); + return entity; + } + + public static void Serialize(EntityNotification that, BinaryWriter writer) + { + that.Serialize(writer); + } + + public static void Deserialize(EntityNotification that, BinaryReader reader) + { + that.Deserialize(reader); + } + /// /// Enumeration of entity notification action types. /// diff --git a/OctoAwesome/OctoAwesome/Notifications/INotificationSubject.cs b/OctoAwesome/OctoAwesome/Notifications/INotificationSubject.cs deleted file mode 100644 index 87b1d04d..00000000 --- a/OctoAwesome/OctoAwesome/Notifications/INotificationSubject.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace OctoAwesome.Notifications -{ - /// - /// Interface for receiving notifications of a specific subject type. - /// - /// The subject notification type. - public interface INotificationSubject where TNotification : Notification - { - /// - /// Gets called when a notification is received. - /// - /// The notification that was received. - void OnNotification(TNotification notification); - - /// - /// Push a new notification to be handled. - /// - /// The new notification to be handled. - void Push(TNotification notification); - } -} diff --git a/OctoAwesome/OctoAwesome/Notifications/IUpdateHub.cs b/OctoAwesome/OctoAwesome/Notifications/IUpdateHub.cs index b541bedd..585dd741 100644 --- a/OctoAwesome/OctoAwesome/Notifications/IUpdateHub.cs +++ b/OctoAwesome/OctoAwesome/Notifications/IUpdateHub.cs @@ -16,13 +16,21 @@ public interface IUpdateHub /// A reference to an interface that allows observers to stop receiving notifications /// before the provider has finished sending them. /// - IDisposable AddSource(IObservable notification, string channel); + IDisposable AddSource(IObservable notification, string channel, bool sendOverNetwork = false); /// /// Gets an observable to listen for notifications on the specified channel. /// /// The channel to listen on for notifications. /// An observable to listen for notifications on the specified channel. - IObservable ListenOn(string channel); - } + IObservable ListenOn(string channel); + + /// + /// Creates a listener on the network channel + /// + /// + IObservable ListenOnNetwork(); + void Push(object notification, string channel); + void PushNetwork(object notification, string channel); + } } diff --git a/OctoAwesome/OctoAwesome/Notifications/Notification.cs b/OctoAwesome/OctoAwesome/Notifications/Notification.cs index 2ee8b19f..bcb36fc9 100644 --- a/OctoAwesome/OctoAwesome/Notifications/Notification.cs +++ b/OctoAwesome/OctoAwesome/Notifications/Notification.cs @@ -11,9 +11,11 @@ public abstract class Notification : IPoolElement /// /// Gets or sets the id of the sender of this notification. /// + [NoosonIgnore] public uint SenderId { get; set; } private IPool? pool; + [NoosonIgnore] private IPool Pool { get => NullabilityHelper.NotNullAssert(pool, $"{nameof(IPoolElement)} was not initialized!"); diff --git a/OctoAwesome/OctoAwesome/Notifications/PropertyChangedNotification.cs b/OctoAwesome/OctoAwesome/Notifications/PropertyChangedNotification.cs index 0bfdfab4..9e2812dd 100644 --- a/OctoAwesome/OctoAwesome/Notifications/PropertyChangedNotification.cs +++ b/OctoAwesome/OctoAwesome/Notifications/PropertyChangedNotification.cs @@ -1,33 +1,24 @@ using System.IO; + using OctoAwesome.Extension; +using OctoAwesome.Serialization; namespace OctoAwesome.Notifications { /// /// Notification for a property change. /// - public class PropertyChangedNotification : SerializableNotification + [Nooson] + public partial class PropertyChangedNotification : SerializableNotification, IConstructionSerializable { - private string? issuer, property; private byte[]? value; + private int componentId; /// /// Gets or sets the name of the issuer that caused the property change. /// - public string Issuer - { - get => NullabilityHelper.NotNullAssert(issuer, $"{nameof(Issuer)} was not initialized!"); - set => issuer = NullabilityHelper.NotNullAssert(value, $"{nameof(Issuer)} cannot be initialized with null!"); - } + public ulong Issuer { get; set; } - /// - /// Gets or sets the name of the property that was changed. - /// - public string Property - { - get => NullabilityHelper.NotNullAssert(property, $"{nameof(Property)} was not initialized!"); - set => property = NullabilityHelper.NotNullAssert(value, $"{nameof(Property)} cannot be initialized with null!"); - } /// /// Gets or sets the raw data of the new property value. @@ -38,30 +29,22 @@ public byte[] Value set => this.value = NullabilityHelper.NotNullAssert(value, $"{nameof(Value)} cannot be initialized with null!"); } - /// - public override void Deserialize(BinaryReader reader) + /// + /// Gets or sets the raw data of the new property value. + /// + public int ComponentId { - Issuer = reader.ReadString(); - Property = reader.ReadString(); - var count = reader.ReadInt32(); - Value = reader.ReadBytes(count); + get => componentId; + set => this.componentId = value; } - /// - public override void Serialize(BinaryWriter writer) - { - writer.Write(Issuer); - writer.Write(Property); - writer.Write(Value.Length); - writer.Write(Value); - } /// protected override void OnRelease() { - issuer = default; - property = default; + Issuer = 0; value = default; + componentId = default; base.OnRelease(); } diff --git a/OctoAwesome/OctoAwesome/Notifications/StableCompositeDisposable.cs b/OctoAwesome/OctoAwesome/Notifications/StableCompositeDisposable.cs new file mode 100644 index 00000000..102b21d0 --- /dev/null +++ b/OctoAwesome/OctoAwesome/Notifications/StableCompositeDisposable.cs @@ -0,0 +1,53 @@ +using System; + +namespace OctoAwesome.Notifications; + +/// +/// Holds multiple disposables to dispose together +/// +public class StableCompositeDisposable : IDisposable +{ + private readonly IDisposable[] disposables; + + private bool disposedValue; + + private StableCompositeDisposable(IDisposable[] disposables) + { + this.disposables = disposables; + } + + /// + /// Creates an with the disposables + /// + /// The item to dispose + /// The other item to dispose + /// as an IDisposable + public static IDisposable Create(IDisposable disposable1, IDisposable disposable2) + { + return new StableCompositeDisposable(new[] { disposable1, disposable2 }); + } + + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + foreach (var item in disposables) + { + item.Dispose(); + } + } + + disposedValue = true; + } + } + + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome/Notifications/UpdateHub.cs b/OctoAwesome/OctoAwesome/Notifications/UpdateHub.cs index cc7e4e09..acdd2ac3 100644 --- a/OctoAwesome/OctoAwesome/Notifications/UpdateHub.cs +++ b/OctoAwesome/OctoAwesome/Notifications/UpdateHub.cs @@ -1,16 +1,20 @@ using OctoAwesome.Rx; using OctoAwesome.Threading; + using System; using System.Collections.Generic; namespace OctoAwesome.Notifications { + public record struct PushInfo(object Notification, string Channel); + /// /// Update hub implementation for managing observers and observables in a thread safe manner. /// public class UpdateHub : IDisposable, IUpdateHub { - private readonly Dictionary> channels; + private readonly Dictionary> channels; + private readonly ConcurrentRelay networkChannel; private readonly LockSemaphore lockSemaphore; /// @@ -20,17 +24,33 @@ public UpdateHub() { channels = new(); lockSemaphore = new LockSemaphore(1, 1); + networkChannel = new ConcurrentRelay(); } /// - public IObservable ListenOn(string channel) + public IObservable ListenOn(string channel) => GetChannelRelay(channel); /// - public IDisposable AddSource(IObservable notification, string channel) - => notification.Subscribe(GetChannelRelay(channel)); + public IObservable ListenOnNetwork() => networkChannel; + + /// + public IDisposable AddSource(IObservable notification, string channel, bool sendOverNetwork = false) + { + if (sendOverNetwork) + { + return StableCompositeDisposable.Create( + notification.Subscribe(GetChannelRelay(channel)), + notification.Subscribe(x => networkChannel.OnNext(new(x, channel)))); + } + else + { + return notification.Subscribe(GetChannelRelay(channel)); + } + } + - private ConcurrentRelay GetChannelRelay(string channel) + public ConcurrentRelay GetChannelRelay(string channel) { using var scope = lockSemaphore.Wait(); @@ -43,7 +63,18 @@ private ConcurrentRelay GetChannelRelay(string channel) return channelRelay; } - /// + + public void PushNetwork(object notification, string channel) + { + networkChannel.OnNext(new PushInfo(notification, channel)); + } + + public void Push(object notification, string channel) + { + if (channels.TryGetValue(channel, out var channelRelay)) + channelRelay.OnNext(notification); + } + public void Dispose() { foreach (var channel in channels) @@ -53,7 +84,8 @@ public void Dispose() channels.Clear(); lockSemaphore.Dispose(); + networkChannel?.Dispose(); } - } + } diff --git a/OctoAwesome/OctoAwesome/OctoAwesome.csproj b/OctoAwesome/OctoAwesome/OctoAwesome.csproj index a0b0bf12..ac83a057 100644 --- a/OctoAwesome/OctoAwesome/OctoAwesome.csproj +++ b/OctoAwesome/OctoAwesome/OctoAwesome.csproj @@ -4,8 +4,10 @@ net7.0 enable True + CS0109 + - + @@ -16,12 +18,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome/Player.cs b/OctoAwesome/OctoAwesome/Player.cs index 1997a485..772dc1ed 100644 --- a/OctoAwesome/OctoAwesome/Player.cs +++ b/OctoAwesome/OctoAwesome/Player.cs @@ -3,45 +3,46 @@ using OctoAwesome.Notifications; using OctoAwesome.Pooling; using OctoAwesome.Serialization; +using NonSucking.Framework.Serialization; +using OctoAwesome.EntityComponents; +using OctoAwesome.Components; +using System.IO; +using OctoAwesome.Extension; +using System; namespace OctoAwesome { + + /// /// Entity, that the user can control using input devices. /// - [SerializationId(1, 1)] - public sealed class Player : Entity + [SerializationId()] + [Nooson] + public partial class Player : Entity, IConstructionSerializable { /// /// The range the user can interact with in game elements e.g. and . /// public const int SELECTIONRANGE = 8; - private readonly IPool entityNotificationPool; + /// + /// Gets or Sets the Name of this player + /// + [NoosonIgnore] + public string Name { get; set; } /// /// Initializes a new instance of the class. /// public Player() { - entityNotificationPool = TypeContainer.Get>(); } - /// - public override void OnNotification(SerializableNotification notification) + public Player(Guid id, ComponentList components) : base(id, components) { - base.OnNotification(notification); - - var entityNotification = entityNotificationPool.Rent(); - entityNotification.Entity = this; - entityNotification.Type = EntityNotification.ActionType.Update; - entityNotification.Notification = notification as PropertyChangedNotification; - - Simulation?.OnUpdate(entityNotification); - entityNotification.Release(); } - /// - protected override void OnInteract(GameTime gameTime, Entity entity) => throw new System.NotImplementedException(); + } } diff --git a/OctoAwesome/OctoAwesome/Pooling/ChunkPool.cs b/OctoAwesome/OctoAwesome/Pooling/ChunkPool.cs index 73ead0a5..ff664241 100644 --- a/OctoAwesome/OctoAwesome/Pooling/ChunkPool.cs +++ b/OctoAwesome/OctoAwesome/Pooling/ChunkPool.cs @@ -35,10 +35,10 @@ public Chunk Rent() /// Retrieves an element from the memory pool. /// /// The position to initialize the pooled element with. - /// The planet to initialize the pooled element with. + /// The planet to initialize the pooled element with. /// The pooled element that can be used thereon. /// Use to return the object back into the memory pool. - public Chunk Rent(Index3 position, IPlanet planet) + public Chunk Rent(Index3 position, int planetId) { Chunk obj; @@ -47,13 +47,19 @@ public Chunk Rent(Index3 position, IPlanet planet) if (internalStack.Count > 0) obj = internalStack.Pop(); else - obj = new Chunk(position, planet); + obj = new Chunk(position, planetId); } - obj.Init(position, planet); + obj.Init(position, planetId); return obj; } + /// + public IPoolElement RentElement() + { + throw new NotSupportedException($"Use Get(Index3, IPlanet) instead."); + } + /// public void Return(Chunk obj) diff --git a/OctoAwesome/OctoAwesome/Pooling/IPool.cs b/OctoAwesome/OctoAwesome/Pooling/IPool.cs index d4f2fd21..b87e4684 100644 --- a/OctoAwesome/OctoAwesome/Pooling/IPool.cs +++ b/OctoAwesome/OctoAwesome/Pooling/IPool.cs @@ -10,6 +10,14 @@ public interface IPool /// /// The element to return into the memory pool. void Return(IPoolElement obj); + + /// + /// Retrieves an element from the memory pool. + /// + /// The pooled element that can be used thereon. + /// Use to return the object back into the memory pool. + IPoolElement RentElement(); + } /// /// Interface for generic memory pools. diff --git a/OctoAwesome/OctoAwesome/Pooling/Pool.cs b/OctoAwesome/OctoAwesome/Pooling/Pool.cs index 4d58500d..2c55209b 100644 --- a/OctoAwesome/OctoAwesome/Pooling/Pool.cs +++ b/OctoAwesome/OctoAwesome/Pooling/Pool.cs @@ -48,6 +48,12 @@ public T Rent() return obj; } + /// + public IPoolElement RentElement() + { + return Rent(); + } + /// public void Return(T obj) { @@ -67,5 +73,6 @@ public void Return(IPoolElement obj) throw new InvalidCastException("Can not push object from type: " + obj.GetType()); } } + } } diff --git a/OctoAwesome/OctoAwesome/RemoteEntity.cs b/OctoAwesome/OctoAwesome/RemoteEntity.cs index 81ff7446..f81e8988 100644 --- a/OctoAwesome/OctoAwesome/RemoteEntity.cs +++ b/OctoAwesome/OctoAwesome/RemoteEntity.cs @@ -1,51 +1,50 @@ using engenious; +using OctoAwesome.Serialization; + using System.IO; +using NonSucking.Framework.Serialization; +using OctoAwesome.Components; +using System; + namespace OctoAwesome { /// /// Entity that is simulated on a remote server. /// - public class RemoteEntity : Entity + public partial class RemoteEntity : Entity { /// /// Initializes a new instance of the class. /// - public RemoteEntity() + public RemoteEntity() : base() { + } + public RemoteEntity(Guid id, ComponentList components) : base(id, components) + { } /// /// Initializes a new instance of the class. /// /// The origin entity that is controlled by the remote server. - public RemoteEntity(Entity originEntity) + public RemoteEntity(Entity originEntity) : this() { - foreach (var component in Components) + Simulation = originEntity.Simulation; + using var ms = new MemoryStream(); + using var bw = new BinaryWriter(ms); + originEntity.Components.Serialize(bw); + ms.Position = 0; + using var br = new BinaryReader(ms); + var components = ComponentList.DeserializeStatic(br); + foreach (var component in components) { if (component.Sendable) - Components.AddIfTypeNotExists(component); + Components.Add(component); } Id = originEntity.Id; } - - /// - public override void Serialize(BinaryWriter writer) - { - Components.Serialize(writer); - base.Serialize(writer); - } - - /// - public override void Deserialize(BinaryReader reader) - { - Components.Deserialize(reader); - base.Deserialize(reader); - } - - /// - protected override void OnInteract(GameTime gameTime, Entity entity) => throw new System.NotImplementedException(); } } diff --git a/OctoAwesome/OctoAwesome/Rx/ConcurrentRelay.cs b/OctoAwesome/OctoAwesome/Rx/ConcurrentRelay.cs index 5ca4175f..82751158 100644 --- a/OctoAwesome/OctoAwesome/Rx/ConcurrentRelay.cs +++ b/OctoAwesome/OctoAwesome/Rx/ConcurrentRelay.cs @@ -1,4 +1,8 @@ -using OctoAwesome.Threading; +using NonSucking.Framework.Extension.Collections; + +using OctoAwesome.Logging; +using OctoAwesome.Threading; + using System; using System.Collections.Generic; @@ -11,7 +15,8 @@ namespace OctoAwesome.Rx /// public class ConcurrentRelay : IObservable, IObserver, IDisposable { - private readonly List subscriptions; + private readonly EnumerationModifiableConcurrentList subscriptions; + private readonly ILogger logger; private readonly LockSemaphore lockSemaphore; /// @@ -21,6 +26,7 @@ public ConcurrentRelay() { lockSemaphore = new LockSemaphore(1, 1); subscriptions = new(); + logger = TypeContainer.Get().As(nameof(ConcurrentRelay)); } /// @@ -28,9 +34,9 @@ public void OnCompleted() { using var scope = lockSemaphore.Wait(); - for (int i = 0; i < subscriptions.Count; i++) + foreach (var sub in subscriptions) { - subscriptions[i].Observer.OnCompleted(); + sub.Observer.OnCompleted(); } } @@ -39,9 +45,9 @@ public void OnError(Exception error) { using var scope = lockSemaphore.Wait(); - for (int i = 0; i < subscriptions.Count; i++) + foreach (var sub in subscriptions) { - subscriptions[i].Observer.OnError(error); + sub.Observer.OnError(error); } } @@ -49,10 +55,11 @@ public void OnError(Exception error) public void OnNext(T value) { using var scope = lockSemaphore.Wait(); - - for (int i = 0; i < subscriptions.Count; i++) + logger.Trace($"Got lock, dispatching {value} to {subscriptions.Count} subs"); + + foreach (var sub in subscriptions) { - subscriptions[i].Observer.OnNext(value); + sub.Observer.OnNext(value); } } @@ -61,8 +68,7 @@ public IDisposable Subscribe(IObserver observer) { var sub = new RelaySubscription(this, observer); - using (var scope = lockSemaphore.Wait()) - subscriptions.Add(sub); + subscriptions.Add(sub); return sub; } @@ -70,16 +76,12 @@ public IDisposable Subscribe(IObserver observer) /// public void Dispose() { - subscriptions.Clear(); lockSemaphore.Dispose(); } private void Unsubscribe(RelaySubscription subscription) { - - using var scope = lockSemaphore.Wait(); - subscriptions.Remove(subscription); } diff --git a/OctoAwesome/OctoAwesome/Rx/IObservableExtension.cs b/OctoAwesome/OctoAwesome/Rx/IObservableExtension.cs index 3c77ab76..3da519c4 100644 --- a/OctoAwesome/OctoAwesome/Rx/IObservableExtension.cs +++ b/OctoAwesome/OctoAwesome/Rx/IObservableExtension.cs @@ -19,6 +19,17 @@ public static class IObservableExtension public static IDisposable Subscribe(this IObservable observable, Action onNext) => observable.Subscribe(new Observer(onNext)); + /// + /// Subscribes an element handler to an observable sequence and disposes after once onNext. + /// + /// The relay to subscribe to. + /// Action to invoke for each element in the observable sequence. + /// The object type that provides notification information. + + public static void SubscribeOnce(this Relay observable, Action onNext) + => observable.SubscribeOnce(new Observer(onNext)); + + /// /// Subscribes an element handler and an exception handler handler to an observable sequence. /// diff --git a/OctoAwesome/OctoAwesome/Rx/Relay.cs b/OctoAwesome/OctoAwesome/Rx/Relay.cs index 0f7201c3..024eeaa7 100644 --- a/OctoAwesome/OctoAwesome/Rx/Relay.cs +++ b/OctoAwesome/OctoAwesome/Rx/Relay.cs @@ -1,4 +1,6 @@ -using System; +using NonSucking.Framework.Extension.Collections; + +using System; using System.Collections.Generic; namespace OctoAwesome.Rx @@ -9,7 +11,8 @@ namespace OctoAwesome.Rx /// The type of the observable and observed data. public class Relay : IObservable, IObserver, IDisposable { - private readonly List subscriptions; + private readonly EnumerationModifiableList subscriptions; + private readonly EnumerationModifiableList oneShotSubscriptions; /// /// Initializes a new instance of the class. @@ -17,42 +20,57 @@ public class Relay : IObservable, IObserver, IDisposable public Relay() { subscriptions = new(); + oneShotSubscriptions = new(); } /// public void OnCompleted() { - for (int i = 0; i < subscriptions.Count; i++) + foreach (RelaySubscription sub in subscriptions) { - subscriptions[i].Observer.OnCompleted(); + sub.Observer.OnCompleted(); } } /// public void OnError(Exception error) { - for (int i = 0; i < subscriptions.Count; i++) + foreach (RelaySubscription sub in subscriptions) { - subscriptions[i].Observer.OnError(error); + sub.Observer.OnError(error); } } /// public void OnNext(T value) { - for (int i = 0; i < subscriptions.Count; i++) + foreach (RelaySubscription sub in subscriptions) + { + sub.Observer.OnNext(value); + } + + foreach (RelaySubscription sub in oneShotSubscriptions) { - subscriptions[i].Observer.OnNext(value); + sub.Observer.OnNext(value); + sub.Dispose(); } } /// public IDisposable Subscribe(IObserver observer) { - var sub = new RelaySubscription(this, observer); + var sub = new RelaySubscription(subscriptions, observer); subscriptions.Add(sub); return sub; } + /// + public void SubscribeOnce(IObserver observer) + { +#pragma warning disable DF0010 // Marks undisposed local variables. + var sub = new RelaySubscription(oneShotSubscriptions, observer); +#pragma warning restore DF0010 // Marks undisposed local variables. + oneShotSubscriptions.Add(sub); + } /// public void Dispose() @@ -75,18 +93,17 @@ private class RelaySubscription : IDisposable { public IObserver Observer { get; } - private readonly Relay relay; + private readonly IList parent; - public RelaySubscription(Relay relay, IObserver observer) + public RelaySubscription(IList parent,IObserver observer) { - this.relay = relay; - Observer = observer; + this.parent = parent; } public void Dispose() { - relay.Unsubscribe(this); + parent.Remove(this); } } } diff --git a/OctoAwesome/OctoAwesome/Serialization/ChunkColumnDbContext.cs b/OctoAwesome/OctoAwesome/Serialization/ChunkColumnDbContext.cs index 58803b8b..17883da3 100644 --- a/OctoAwesome/OctoAwesome/Serialization/ChunkColumnDbContext.cs +++ b/OctoAwesome/OctoAwesome/Serialization/ChunkColumnDbContext.cs @@ -2,6 +2,7 @@ using OctoAwesome.Database; using OctoAwesome.Location; +using System; using System.IO; using System.IO.Compression; @@ -43,15 +44,16 @@ public override void AddOrUpdate(IChunkColumn value) if (!Database.ContainsKey(key)) return null; - var chunkColumn = new ChunkColumn(currentPlanet); - using (var stream = new MemoryStream(Database.GetValue(key).Content)) - using (var zip = new GZipStream(stream, CompressionMode.Decompress)) - using (var buffered = new BufferedStream(zip)) - using (var reader = new BinaryReader(buffered)) - { - chunkColumn.Deserialize(reader); - return chunkColumn; - } + var chunkColumn = new ChunkColumn(currentPlanet.Id); + using var stream + = Serializer + .Manager + .GetStream($"{nameof(ChunkColumnDbContext)}.{nameof(Get)}", Database.GetValue(key).Content.AsSpan()); + using var zip = new GZipStream(stream, CompressionMode.Decompress); + using var buffered = new BufferedStream(zip); + using var reader = new BinaryReader(buffered); + chunkColumn.Deserialize(reader); + return chunkColumn; } /// diff --git a/OctoAwesome/OctoAwesome/Serialization/ChunkDiffDbContext.cs b/OctoAwesome/OctoAwesome/Serialization/ChunkDiffDbContext.cs index 33e3389a..9b2c2331 100644 --- a/OctoAwesome/OctoAwesome/Serialization/ChunkDiffDbContext.cs +++ b/OctoAwesome/OctoAwesome/Serialization/ChunkDiffDbContext.cs @@ -3,6 +3,7 @@ using OctoAwesome.Information; using OctoAwesome.Notifications; using OctoAwesome.Pooling; + using System.Collections.Generic; using System.IO; using System.Linq; @@ -89,7 +90,7 @@ private void InternalRemove(ChunkDiffTag tag) private void InternalAddOrUpdate(ChunkDiffTag tag, BlockInfo blockInfo) { - using (var memory = new MemoryStream()) + using (var memory = Serializer.Manager.GetStream()) using (var writer = new BinaryWriter(memory)) { BlockInfo.Serialize(writer, blockInfo); @@ -100,7 +101,8 @@ private void InternalAddOrUpdate(ChunkDiffTag tag, BlockInfo blockInfo) private BlockInfo InternalGet(ChunkDiffTag tag) { Value value = Database.GetValue(tag); - using (var memory = new MemoryStream(value.Content)) + + using (var memory = Serializer.Manager.GetStream(value.Content)) using (var reader = new BinaryReader(memory)) { return BlockInfo.Deserialize(reader); diff --git a/OctoAwesome/OctoAwesome/Serialization/ComponentChangedNotificationHandler.cs b/OctoAwesome/OctoAwesome/Serialization/ComponentChangedNotificationHandler.cs new file mode 100644 index 00000000..2f0cc947 --- /dev/null +++ b/OctoAwesome/OctoAwesome/Serialization/ComponentChangedNotificationHandler.cs @@ -0,0 +1,142 @@ +using engenious; + +using OctoAwesome.Components; +using OctoAwesome.EntityComponents; +using OctoAwesome.Logging; +using OctoAwesome.Notifications; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace OctoAwesome.Serialization; +internal class ComponentChangedNotificationHandler +{ + public Simulation? AssociatedSimulation { get; set; } + + private readonly IUpdateHub updateHub; + private readonly ILogger logger; + private readonly Dictionary> actionHandlers; + + public ComponentChangedNotificationHandler(IUpdateHub updatehub, ILogger logger) + { + actionHandlers = new() + { + }; + updateHub = updatehub; + this.logger = logger.As(typeof(ComponentChangedNotificationHandler)); + } + + + public void Execute(Entity entity, EntityNotification entityNotitification) + { + if (entityNotitification.Notification is not PropertyChangedNotification propChanged) + { + return; + } + logger.Trace($"Rec {nameof(EntityNotification)} of entity {entity.Id} with type {propChanged.Issuer}"); + if (actionHandlers.TryGetValue(SerializationIdTypeProvider.Get(propChanged.Issuer).Name, out var handler)) + { + handler.Invoke(this, entity, entityNotitification, propChanged); + } + } + + /// + /// + /// + /// + /// Name of the Class to interact with, first component container is the interactor and second component container the target + public void Register(string key, Action action) + { + ref var val = ref CollectionsMarshal.GetValueRefOrNullRef(actionHandlers, key); + if (Unsafe.IsNullRef(ref val)) + { + actionHandlers[key] = action; + } + else + { + val += action; + } + } + /// + /// + /// + /// + /// Name of the Class to interact with, first component container is the interactor and second component container the target + public void Unregister(string key, Action action) + { + ref var val = ref CollectionsMarshal.GetValueRefOrNullRef(actionHandlers, key); + if (!Unsafe.IsNullRef(ref val)) + { + val -= action; + } + } + + /// + /// Gets the component from the or the if is null + /// + /// + /// + /// + /// + public T? GetComponent(Entity e, int componentId) where T : IComponent + { + return AssociatedSimulation is not null + ? AssociatedSimulation.GlobalComponentList.Get(componentId) + : e.GetComponent(componentId); + } +} + +internal class ComponentChangeContainer +{ + private readonly IUpdateHub updateHub; + + public ComponentChangeContainer(IUpdateHub updateHub) + { + this.updateHub = updateHub; + } + + internal void AnimationChanged(ComponentChangedNotificationHandler handler, Entity entity, EntityNotification notification, PropertyChangedNotification propertyChangedNotification) + { + var comp = handler.GetComponent(entity, propertyChangedNotification.ComponentId); + if (comp is null) + return; + + _ = Serializer.Deserialize(comp, propertyChangedNotification.Value); + } + + + internal void InventoryChanged(ComponentChangedNotificationHandler handler, Entity entity, EntityNotification notification, PropertyChangedNotification propertyChangedNotification) + { + var servcomp = entity.GetComponent(); + var comp = handler.GetComponent(entity, propertyChangedNotification.ComponentId); + if (comp is null) + return; + + _ = Serializer.Deserialize(comp, propertyChangedNotification.Value); + if (servcomp is { OnServer: true }) + { + updateHub.PushNetwork(notification, DefaultChannels.Simulation); + } + } + + internal void PositionChanged(ComponentChangedNotificationHandler handler, Entity entity, EntityNotification notification, PropertyChangedNotification propertyChangedNotification) + { + var comp = entity.GetComponent(); + var posComp = handler.GetComponent(entity, propertyChangedNotification.ComponentId); + if (posComp is null) + return; + + _ = Serializer.Deserialize(posComp, propertyChangedNotification.Value); + + if (comp is { OnServer: true }) + { + updateHub.PushNetwork(notification, DefaultChannels.Simulation); + } + } +} diff --git a/OctoAwesome/OctoAwesome/Serialization/Entities/ComponentContainerComponentDbContext.cs b/OctoAwesome/OctoAwesome/Serialization/Entities/ComponentContainerComponentDbContext.cs index f13eceb1..afbda273 100644 --- a/OctoAwesome/OctoAwesome/Serialization/Entities/ComponentContainerComponentDbContext.cs +++ b/OctoAwesome/OctoAwesome/Serialization/Entities/ComponentContainerComponentDbContext.cs @@ -10,7 +10,7 @@ namespace OctoAwesome.Serialization.Entities /// Database context for components in component containers. /// /// The component type of the values in the database. - public sealed class ComponentContainerComponentDbContext where TComponent : IComponent + public sealed class ComponentContainerComponentDbContext where TComponent : IComponent, ISerializable { private readonly IDatabaseProvider databaseProvider; private readonly Guid universeGuid; @@ -34,7 +34,7 @@ public ComponentContainerComponentDbContext(IDatabaseProvider databaseProvider, /// The component to add or update. /// The component container to add or update the component in. /// The type of the component to add or update. - public void AddOrUpdate(T value, ComponentContainer entity) where T : IComponent + public void AddOrUpdate(T value, ComponentContainer entity) where T : IComponent, ISerializable { Database> database = databaseProvider.GetDatabase>(universeGuid, false); var tag = new GuidTag(entity.Id); diff --git a/OctoAwesome/OctoAwesome/Serialization/Entities/ComponentContainerDbContext.cs b/OctoAwesome/OctoAwesome/Serialization/Entities/ComponentContainerDbContext.cs index 5bc40aa3..0dbc2f34 100644 --- a/OctoAwesome/OctoAwesome/Serialization/Entities/ComponentContainerDbContext.cs +++ b/OctoAwesome/OctoAwesome/Serialization/Entities/ComponentContainerDbContext.cs @@ -15,7 +15,7 @@ namespace OctoAwesome.Serialization.Entities public sealed class ComponentContainerDbContext : IDatabaseContext, TContainer> where TContainer : ComponentContainer - where TComponent : IComponent + where TComponent : IComponent, ISerializable { private readonly ComponentContainerDefinition.ComponentContainerDefinitionContext entityDefinitionContext; private readonly ComponentContainerComponentDbContext componentsDbContext; diff --git a/OctoAwesome/OctoAwesome/Serialization/Entities/ComponentContainerDefinition.cs b/OctoAwesome/OctoAwesome/Serialization/Entities/ComponentContainerDefinition.cs index 4e3dd619..f7cfbd7a 100644 --- a/OctoAwesome/OctoAwesome/Serialization/Entities/ComponentContainerDefinition.cs +++ b/OctoAwesome/OctoAwesome/Serialization/Entities/ComponentContainerDefinition.cs @@ -13,7 +13,7 @@ namespace OctoAwesome.Serialization.Entities /// Component container definition for instances. /// /// The type of the components contained in the container definition. - public sealed class ComponentContainerDefinition : ISerializable where TComponent : IComponent + public sealed class ComponentContainerDefinition : ISerializable where TComponent : IComponent, ISerializable { private IEnumerable? components; private Type? type; diff --git a/OctoAwesome/OctoAwesome/Serialization/ISerializable.cs b/OctoAwesome/OctoAwesome/Serialization/ISerializable.cs index c917db94..e9c94202 100644 --- a/OctoAwesome/OctoAwesome/Serialization/ISerializable.cs +++ b/OctoAwesome/OctoAwesome/Serialization/ISerializable.cs @@ -7,16 +7,53 @@ namespace OctoAwesome.Serialization /// public interface ISerializable { + /// /// Serialize this instance to a . /// /// The binary writer to write the serialized instance to. void Serialize(BinaryWriter writer); - /// - /// Deserializes from a into this instance. + /// Deserialize this instance from a . /// /// The binary reader to read the serialized instance from. void Deserialize(BinaryReader reader); } + + /// + /// Used for serialization and deserialization. + /// + /// The own type which should be deserialized + public interface ISerializable: ISerializable + { + /// + /// Serialize the instance of to a . + /// + /// The instance to serialize. + /// The binary writer to write the serialized instance to. + abstract static void Serialize(T that, BinaryWriter writer); + /// + /// Deserializes from a into the instance . + /// + /// The instance to deserialize into. + /// The binary reader to read the serialized instance from. + abstract static void Deserialize(T that, BinaryReader reader); + } + + + /// + /// Used for serialization and deserialization. + /// + /// The own type which should be deserialized + public interface IConstructionSerializable : ISerializable + { + + /// + /// Deserializes and creates an instance from a . + /// + /// The binary reader to read the serialized instance from. + /// The newly created instance + abstract static T DeserializeAndCreate(BinaryReader reader); + + } } \ No newline at end of file diff --git a/OctoAwesome/OctoAwesome/Serialization/ISerializableEnumerable.cs b/OctoAwesome/OctoAwesome/Serialization/ISerializableEnumerable.cs deleted file mode 100644 index 0fa48692..00000000 --- a/OctoAwesome/OctoAwesome/Serialization/ISerializableEnumerable.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; - -namespace OctoAwesome.Serialization -{ - /// - /// Interface for serializable enumerable. - /// - /// The type of items in the enumeration. - public interface ISerializableEnumerable : IEnumerable, ISerializable where T : ISerializable - { - } -} diff --git a/OctoAwesome/OctoAwesome/Serialization/SerializableCollection.cs b/OctoAwesome/OctoAwesome/Serialization/SerializableCollection.cs index e7ab65eb..f9982b6d 100644 --- a/OctoAwesome/OctoAwesome/Serialization/SerializableCollection.cs +++ b/OctoAwesome/OctoAwesome/Serialization/SerializableCollection.cs @@ -7,21 +7,45 @@ namespace OctoAwesome.Serialization /// A collection that can be serialized with the OctoAwesome serializer. /// /// The type of elements in the collection. - public class SerializableCollection : Collection, ISerializableEnumerable where T : ISerializable + + public partial class SerializableCollection : Collection, IConstructionSerializable> where T : ISerializable { + /// - public void Deserialize(BinaryReader reader) + public static SerializableCollection DeserializeAndCreate(BinaryReader reader) { - foreach (var item in this) + var collection = new SerializableCollection(); + Deserialize(collection, reader); + return collection; + } + + /// + public static void Deserialize(SerializableCollection that, BinaryReader reader) + { + var count = reader.ReadInt32(); + foreach (var item in that) item.Deserialize(reader); + + } + + /// + public static void Serialize(SerializableCollection that, BinaryWriter writer) + { + that.Serialize(writer); + } + + /// + public void Deserialize(BinaryReader reader) + { + Deserialize(this, reader); } /// public void Serialize(BinaryWriter writer) { + writer.Write(Count); foreach (var item in this) item.Serialize(writer); } - } } diff --git a/OctoAwesome/OctoAwesome/Serialization/SerializationIdAttribute.cs b/OctoAwesome/OctoAwesome/Serialization/SerializationIdAttribute.cs index 7d2f120f..9581852d 100644 --- a/OctoAwesome/OctoAwesome/Serialization/SerializationIdAttribute.cs +++ b/OctoAwesome/OctoAwesome/Serialization/SerializationIdAttribute.cs @@ -1,12 +1,42 @@ using System; -namespace OctoAwesome.Serialization +namespace OctoAwesome { /// - /// Attribute for associating serialization ids to types for generic serialization across multiple mods. + /// Attribute for telling the non existent source code generator, that a type id for this type should be generated. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] public class SerializationIdAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + public SerializationIdAttribute() + { + } + } + + /// + /// Base class for the + /// + public abstract class BaseSerializationIdAttribute : Attribute + { + /// + /// Gets the serialization id + /// + public abstract ulong CombinedId { get; } + + /// + /// Gets the type for which this serialzation id attribute should be used + /// + public abstract Type Type { get; } + } + + /// + /// Attribute for associating serialization ids to types for generic serialization across multiple mods. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = true)] + public class SerializationIdAttribute : BaseSerializationIdAttribute { /// /// Gets the id for the mod. @@ -21,10 +51,15 @@ public class SerializationIdAttribute : Attribute /// /// Gets the id combined from and . /// - public ulong CombinedId { get; } + public override ulong CombinedId { get; } + + /// + /// Gets the type for which this serialzation id attribute should be used + /// + public override Type Type => typeof(T); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The id for the mod. /// The id for the type in the mod. diff --git a/OctoAwesome/OctoAwesome/Serialization/Serializer.cs b/OctoAwesome/OctoAwesome/Serialization/Serializer.cs index c7ee9837..16a06ba5 100644 --- a/OctoAwesome/OctoAwesome/Serialization/Serializer.cs +++ b/OctoAwesome/OctoAwesome/Serialization/Serializer.cs @@ -1,4 +1,8 @@ -using OctoAwesome.Pooling; +using Microsoft.IO; + +using OctoAwesome.Pooling; + +using System; using System.IO; using System.IO.Compression; using System.Text; @@ -10,6 +14,11 @@ namespace OctoAwesome.Serialization /// public static class Serializer { + /// + /// Instance of the for pooling memory streams. + /// + public static RecyclableMemoryStreamManager Manager { get; } = new RecyclableMemoryStreamManager(); + /// /// Serializes a generic serializable instance to an array of bytes. /// @@ -18,12 +27,10 @@ public static class Serializer /// The serialized byte array data. public static byte[] Serialize(T obj) where T : ISerializable { - using (var stream = new MemoryStream()) - using (var writer = new BinaryWriter(stream)) - { - obj.Serialize(writer); - return stream.ToArray(); - } + using var stream = Manager.GetStream(nameof(Serialize)); + using var writer = new BinaryWriter(stream); + obj.Serialize(writer); + return stream.ToArray(); } /// @@ -34,20 +41,16 @@ public static byte[] Serialize(T obj) where T : ISerializable /// The compressed serialized byte array data. public static byte[] SerializeCompressed(T obj) where T : ISerializable { - using (var stream = new MemoryStream()) - { - using (var writer = new BinaryWriter(stream, Encoding.Default, true)) - obj.Serialize(writer); - - using (var ms = new MemoryStream()) - { - stream.Position = 0; - using (var zip = new GZipStream(ms, CompressionMode.Compress, true)) - stream.CopyTo(zip); - - return ms.ToArray(); - } - } + using var stream = Manager.GetStream(nameof(SerializeCompressed)); + using (var writer = new BinaryWriter(stream, Encoding.Default, true)) + obj.Serialize(writer); + + using var ms = Manager.GetStream(nameof(SerializeCompressed)); + stream.Position = 0; + using (var zip = new GZipStream(ms, CompressionMode.Compress, true)) + stream.CopyTo(zip); + + return ms.ToArray(); } /// @@ -59,37 +62,130 @@ public static byte[] SerializeCompressed(T obj) where T : ISerializable /// The compressed serialized byte array data. public static byte[] SerializeCompressed(T obj, int capacity) where T : ISerializable { - using (var stream = new MemoryStream(capacity)) - using (var zip = new GZipStream(stream, CompressionMode.Compress)) - using (var buff = new BufferedStream(zip)) - { - using (var writer = new BinaryWriter(buff, Encoding.Default, true)) - obj.Serialize(writer); - - return stream.ToArray(); - } + using var stream = Manager.GetStream(nameof(SerializeCompressed), capacity); + using var zip = new GZipStream(stream, CompressionMode.Compress); + using var buff = new BufferedStream(zip); + using (var writer = new BinaryWriter(buff, Encoding.Default, true)) + obj.Serialize(writer); + + return stream.ToArray(); } + + + /// + /// Serializes a generic serializable instance to an array of bytes to send over network by included the value. + /// + /// The object to serialize. + /// The type of the object to serialize. + /// The serialized byte array data. + public static byte[] SerializeNetwork(T obj) where T : ISerializable + { + using var stream = Manager.GetStream(nameof(Serialize)); + using var writer = new BinaryWriter(stream); + writer.Write(obj.GetType().SerializationId()); + obj.Serialize(writer); + return stream.ToArray(); + } + + /// /// Deserializes a generic deserializable instance from an array of bytes. /// /// The data to deserialize the instance from. /// The type of the object to deserialize. /// The deserialized object. - public static T Deserialize(byte[] data) where T : ISerializable, new() + public static T Deserialize(Span data) where T : ISerializable, new() { var obj = new T(); InternalDeserialize(ref obj, data); return obj; } + /// + /// Deserializes a response from network into a deserializable instance from an array of bytes while reading the . + /// + /// The data to deserialize the instance from. + /// The type of the object to deserialize. + /// The deserialized object. + public static T DeserializeNetwork(Span data) where T : ISerializable, new() + { + var typeId = BitConverter.ToUInt64(data); + if (typeof(T).SerializationId() != typeId) + throw new ArgumentException($"The provided data contained the type id {typeId} which doesn't match with the provided generic types type id {typeof(T).SerializationId()}", nameof(data)); + + var obj = new T(); + InternalDeserialize(ref obj, data[sizeof(long)..]); + return obj; + } + + /// + /// Deserializes a generic deserializable instance from an array of bytes. + /// + /// The data to deserialize the instance from. + /// The type of the object to deserialize. + /// The deserialized object. + public static T DeserializeSpecialCtor(Span data) where T : IConstructionSerializable + { + using var stream = Manager.GetStream(data); + using var reader = new BinaryReader(stream); + + return T.DeserializeAndCreate(reader); + } + + /// + /// Deserializes a generic deserializable instance from an array of bytes. + /// + /// The data to deserialize the instance from. + /// The type of the object to deserialize. + /// The deserialized object. + public static T DeserializeSpecialCtorNetwork(Span data) where T : IConstructionSerializable + { + var typeId = BitConverter.ToUInt64(data); + if (typeof(T).SerializationId() != typeId) + throw new ArgumentException($"The provided data contained the type id {typeId} which doesn't match with the provided generic types type id {typeof(T).SerializationId()}", nameof(data)); + + return DeserializeSpecialCtor(data[sizeof(long)..]); + } + + /// + /// Deserializes a generic deserializable instance from an array of bytes. + /// + /// Existing instance to deserialize into. + /// The data to deserialize the instance from. + /// The type of the object to deserialize. + /// The deserialized object. + public static T Deserialize(T instance, Span data) where T : ISerializable + { + InternalDeserialize(ref instance, data); + return instance; + } + + + /// + /// Deserializes a response from network into a deserializable instance from an array of bytes while reading the . + /// + /// Existing instance to deserialize into. + /// The data to deserialize the instance from. + /// The type of the object to deserialize. + /// The deserialized object. + public static T DeserializeNetwork(T instance, Span data) where T : ISerializable + { + var typeId = BitConverter.ToUInt64(data); + if (typeof(T).SerializationId() != typeId) + throw new ArgumentException($"The provided data contained the type id {typeId} which doesn't match with the provided generic types type id {typeof(T).SerializationId()}", nameof(data)); + + InternalDeserialize(ref instance, data[sizeof(long)..]); + return instance; + } /// /// Deserializes a generic deserializable instance from a compressed array of bytes. /// /// The compressed data to deserialize the instance from. /// The type of the object to deserialize. /// The deserialized object. - public static T DeserializeCompressed(byte[] data) where T : ISerializable, new() + public static T DeserializeCompressed(Span data) where T : ISerializable, new() { var obj = new T(); InternalDeserializeCompressed(ref obj, data); @@ -102,7 +198,7 @@ public static byte[] SerializeCompressed(T obj, int capacity) where T : ISeri /// The data to deserialize the instance from. /// The type of the object to serialize. /// The deserialized object from the memory pool. - public static T DeserializePoolElement(byte[] data) where T : ISerializable, IPoolElement, new() + public static T DeserializePoolElement(Span data) where T : ISerializable, IPoolElement, new() { var obj = TypeContainer.Get>().Rent(); InternalDeserialize(ref obj, data); @@ -117,30 +213,26 @@ public static byte[] SerializeCompressed(T obj, int capacity) where T : ISeri /// The data to deserialize the instance from. /// The type of the object to serialize. /// The deserialized object from the memory pool. - public static T DeserializePoolElement(IPool pool, byte[] data) where T : ISerializable, IPoolElement, new() + public static T DeserializePoolElement(IPool pool, Span data) where T : ISerializable, IPoolElement, new() { var obj = pool.Rent(); InternalDeserialize(ref obj, data); return obj; } - private static void InternalDeserialize(ref T instance, byte[] data) where T : ISerializable + private static void InternalDeserialize(ref T instance, Span data) where T : ISerializable { - using (var stream = new MemoryStream(data)) - using (var reader = new BinaryReader(stream)) - { - instance.Deserialize(reader); - } + using var stream = Manager.GetStream(data); + using var reader = new BinaryReader(stream); + instance.Deserialize(reader); } - private static void InternalDeserializeCompressed(ref T instance, byte[] data) where T : ISerializable + private static void InternalDeserializeCompressed(ref T instance, Span data) where T : ISerializable { - using (var stream = new MemoryStream(data)) - using (var zip = new GZipStream(stream, CompressionMode.Decompress)) - using (var reader = new BinaryReader(zip)) - { - instance.Deserialize(reader); - } + using var stream = Manager.GetStream(data); + using var zip = new GZipStream(stream, CompressionMode.Decompress); + using var reader = new BinaryReader(zip); + instance.Deserialize(reader); } } } diff --git a/OctoAwesome/OctoAwesome/Serialization/TypeExtension.cs b/OctoAwesome/OctoAwesome/Serialization/TypeExtension.cs index 64573d37..49e2c7d9 100644 --- a/OctoAwesome/OctoAwesome/Serialization/TypeExtension.cs +++ b/OctoAwesome/OctoAwesome/Serialization/TypeExtension.cs @@ -9,21 +9,19 @@ namespace OctoAwesome.Serialization public static class TypeExtension { /// - /// Gets the from this + /// Gets the from this /// or 0 if no is associated with this . /// /// The to get the id for. /// - /// The read ; + /// The read ; /// or 0 if no is associated with this . /// public static ulong SerializationId(this Type type) { - var attr = type.GetCustomAttribute(); - if (attr is null) + if (!SerializationIdTypeProvider.TryGet(type, out var serId)) return 0; - - return attr.CombinedId; + return serId; } } } diff --git a/OctoAwesome/OctoAwesome/SerializationIdTypeProvider.cs b/OctoAwesome/OctoAwesome/SerializationIdTypeProvider.cs index ee8544fe..2f5723cf 100644 --- a/OctoAwesome/OctoAwesome/SerializationIdTypeProvider.cs +++ b/OctoAwesome/OctoAwesome/SerializationIdTypeProvider.cs @@ -8,18 +8,20 @@ namespace OctoAwesome /// /// Type provider for serialization ids. /// - public sealed class SerializationIdTypeProvider + public static class SerializationIdTypeProvider { - private readonly Dictionary types = new(); + private readonly static Dictionary idToTypes = new(); + private readonly static Dictionary typesToId = new(); /// /// Registers a type associated to a given serialization id(). /// /// The serialization id to associated the to. /// The type to register with the serialization id association. - public void Register(ulong serId, Type type) + public static void Register(ulong serId, Type type) { - types.Add(serId, type); + idToTypes.Add(serId, type); + typesToId.Add(type, serId); } /// @@ -30,9 +32,22 @@ public void Register(ulong serId, Type type) /// /// Thrown when no matching was found for the given serialization id. /// - public Type Get(ulong serId) + public static Type Get(ulong serId) { - return types[serId]; + return idToTypes[serId]; + } + + /// + /// Gets the serialization id associated to a serialization type. + /// + /// The type to get the serialization id for. + /// The type associated to the serialization id. + /// + /// Thrown when no matching serialization id was found for the given . + /// + public static ulong Get(Type type) + { + return typesToId[type]; } private static ulong SerIdFromModTypeId(uint modId, uint typeId) => ((ulong)modId << (sizeof(uint) * 8)) | typeId; @@ -47,7 +62,7 @@ public Type Get(ulong serId) /// /// Thrown when no matching was found for the given mod and type id. /// - public Type Get(uint modId, uint typeId) + public static Type Get(uint modId, uint typeId) { return Get(SerIdFromModTypeId(modId, typeId)); } @@ -59,9 +74,20 @@ public Type Get(uint modId, uint typeId) /// The serialization id to get the for. /// The type associated to the serialization id. /// true when the type could be found; otherwise false. - public bool TryGet(ulong serId, [MaybeNullWhen(false)] out Type type) + public static bool TryGet(ulong serId, [MaybeNullWhen(false)] out Type type) + { + return idToTypes.TryGetValue(serId, out type); + } + /// + /// Tries to get the serialization id associated to a the type + /// (). + /// + /// The type associated to the serialization id. + /// The serialization id to get the for. + /// true when the type could be found; otherwise false. + public static bool TryGet(Type type, [MaybeNullWhen(false)] out ulong serId) { - return types.TryGetValue(serId, out type); + return typesToId.TryGetValue(type, out serId); } /// @@ -72,7 +98,7 @@ public bool TryGet(ulong serId, [MaybeNullWhen(false)] out Type type) /// The type id to get the for. /// The type associated to the serialization id. /// true when the type could be found; otherwise false. - public bool TryGet(uint modId, uint typeId, [MaybeNullWhen(false)] out Type type) + public static bool TryGet(uint modId, uint typeId, [MaybeNullWhen(false)] out Type type) { return TryGet(SerIdFromModTypeId(modId, typeId), out type); } diff --git a/OctoAwesome/OctoAwesome/Simulation.cs b/OctoAwesome/OctoAwesome/Simulation.cs index 84aeb1f8..e84128b6 100644 --- a/OctoAwesome/OctoAwesome/Simulation.cs +++ b/OctoAwesome/OctoAwesome/Simulation.cs @@ -1,7 +1,9 @@ using engenious; -using OctoAwesome.Collections; -using OctoAwesome.Common; +using NLog; + +using NonSucking.Framework.Extension.Collections; + using OctoAwesome.Components; using OctoAwesome.EntityComponents; using OctoAwesome.Extension; @@ -9,6 +11,7 @@ using OctoAwesome.Notifications; using OctoAwesome.Pooling; using OctoAwesome.Rx; +using OctoAwesome.Serialization; using OctoAwesome.Threading; using System; @@ -16,13 +19,14 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.InteropServices; namespace OctoAwesome { /// /// Interface between application and world model. /// - public sealed class Simulation : IDisposable + public sealed class Simulation : ComponentContainer, IDisposable { /// /// Gets the resource manager for managing resources. @@ -30,64 +34,71 @@ public sealed class Simulation : IDisposable public IResourceManager ResourceManager { get; } /// - /// Gets or sets a value indicating whether the simulation is server side or client side. + /// Gets or sets a value indicating whether the simulation is server side. /// public bool IsServerSide { get; set; } + /// + /// Gets or sets a value indicating whether the simulation is client side. + /// + public bool IsClientSide => !IsServerSide; + /// /// Gets a list of simulation components. /// public ComponentList Components { get; } + public ComponentList GlobalComponentList { get; } + /// /// Gets the current state of the simulation. /// public SimulationState State { get; private set; } /// - ///Gets the of the currently loaded universe. + /// Gets the of the currently loaded universe. /// public Guid UniverseId { get; } /// - /// Gets the game service. + /// Gets the of the current universe for sending components via Network /// - public IGameService Service { get; } + public NetworkingSimulationComponent FooBbqSimulationComponent { get; } + private readonly Logger logger; private readonly ExtensionService extensionService; - private readonly EnumerationmodifiableConcurrentList entities = new(); + private readonly EnumerationModifiableConcurrentList entities = new(); + private readonly CountedScopeSemaphore entitiesSemaphore = new(); private readonly IPool entityNotificationPool; - private readonly Relay networkRelay; - private readonly Relay uiRelay; - + private readonly ComponentChangedNotificationHandler componentChangedHandler; private IDisposable? simulationSubscription; - private IDisposable? networkSubscription; - private IDisposable? uiSubscription; /// /// Initializes a new instance of the class. /// /// The resource manager for managing resources. /// The extension service for extending this simulation. - /// The game service. - public Simulation(IResourceManager resourceManager, ExtensionService extensionService, IGameService service) + public Simulation(IResourceManager resourceManager, ExtensionService extensionService) { ResourceManager = resourceManager; - networkRelay = new Relay(); - uiRelay = new Relay(); - - entityNotificationPool = TypeContainer.Get>(); - - + var typeContainer = TypeContainer.Get(); + entityNotificationPool = typeContainer.Get>(); + componentChangedHandler = typeContainer.Get(); + componentChangedHandler.AssociatedSimulation = this; + FooBbqSimulationComponent = new NetworkingSimulationComponent( + typeContainer.Get(), + entityNotificationPool, + typeContainer.Get>()); this.extensionService = extensionService; State = SimulationState.Ready; UniverseId = Guid.Empty; - Service = service; + logger = NLog.LogManager.GetCurrentClassLogger(); + GlobalComponentList = new(); Components = new ComponentList( - ValidateAddComponent, ValidateRemoveComponent, null, null); + ValidateAddComponent, ValidateRemoveComponent, null, null, this); extensionService.ExecuteExtender(this); } @@ -97,12 +108,14 @@ private void ValidateAddComponent(SimulationComponent component) { if (State != SimulationState.Ready) throw new NotSupportedException("Simulation needs to be in Ready mode to add Components"); + GlobalComponentList.Add(component); } private void ValidateRemoveComponent(SimulationComponent component) { if (State != SimulationState.Ready) throw new NotSupportedException("Simulation needs to be in Ready mode to remove Components"); + GlobalComponentList.Remove(component); } /// @@ -111,7 +124,7 @@ private void ValidateRemoveComponent(SimulationComponent component) /// The name of the universe. /// The seed used for creating the universe. /// The of the created universe. - public Guid NewGame(string name, string rawSeed) + public static Guid NewGame(IResourceManager resourceManager, string name, string rawSeed) { int numericSeed; @@ -130,9 +143,7 @@ public Guid NewGame(string name, string rawSeed) } - Guid guid = ResourceManager.NewUniverse(name, numericSeed); - - Start(); + Guid guid = resourceManager.NewUniverse(name, numericSeed); return guid; } @@ -161,18 +172,6 @@ private void Start() .ListenOn(DefaultChannels.Simulation) .Subscribe(OnNext); - networkSubscription - = ResourceManager - .UpdateHub - .AddSource(networkRelay, DefaultChannels.Network); - - uiSubscription - = ResourceManager - .UpdateHub - .AddSource(uiRelay, DefaultChannels.UI); - - - State = SimulationState.Running; } @@ -190,12 +189,10 @@ public void Update(GameTime gameTime) planet.Value.GlobalChunkCache.BeforeSimulationUpdate(this); //Update all Entities - foreach (var entity in entities) - { - if (entity is IUpdateable updateableEntity) - updateableEntity.Update(gameTime); - } - + if (entities.Count > 0) + foreach (Entity entity in entities) + if (entity is IUpdateable updateable) + updateable.Update(gameTime); // Update all Components foreach (var component in Components) @@ -204,6 +201,8 @@ public void Update(GameTime gameTime) foreach (var planet in ResourceManager.Planets) planet.Value.GlobalChunkCache.AfterSimulationUpdate(this); + + FooBbqSimulationComponent.Update(gameTime); } /// @@ -219,17 +218,15 @@ public void ExitGame() //TODO: unschön, Dispose Entity's, Reset Extensions - foreach (var item in entities) - Remove(item); - + foreach (var entity in entities) + { + Remove(entity); + } State = SimulationState.Finished; - // thread.Join(); ResourceManager.UnloadUniverse(); simulationSubscription?.Dispose(); - networkSubscription?.Dispose(); - uiSubscription?.Dispose(); } /// @@ -250,41 +247,92 @@ public void Add(Entity entity, bool overwriteExisting) if (entity.Simulation is not null && entity.Simulation != this) throw new NotSupportedException("Entity can't be part of more than one simulation"); + var hasPosComponent = entity.Components.TryGet(out var newPosComponent); + using (var _ = entitiesSemaphore.EnterCountScope()) + foreach (var fb in entities) + { + if (fb == entity + || (fb.Components.Contains() + && entity.Components.Contains() + && fb.Components.TryGet(out var existingPosition) + && (!hasPosComponent || existingPosition.Position == newPosComponent!.Position))) + { + SendToClientIfRequired(entity, true, EntityNotification.ActionType.Add); + return; + } + } - foreach (var fb in entities) + Entity? existing; + using (var _ = entitiesSemaphore.EnterCountScope()) { - if (fb == entity - || (fb.Components.Contains() - && entity.Components.Contains() - && fb.Components.TryGet(out var existingPosition) - && (!entity.Components.TryGet(out var newPosComponent) - || existingPosition.Position == newPosComponent.Position))) + existing = entities.FirstOrDefault(x => x.Id == entity.Id); + if (existing != default && !overwriteExisting) { + SendToClientIfRequired(entity, true, EntityNotification.ActionType.Add); return; } } - var existing = entities.FirstOrDefault(x => x.Id == entity.Id); - if (existing != default && !overwriteExisting) - return; - if (existing != default) Remove(existing); - extensionService.ExecuteExtender(entity); - entity.Initialize(ResourceManager); - entity.Simulation = this; - if (entity.Id == Guid.Empty) entity.Id = Guid.NewGuid(); - entities.Add(entity); + entity.ResourceManager = ResourceManager; + entity.Simulation = this; + extensionService.ExecuteExtender(entity); + entity.Initialize(); + + if (!hasPosComponent) + hasPosComponent = entity.Components.TryGet(out newPosComponent); + //using (var _ = entitiesSemaphore.EnterExclusiveScope()) + entities.Add(entity); + foreach (var component in Components) { if (component is IHoldComponent holdComponent) holdComponent.Add(entity); } + + if (IsServerSide) + { + entity.Components.AddIfTypeNotExists(new ServerManagedComponent { OnServer = true }); + } + + if (hasPosComponent) + { + var gcc = newPosComponent.Planet.GlobalChunkCache; + gcc.CacheService.AddOrUpdate(entity.Id, newPosComponent); + gcc.CacheService.AddOrUpdate(entity.Id, entity); + } + + foreach (var comp in entity.Components) + { + GlobalComponentList.AddIfNotExists(comp); + } + + SendToClientIfRequired(entity, false, EntityNotification.ActionType.Add); + } + + private void SendToClientIfRequired(Entity entity, bool existsAlready, EntityNotification.ActionType type) + { + if ((entity is RemoteEntity && IsClientSide) + || (!existsAlready && entity is Player && IsServerSide)) + return; + + logger.Debug($"Send {entity.GetType().Name} with id {entity.Id} to network"); + if (entity is not RemoteEntity) + entity = new RemoteEntity(entity); + + var remoteNotification = new EntityNotification + { + Entity = entity, + Type = type + }; + + ResourceManager.UpdateHub.PushNetwork(remoteNotification, DefaultChannels.Simulation); } /// @@ -311,13 +359,21 @@ public void Remove(Entity entity) ResourceManager.SaveComponentContainer(entity); + + foreach (var comp in entity.Components) + { + GlobalComponentList.Remove(comp); + } + foreach (var component in Components) { if (component is IHoldComponent holdComponent) holdComponent.Remove(entity); } + //using (var _ = entitiesSemaphore.EnterExclusiveScope()) entities.Remove(entity); + SendToClientIfRequired(entity, true, EntityNotification.ActionType.Remove); entity.Id = Guid.Empty; entity.Simulation = null; } @@ -399,13 +455,14 @@ public IReadOnlyCollection GetByComponentTypes() /// if a container was found, otherwise public bool TryGetById(Guid id, [MaybeNullWhen(false)] out T componentContainer) where T : ComponentContainer { - foreach (var item in entities) - { - if (!(item is T t) || t.Id != id) - continue; - componentContainer = t; - return true; - } + using (var _ = entitiesSemaphore.EnterCountScope()) + foreach (var item in entities) + { + if (item is not T t || t.Id != id) + continue; + componentContainer = t; + return true; + } componentContainer = default; return false; @@ -424,67 +481,52 @@ public void RemoveEntity(Guid entityId) /// Gets called for receiving new notifications. /// /// The new notification. - public void OnNext(Notification value) + public void OnNext(object value) { - if (entities.Count < 0 && !IsServerSide) - return; - switch (value) { case EntityNotification entityNotification: - if (entityNotification.Type == EntityNotification.ActionType.Remove) - RemoveEntity(entityNotification.EntityId); - else if (entityNotification.Type == EntityNotification.ActionType.Add) - Add(entityNotification.Entity, entityNotification.OverwriteExisting); - else if (entityNotification.Type == EntityNotification.ActionType.Update) - EntityUpdate(entityNotification); - else if (entityNotification.Type == EntityNotification.ActionType.Request) - RequestEntity(entityNotification); - - uiRelay.OnNext(value); + switch (entityNotification.Type) + { + case EntityNotification.ActionType.Remove: + RemoveEntity(entityNotification.EntityId); + break; + case EntityNotification.ActionType.Add: + Add(entityNotification.Entity, entityNotification.OverwriteExisting); + break; + case EntityNotification.ActionType.Request: + RequestEntity(entityNotification.EntityId); + break; + case EntityNotification.ActionType.Update: + EntityUpdate(entityNotification); + break; + } break; default: break; } } - - - /// - /// Called when an update notification was received. - /// - /// The update notification. - public void OnUpdate(SerializableNotification notification) - { - if (!IsServerSide) - networkRelay.OnNext(notification); - } - private void EntityUpdate(EntityNotification notification) { - var entity = entities.FirstOrDefault(e => e.Id == notification.EntityId); - if (entity == null) - { - var entityNotification = entityNotificationPool.Rent(); - entityNotification.EntityId = notification.EntityId; - entityNotification.Type = EntityNotification.ActionType.Request; - networkRelay.OnNext(entityNotification); - entityNotification.Release(); - } - else - { - Debug.Assert(notification.Notification != null, "notification.Notification != null"); - entity.Push(notification.Notification); - } + var instance = entities.FirstOrDefault(x => x.Id == notification.EntityId); + if (instance is null) + return; + + componentChangedHandler.Execute(instance, notification); } - private void RequestEntity(EntityNotification entityNotification) + private void RequestEntity(Guid entityId) { if (!IsServerSide) return; - var entity = entities.FirstOrDefault(e => e.Id == entityNotification.EntityId); - if (entity == null) - return; + Entity? entity; + using (var _ = entitiesSemaphore.EnterCountScope()) + { + entity = entities.FirstOrDefault(e => e.Id == entityId); + if (entity == null) + return; + } var remoteEntity = new RemoteEntity(entity); remoteEntity.Components.AddIfTypeNotExists(new BodyComponent() { Mass = 50f, Height = 2f, Radius = 1.5f }); @@ -495,15 +537,14 @@ private void RequestEntity(EntityNotification entityNotification) newEntityNotification.Entity = remoteEntity; newEntityNotification.Type = EntityNotification.ActionType.Add; - networkRelay.OnNext(newEntityNotification); + ResourceManager.UpdateHub.PushNetwork(newEntityNotification, DefaultChannels.Simulation); newEntityNotification.Release(); } /// public void Dispose() { - uiRelay.Dispose(); - networkRelay.Dispose(); + simulationSubscription?.Dispose(); } } } diff --git a/OctoAwesome/OctoAwesome/StandaloneTypeContainer.cs b/OctoAwesome/OctoAwesome/StandaloneTypeContainer.cs deleted file mode 100644 index a9b73c0e..00000000 --- a/OctoAwesome/OctoAwesome/StandaloneTypeContainer.cs +++ /dev/null @@ -1,210 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; - -namespace OctoAwesome -{ - /// - /// Implementation for a standalone type container. - /// - public sealed class StandaloneTypeContainer : ITypeContainer - { - private readonly Dictionary typeInformationRegister; - private readonly Dictionary typeRegister; - - /// - /// Initializes a new instance of the class. - /// - public StandaloneTypeContainer() - { - typeInformationRegister = new Dictionary(); - typeRegister = new Dictionary(); - } - - - /// - public void Register(Type registrar, Type type, InstanceBehavior instanceBehavior) - { - if (!typeInformationRegister.ContainsKey(type)) - typeInformationRegister.Add(type, new TypeInformation(this, type, instanceBehavior)); - - typeRegister.Add(registrar, type); - } - - /// - public void Register(InstanceBehavior instanceBehavior = InstanceBehavior.Instance) where T : class - => Register(typeof(T), typeof(T), instanceBehavior); - - /// - public void Register(InstanceBehavior instanceBehavior = InstanceBehavior.Instance) where T : class - => Register(typeof(TRegistrar), typeof(T), instanceBehavior); - - /// - public void Register(Type registrar, Type type, object singleton) - { - if (!typeInformationRegister.ContainsKey(type)) - typeInformationRegister.Add(type, new TypeInformation(this, type, InstanceBehavior.Singleton, singleton)); - - typeRegister.Add(registrar, type); - } - - /// - public void Register(T singleton) where T : class - => Register(typeof(T), typeof(T), singleton); - - /// - public void Register(object singleton) where T : class - => Register(typeof(TRegistrar), typeof(T), singleton); - - /// - public bool TryGet(Type type, [MaybeNullWhen(false)] out object instance) - { - instance = GetOrNull(type); - return instance != null; - } - - /// - public bool TryGet([MaybeNullWhen(false)] out T instance) where T : class - { - if (TryGet(typeof(T), out var obj)) - { - instance = (T)obj; - return true; - } - instance = null; - return false; - } - - /// - public object Get(Type type) - => GetOrNull(type) ?? throw new KeyNotFoundException($"Type {type} was not found in Container"); - - /// - public T Get() where T : class - => (T)Get(typeof(T)); - - /// - public object? GetOrNull(Type type) - { - if (typeRegister.TryGetValue(type, out var searchType)) - { - if (typeInformationRegister.TryGetValue(searchType, out var typeInformation)) - return typeInformation.Instance; - } - return null; - } - - /// - public T? GetOrNull() where T : class - => (T?)GetOrNull(typeof(T)); - - /// - public object GetUnregistered(Type type) - => GetOrNull(type) - ?? CreateObject(type) - ?? throw new InvalidOperationException($"Can not create unregistered type of {type}"); - - /// - public T GetUnregistered() where T : class - => (T)GetUnregistered(typeof(T)); - - /// - public object? CreateObject(Type type) - { - var tmpList = new List(); - - var constructors = type.GetConstructors().OrderByDescending(c => c.GetParameters().Length); - - foreach (var constructor in constructors) - { - bool next = false; - foreach (var parameter in constructor.GetParameters()) - { - if (TryGet(parameter.ParameterType, out var instance)) - { - tmpList.Add(instance); - } - else if (parameter.IsOptional && parameter.HasDefaultValue) - { - tmpList.Add(parameter.DefaultValue); - } - else // Unknown parameter type without default value - { - tmpList.Clear(); - next = true; - break; - } - } - - if (next) - continue; - - return constructor.Invoke(tmpList.ToArray()); - } - - if (!constructors.Any()) - { - try - { - return Activator.CreateInstance(type); - } - catch - { - return null; - } - } - - return null; - } - - /// - public T? CreateObject() where T : class - => (T?)CreateObject(typeof(T)); - - /// - public void Dispose() - { - typeRegister.Clear(); - typeInformationRegister.Values - .Where(t => t.Behavior == InstanceBehavior.Singleton && t.Instance != this) - .Select(t => t.Instance as IDisposable) - .ToList() - .ForEach(i => i?.Dispose()); - - typeInformationRegister.Clear(); - } - - private class TypeInformation - { - public InstanceBehavior Behavior { get; set; } - public object? Instance => CreateObject(); - - private readonly StandaloneTypeContainer typeContainer; - private readonly Type type; - private object? singletonInstance; - - public TypeInformation(StandaloneTypeContainer container, - Type type, InstanceBehavior instanceBehavior, object? instance = null) - { - this.type = type; - Behavior = instanceBehavior; - typeContainer = container; - singletonInstance = instance; - } - - private object? CreateObject() - { - if (Behavior == InstanceBehavior.Singleton && singletonInstance != null) - return singletonInstance; - - var obj = typeContainer.CreateObject(type); - - if (Behavior == InstanceBehavior.Singleton) - singletonInstance = obj; - - return obj; - } - } - } -} diff --git a/OctoAwesome/OctoAwesome/Startup.cs b/OctoAwesome/OctoAwesome/Startup.cs index 58590d02..e44eba05 100644 --- a/OctoAwesome/OctoAwesome/Startup.cs +++ b/OctoAwesome/OctoAwesome/Startup.cs @@ -2,10 +2,14 @@ using NLog.Config; using NLog.Targets; +using NonSucking.Framework.Extension.IoC; + + using OctoAwesome.Chunking; using OctoAwesome.Information; using OctoAwesome.Notifications; using OctoAwesome.Pooling; +using OctoAwesome.Serialization; using OctoAwesome.Services; using OctoAwesome.Threading; @@ -31,21 +35,26 @@ public static void Register(ITypeContainer typeContainer) typeContainer.Register(); typeContainer.Register(); - typeContainer.Register, Pool>(InstanceBehavior.Singleton); - typeContainer.Register, Pool>(InstanceBehavior.Singleton); + typeContainer.Register, Pool>(InstanceBehaviour.Singleton); + typeContainer.Register, Pool>(InstanceBehaviour.Singleton); - typeContainer.Register, Pool>(InstanceBehavior.Singleton); - typeContainer.Register, Pool>(InstanceBehavior.Singleton); - typeContainer.Register, Pool>(InstanceBehavior.Singleton); - typeContainer.Register, Pool>(InstanceBehavior.Singleton); - typeContainer.Register, Pool>(InstanceBehavior.Singleton); - typeContainer.Register, Pool>(InstanceBehavior.Singleton); - typeContainer.Register, Pool>(InstanceBehavior.Singleton); - typeContainer.Register, Pool>(InstanceBehavior.Singleton); - typeContainer.Register, ChunkPool>(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); - typeContainer.Register, Pool>(InstanceBehavior.Singleton); - typeContainer.Register(InstanceBehavior.Singleton); + typeContainer.Register, Pool>(InstanceBehaviour.Singleton); + typeContainer.Register, Pool>(InstanceBehaviour.Singleton); + typeContainer.Register, Pool>(InstanceBehaviour.Singleton); + typeContainer.Register, Pool>(InstanceBehaviour.Singleton); + typeContainer.Register, Pool>(InstanceBehaviour.Singleton); + typeContainer.Register, Pool>(InstanceBehaviour.Singleton); + typeContainer.Register, Pool>(InstanceBehaviour.Singleton); + typeContainer.Register, Pool>(InstanceBehaviour.Singleton); + typeContainer.Register, Pool>(InstanceBehaviour.Singleton); + typeContainer.Register, Pool>(InstanceBehaviour.Singleton); + typeContainer.Register, ChunkPool>(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register, Pool>(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + typeContainer.Register(InstanceBehaviour.Singleton); + + typeContainer.Register(InstanceBehaviour.Singleton); } /// @@ -61,20 +70,20 @@ public static void ConfigureLogger(ClientType clientType) case ClientType.DesktopClient: config.AddRule(LogLevel.Trace, LogLevel.Fatal, new FileTarget("octoawesome.logfile") { - FileName = $"./logs/octoClient-{DateTime.Now:ddMMyy_hhmmss}.log" + FileName = $"./logs/octoClient-{DateTime.Now:dd_MM_yyyy}.log" }); break; case ClientType.GameServer: - config.AddRule(LogLevel.Trace, LogLevel.Fatal, new ColoredConsoleTarget("octoawesome.logconsole")); - config.AddRule(LogLevel.Debug, LogLevel.Fatal, new FileTarget("octoawesome.logfile") + config.AddRule(LogLevel.Debug, LogLevel.Fatal, new ColoredConsoleTarget("octoawesome.logconsole")); + config.AddRule(LogLevel.Trace, LogLevel.Fatal, new FileTarget("octoawesome.logfile") { - FileName = $"./logs/server-{DateTime.Now:ddMMyy_hhmmss}.log" + FileName = $"./logs/server-{DateTime.Now:dd_MM_yyyy}.log" }); break; default: config.AddRule(LogLevel.Trace, LogLevel.Fatal, new FileTarget("octoawesome.logfile") { - FileName = $"./logs/generic-{DateTime.Now:ddMMyy_hhmmss}.log" + FileName = $"./logs/generic-{DateTime.Now:dd_MM_yyyy}.log" }); break; } diff --git a/OctoAwesome/OctoAwesome/Threading/Awaiter.cs b/OctoAwesome/OctoAwesome/Threading/Awaiter.cs index 59aebb6a..d2c3bccf 100644 --- a/OctoAwesome/OctoAwesome/Threading/Awaiter.cs +++ b/OctoAwesome/OctoAwesome/Threading/Awaiter.cs @@ -6,6 +6,7 @@ using System.IO; using System.Threading; using OctoAwesome.Extension; +using OctoAwesome.Caching; namespace OctoAwesome.Threading { @@ -14,19 +15,19 @@ namespace OctoAwesome.Threading /// public class Awaiter : IPoolElement, IDisposable { - /// - /// Gets or sets the result for the awaiter. - /// - public ISerializable? Result { get; set; } - /// /// Gets a value indicating whether awaiting has timed out. /// public bool TimedOut { get; private set; } + public bool Network { get; set; } + + private ISerializable? knownResult; + private byte[]? result; private readonly ManualResetEventSlim manualReset; private readonly LockSemaphore semaphore; private bool alreadyDeserialized; private IPool? pool; + private Func? deserializeFunc; private IPool Pool { get => NullabilityHelper.NotNullAssert(pool, $"{nameof(IPoolElement)} was not initialized!"); @@ -48,27 +49,90 @@ public Awaiter() /// Waits on the result or time outs(10000s). /// /// The result; or null if there is no result yet. - public ISerializable? WaitOn() + public bool WaitOn() { Debug.Assert(!isPooled, "Is released into pool!"); + Debug.WriteLine("Waiting for result"); if (!alreadyDeserialized) - TimedOut = !manualReset.Wait(10000); + TimedOut = !manualReset.Wait(-1); //10000 + + return !TimedOut; + } + + /// + /// Waits on the result or time outs(10000s) and releases the awaiter. + /// + /// The result; or null if there is no result yet. + public T? WaitOnAndRelease() where T : IConstructionSerializable, new() + { + Debug.Assert(!isPooled, "Is released into pool!"); + var res = WaitOn(); + if (!res) + { + Release(); + return default; + } + + T? ret; + if (deserializeFunc is not null) + { + ret = GenericCaster.Cast(deserializeFunc(result)); + } + else if (knownResult != default && result is null) + { + ret = GenericCaster.Cast(knownResult); + } + else if (knownResult is T instance && result is not null) + { + ret = Network + ? Serializer.DeserializeNetwork(instance, result) + : Serializer.Deserialize(instance, result); + } + else if (result is not null) + { + ret = Network + ? Serializer.DeserializeSpecialCtorNetwork(result) + : Serializer.DeserializeSpecialCtor(result); + } + else + { + ret = default; + } + - return Result; + Release(); + return ret; } /// /// Waits on the result or time outs(10000s) and releases the awaiter. /// /// The result; or null if there is no result yet. - public ISerializable? WaitOnAndRelease() + public T? WaitOnAndRelease(T? instance) where T : class, ISerializable { Debug.Assert(!isPooled, "Is released into pool!"); var res = WaitOn(); + if (!res) + { + Release(); + return null; + } + + T? ret; + if (deserializeFunc is not null) + ret = GenericCaster.Cast(deserializeFunc(result)); + else if (knownResult is not null && result is null) + ret = GenericCaster.Cast(knownResult); + else if (result is not null && instance is not null) + ret = Serializer.Deserialize(instance, result); + else + ret = null; + Release(); - return res; + return ret; } + /// /// Sets the awaiter result to the given value. /// @@ -78,9 +142,9 @@ public void SetResult(ISerializable result) Debug.Assert(!isPooled, "Is released into pool!"); using (semaphore.Wait()) { - Result = result; - manualReset.Set(); + knownResult = result; alreadyDeserialized = true; + manualReset.Set(); } } @@ -92,6 +156,7 @@ public void SetResult(ISerializable result) /// Throws when is null. public bool TrySetResult(byte[] bytes) { + Debug.WriteLine("Setting result"); Debug.Assert(!isPooled, "Is released into pool!"); try { @@ -100,16 +165,10 @@ public bool TrySetResult(byte[] bytes) if (TimedOut) return false; - if (Result == null) - throw new ArgumentNullException(nameof(Result)); - - using (var stream = new MemoryStream(bytes)) - using (var reader = new BinaryReader(stream)) - { - Result.Deserialize(reader); - } + result = bytes; + alreadyDeserialized = true; manualReset.Set(); - return alreadyDeserialized = true; + return true; } } @@ -119,6 +178,11 @@ public bool TrySetResult(byte[] bytes) } } + public void SetDesializeFunc(Func func) + { + deserializeFunc = func; + } + /// public void Init(IPool pool) { @@ -126,7 +190,10 @@ public void Init(IPool pool) TimedOut = false; isPooled = false; alreadyDeserialized = false; - Result = null; + result = null; + knownResult = null; + deserializeFunc = null; + Network = false; manualReset.Reset(); } @@ -149,6 +216,7 @@ public void Release() public void Dispose() { manualReset.Dispose(); + semaphore.Dispose(); } } } diff --git a/OctoAwesome/OctoAwesome/TypeContainer.cs b/OctoAwesome/OctoAwesome/TypeContainer.cs deleted file mode 100644 index fa7bd5fd..00000000 --- a/OctoAwesome/OctoAwesome/TypeContainer.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; - -namespace OctoAwesome -{ - /// - /// Static interface to a singleton. - /// - public static class TypeContainer - { - private static readonly ITypeContainer instance; - - static TypeContainer() - { - instance = new StandaloneTypeContainer(); - instance.Register((StandaloneTypeContainer)instance); - instance.Register(instance); - } - - /// - public static object? CreateObject(Type type) - => instance.CreateObject(type); - - /// - public static T? CreateObject() where T : class - => instance.CreateObject(); - - /// - public static void Register(Type registrar, Type type, InstanceBehavior instanceBehavior) - => instance.Register(registrar, type, instanceBehavior); - - /// - public static void Register(InstanceBehavior instanceBehavior = InstanceBehavior.Instance) where T : class - - => instance.Register(instanceBehavior); - /// - public static void Register(InstanceBehavior instanceBehavior = InstanceBehavior.Instance) where T : class - => instance.Register(instanceBehavior); - - /// - public static void Register(Type registrar, Type type, object singleton) - => instance.Register(registrar, type, singleton); - - /// - public static void Register(T singleton) where T : class - => instance.Register(singleton); - - /// - public static void Register(object singleton) where T : class - => instance.Register(singleton); - - /// - public static bool TryResolve(Type type, [MaybeNullWhen(false)] out object resolvedInstance) - => instance.TryGet(type, out resolvedInstance); - - /// - public static bool TryResolve([MaybeNullWhen(false)] out T resolvedInstance) where T : class - => instance.TryGet(out resolvedInstance); - - /// - public static object Get(Type type) - => instance.Get(type); - - /// - public static T Get() where T : class - => instance.Get(); - - /// - public static object? GetOrNull(Type type) - => instance.GetOrNull(type); - - /// - public static T? GetOrNull() where T : class - => instance.GetOrNull(); - - /// - public static object GetUnregistered(Type type) - => instance.GetUnregistered(type); - - /// - public static T GetUnregistered() where T : class - => instance.GetUnregistered(); - - } -} diff --git a/OctoAwesome/global.json b/OctoAwesome/global.json new file mode 100644 index 00000000..7cd6a1f4 --- /dev/null +++ b/OctoAwesome/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "7.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file