Files
2026-06-07 01:12:10 +07:00

202 lines
6.8 KiB
C#

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<int, Entity> cellsByIndex = new Dictionary<int, Entity>();
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<BoardConfigComponent>(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<BoardCellData> cells, IBoardService boardService)
{
if (cells == null || cells.Count == 0 || !TryGetEntityManager(out var entityManager))
{
return;
}
var boardEntity = GetOrCreateSingleton<BoardConfigComponent>(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<GameStateComponent>(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<CellChangedTag>(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<CellChangedTag>(entity))
{
entityManager.AddComponent<CellChangedTag>(entity);
}
}
private static int ToIndex(int x, int y, int width)
{
return y * width + x;
}
private static Entity GetOrCreateSingleton<T>(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;
}
}
}