Skip to content

Commit

Permalink
Adopt ECS Design Pattern (Part II) (#73)
Browse files Browse the repository at this point in the history
* 开始对 BlockEntity 使用 ECS

* 完成 BlockEntity 的改造

* fix typos

* fix typos
  • Loading branch information
sunnycase committed Sep 20, 2017
1 parent db41352 commit 8120510
Show file tree
Hide file tree
Showing 42 changed files with 724 additions and 395 deletions.
11 changes: 11 additions & 0 deletions src/MineCase.Core/Slot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ public Slot WithItemCount(byte count)
};
}

public Slot AddItemCount(int count)
{
return new Slot
{
BlockId = BlockId,
ItemCount = (byte)(ItemCount + count),
ItemDamage = ItemDamage,
NBT = NBT
};
}

public void MakeEmptyIfZero()
{
if (ItemCount == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,44 @@ protected override Task OnAttached()
{
AttachedObject.RegisterPropertyChangedHandler(WorldComponent.WorldProperty, OnWorldChanged);
AttachedObject.RegisterPropertyChangedHandler(EntityWorldPositionComponent.EntityWorldPositionProperty, OnEntityWorldPositionChanged);
AttachedObject.RegisterPropertyChangedHandler(BlockWorldPositionComponent.BlockWorldPositionProperty, OnBlockWorldPositionChanged);
return Task.CompletedTask;
}

private Task OnWorldChanged(object sender, PropertyChangedEventArgs<IWorld> e)
private Task OnBlockWorldPositionChanged(object sender, PropertyChangedEventArgs<BlockWorldPos> e)
{
if (AttachedObject.TryGetEntityWorldPosition(out var entityPos))
{
var chunkWorldPos = entityPos.ToChunkWorldPos();
var key = e.NewValue.MakeAddressByPartitionKey(chunkWorldPos);
AttachedObject.SetLocalValue(AddressByPartitionKeyProperty, key);
}
UpdateKey();
return Task.CompletedTask;
}

private Task OnWorldChanged(object sender, PropertyChangedEventArgs<IWorld> e)
{
UpdateKey();
return Task.CompletedTask;
}

private Task OnEntityWorldPositionChanged(object sender, PropertyChangedEventArgs<EntityWorldPos> e)
{
UpdateKey();
return Task.CompletedTask;
}

private void UpdateKey()
{
if (AttachedObject.TryGetWorld(out var world))
{
var chunkWorldPos = e.NewValue.ToChunkWorldPos();
var key = world.MakeAddressByPartitionKey(chunkWorldPos);
AttachedObject.SetLocalValue(AddressByPartitionKeyProperty, key);
}
ChunkWorldPos? chunkWorldPos = null;
if (AttachedObject.TryGetLocalValue(EntityWorldPositionComponent.EntityWorldPositionProperty, out var entityPos))
chunkWorldPos = entityPos.ToChunkWorldPos();
else if (AttachedObject.TryGetLocalValue(BlockWorldPositionComponent.BlockWorldPositionProperty, out var blockPos))
chunkWorldPos = blockPos.ToChunkWorldPos();

return Task.CompletedTask;
if (chunkWorldPos.HasValue)
{
var key = world.MakeAddressByPartitionKey(chunkWorldPos.Value);
AttachedObject.SetLocalValue(AddressByPartitionKeyProperty, key);
}
}
}

private static async Task OnAddressByPartitionKeyChanged(object sender, PropertyChangedEventArgs<string> e)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using MineCase.Engine;
using MineCase.World;

namespace MineCase.Server.Components
{
internal class BlockWorldPositionComponent : Component
{
public static readonly DependencyProperty<BlockWorldPos> BlockWorldPositionProperty =
DependencyProperty.Register<BlockWorldPos>("BlockWorldPosition", typeof(BlockWorldPositionComponent));

public BlockWorldPos BlockWorldPosition => AttachedObject.GetValue(BlockWorldPositionProperty);

public BlockWorldPositionComponent(string name = "blockWorldPosition")
: base(name)
{
}

public Task SetBlockWorldPosition(BlockWorldPos value) =>
AttachedObject.SetLocalValue(BlockWorldPositionProperty, value);
}
}
9 changes: 8 additions & 1 deletion src/MineCase.Server.Grains/Components/GameTickComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace MineCase.Server.Components
{
internal class GameTickComponent : Component, IHandle<GameTick>
internal class GameTickComponent : Component, IHandle<GameTick>, IHandle<Disable>
{
public event AsyncEventHandler<(TimeSpan deltaTime, long worldAge)> Tick;

Expand Down Expand Up @@ -49,5 +49,12 @@ Task IHandle<GameTick>.Handle(GameTick message)
{
return Tick.InvokeSerial(this, (message.DeltaTime, message.WorldAge));
}

async Task IHandle<Disable>.Handle(Disable message)
{
var key = AttachedObject.GetAddressByPartitionKey();
if (!string.IsNullOrEmpty(key))
await GrainFactory.GetGrain<ITickEmitter>(key).Unsubscribe(AttachedObject);
}
}
}
6 changes: 3 additions & 3 deletions src/MineCase.Server.Grains/Game/BlockEntities/BlockEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,17 @@ select new
}).ToDictionary(o => o.BlockId, o => o.Method);
}

private static IBlockEntity GetBlockEntity<TGrain>(IGrainFactory grainFactory, string key)
private static IBlockEntity GetBlockEntity<TGrain>(IGrainFactory grainFactory, Guid key)
where TGrain : IBlockEntity
{
return grainFactory.GetGrain<TGrain>(key).Cast<IBlockEntity>();
}

public static IBlockEntity Create(IGrainFactory grainFactory, IWorld world, BlockWorldPos position, BlockId blockId)
public static IBlockEntity Create(IGrainFactory grainFactory, BlockId blockId)
{
if (_blockEntityTypes.TryGetValue(blockId, out var method))
{
var key = world.MakeBlockEntityKey(position);
var key = Guid.NewGuid();
return (IBlockEntity)method.Invoke(null, new object[] { grainFactory, key });
}

Expand Down
33 changes: 17 additions & 16 deletions src/MineCase.Server.Grains/Game/BlockEntities/BlockEntityGrain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,35 @@
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using MineCase.Engine;
using MineCase.Server.Components;
using MineCase.Server.Game.BlockEntities.Components;
using MineCase.Server.World;
using MineCase.World;
using Orleans;

namespace MineCase.Server.Game.BlockEntities
{
internal abstract class BlockEntityGrain : Grain, IBlockEntity
internal abstract class BlockEntityGrain : DependencyObject, IBlockEntity
{
protected IWorld World { get; private set; }
public IWorld World => GetValue(WorldComponent.WorldProperty);

protected BlockWorldPos Position { get; private set; }
public BlockWorldPos Position => GetValue(BlockWorldPositionComponent.BlockWorldPositionProperty);

public override Task OnActivateAsync()
protected override async Task InitializeComponents()
{
var keys = this.GetWorldAndBlockEntityPosition();
World = GrainFactory.GetGrain<IWorld>(keys.worldKey);
Position = keys.position;
return base.OnActivateAsync();
await SetComponent(new WorldComponent());
await SetComponent(new BlockWorldPositionComponent());
await SetComponent(new AddressByPartitionKeyComponent());
await SetComponent(new ChunkEventBroadcastComponent());
await SetComponent(new GameTickComponent());
await SetComponent(new BlockEntityLiftTimeComponent());
}

public virtual Task Destroy()
{
return Task.CompletedTask;
}
Task<IWorld> IBlockEntity.GetWorld() =>
Task.FromResult(World);

public virtual Task OnCreated()
{
return Task.CompletedTask;
}
Task<BlockWorldPos> IBlockEntity.GetPosition() =>
Task.FromResult(Position);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MineCase.Server.Game.BlockEntities.Components;
using MineCase.Server.Game.Entities;
using MineCase.Server.Game.Entities.Components;
using MineCase.Server.Game.Windows;
Expand All @@ -15,84 +16,11 @@ namespace MineCase.Server.Game.BlockEntities
[Reentrant]
internal class ChestBlockEntityGrain : BlockEntityGrain, IChestBlockEntity
{
private Slot[] _slots;
private IChestBlockEntity _neightborEntity;

public override Task OnActivateAsync()
{
_slots = Enumerable.Repeat(Slot.Empty, ChestSlotArea.ChestSlotsCount).ToArray();
return base.OnActivateAsync();
}

public Task<Slot> GetSlot(int slotIndex)
{
return Task.FromResult(_slots[slotIndex]);
}

public Task SetSlot(int slotIndex, Slot item)
protected override async Task InitializeComponents()
{
_slots[slotIndex] = item;
return Task.CompletedTask;
}

private IChestWindow _chestWindow;

public async Task UseBy(IPlayer player)
{
var masterEntity = FindMasterEntity(_neightborEntity);
if (masterEntity.GetPrimaryKeyString() == this.GetPrimaryKeyString())
{
if (_chestWindow == null)
{
_chestWindow = GrainFactory.GetGrain<IChestWindow>(Guid.NewGuid());
await _chestWindow.SetEntities((_neightborEntity == null ?
new[] { this.AsReference<IChestBlockEntity>() } : new[] { this.AsReference<IChestBlockEntity>(), _neightborEntity }).AsImmutable());
}

await player.Tell(new OpenWindow { Window = _chestWindow });
}
else
{
await masterEntity.UseBy(player);
}
}

public override async Task Destroy()
{
if (_chestWindow != null)
await _chestWindow.Destroy();
}

public async Task ClearNeighborEntity()
{
_neightborEntity = null;
if (_chestWindow != null)
{
await _chestWindow.Destroy();
await _chestWindow.SetEntities(new[] { this.AsReference<IChestBlockEntity>() }.AsImmutable());
}
}

public async Task SetNeighborEntity(IChestBlockEntity chestEntity)
{
_neightborEntity = chestEntity;
if (_chestWindow != null)
{
await _chestWindow.Destroy();
await _chestWindow.SetEntities(new[] { this.AsReference<IChestBlockEntity>(), chestEntity }.AsImmutable());
}
}

private IChestBlockEntity FindMasterEntity(IChestBlockEntity neighborEntity)
{
if (neighborEntity == null)
return this.AsReference<IChestBlockEntity>();

// 按 X, Z 排序取最小
return (from e in new[] { this.AsReference<IChestBlockEntity>(), neighborEntity }
let pos = e.GetBlockEntityPosition()
orderby pos.X, pos.Z
select e).First();
await base.InitializeComponents();
await SetComponent(new SlotContainerComponent(ChestSlotArea.ChestSlotsCount));
await SetComponent(new ChestComponent());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using MineCase.Engine;
using MineCase.Server.Components;

namespace MineCase.Server.Game.BlockEntities.Components
{
internal class BlockEntityLiftTimeComponent : Component<BlockEntityGrain>, IHandle<SpawnBlockEntity>, IHandle<DestroyBlockEntity>
{
public BlockEntityLiftTimeComponent(string name = "blockEntityLifeTime")
: base(name)
{
}

async Task IHandle<SpawnBlockEntity>.Handle(SpawnBlockEntity message)
{
await AttachedObject.GetComponent<WorldComponent>().SetWorld(message.World);
await AttachedObject.GetComponent<BlockWorldPositionComponent>().SetBlockWorldPosition(message.Position);
}

Task IHandle<DestroyBlockEntity>.Handle(DestroyBlockEntity message)
{
return AttachedObject.Tell(Disable.Default);
}
}
}
Loading

0 comments on commit 8120510

Please sign in to comment.