using System.Collections.Generic; using Minesweeper.Core; using Minesweeper.ECS.Components; using Unity.Collections; using Unity.Entities; namespace Minesweeper.ECS { public sealed class BoardEcsSyncService : IBoardEcsSyncService { private readonly Dictionary cellsByIndex = new Dictionary(); private int syncedWidth; private int syncedHeight; public void ClearBoard() { if (!TryGetEntityManager(out var entityManager)) { return; } ClearCells(entityManager); cellsByIndex.Clear(); syncedWidth = 0; syncedHeight = 0; } public void SyncBoard(IBoardService boardService) { if (!TryGetEntityManager(out var entityManager)) { return; } if (syncedWidth != boardService.Width || syncedHeight != boardService.Height || cellsByIndex.Count != boardService.Width * boardService.Height) { ClearCells(entityManager); cellsByIndex.Clear(); syncedWidth = boardService.Width; syncedHeight = boardService.Height; } else { ClearChangedTags(entityManager); } var boardEntity = GetOrCreateSingleton(entityManager); entityManager.SetComponentData(boardEntity, new BoardConfigComponent { Width = boardService.Width, Height = boardService.Height, MinesCount = boardService.MinesCount, OpenedSafeCellsCount = boardService.OpenedSafeCellsCount, FlaggedCellsCount = boardService.FlaggedCellsCount, IsGenerated = ToByte(boardService.IsGenerated) }); var archetype = entityManager.CreateArchetype(typeof(CellComponent)); var cells = boardService.GetCells(); for (var i = 0; i < cells.Count; i++) { var cell = cells[i]; var index = ToIndex(cell.X, cell.Y, boardService.Width); if (!cellsByIndex.TryGetValue(index, out var entity) || !entityManager.Exists(entity)) { entity = entityManager.CreateEntity(archetype); cellsByIndex[index] = entity; } SetCell(entityManager, entity, cell, boardService.Width, false); } } public void SyncCells(IReadOnlyList cells, IBoardService boardService) { if (cells == null || cells.Count == 0 || !TryGetEntityManager(out var entityManager)) { return; } var boardEntity = GetOrCreateSingleton(entityManager); entityManager.SetComponentData(boardEntity, new BoardConfigComponent { Width = boardService.Width, Height = boardService.Height, MinesCount = boardService.MinesCount, OpenedSafeCellsCount = boardService.OpenedSafeCellsCount, FlaggedCellsCount = boardService.FlaggedCellsCount, IsGenerated = ToByte(boardService.IsGenerated) }); ClearChangedTags(entityManager); for (var i = 0; i < cells.Count; i++) { var cell = cells[i]; var index = ToIndex(cell.X, cell.Y, boardService.Width); if (cellsByIndex.TryGetValue(index, out var entity) && entityManager.Exists(entity)) { SetCell(entityManager, entity, cell, boardService.Width, true); } } } public void SyncGameState(GameState state, bool hasFirstClick) { if (!TryGetEntityManager(out var entityManager)) { return; } var stateEntity = GetOrCreateSingleton(entityManager); entityManager.SetComponentData(stateEntity, new GameStateComponent { State = state, HasFirstClick = ToByte(hasFirstClick) }); } private static bool TryGetEntityManager(out EntityManager entityManager) { var world = World.DefaultGameObjectInjectionWorld; if (world == null || !world.IsCreated) { entityManager = default; return false; } entityManager = world.EntityManager; return true; } private static void ClearCells(EntityManager entityManager) { var query = entityManager.CreateEntityQuery(typeof(CellComponent)); entityManager.DestroyEntity(query); query.Dispose(); } private static void ClearChangedTags(EntityManager entityManager) { var query = entityManager.CreateEntityQuery(typeof(CellChangedTag)); entityManager.RemoveComponent(query); query.Dispose(); } private static void SetCell(EntityManager entityManager, Entity entity, BoardCellData cell, int width, bool markChanged) { entityManager.SetComponentData(entity, new CellComponent { X = cell.X, Y = cell.Y, Index = ToIndex(cell.X, cell.Y, width), IsMine = ToByte(cell.IsMine), IsOpened = ToByte(cell.IsOpened), IsFlagged = ToByte(cell.IsFlagged), NeighborMines = cell.NeighborMines }); if (markChanged && !entityManager.HasComponent(entity)) { entityManager.AddComponent(entity); } } private static int ToIndex(int x, int y, int width) { return y * width + x; } private static Entity GetOrCreateSingleton(EntityManager entityManager) where T : unmanaged, IComponentData { var query = entityManager.CreateEntityQuery(typeof(T)); Entity entity; if (query.IsEmptyIgnoreFilter) { entity = entityManager.CreateEntity(typeof(T)); } else { var entities = query.ToEntityArray(Allocator.Temp); entity = entities[0]; for (var i = 1; i < entities.Length; i++) { entityManager.DestroyEntity(entities[i]); } entities.Dispose(); } query.Dispose(); return entity; } private static byte ToByte(bool value) { return value ? (byte)1 : (byte)0; } } }