refactor: TileRegistry replaces parallel tile/view collections

This commit is contained in:
2026-05-14 01:12:47 +02:00
parent 34a329ad02
commit 49c9a7904d
6 changed files with 136 additions and 85 deletions

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Core.Ports; using Core.Ports;
using UnityEngine; using UnityEngine;
using Random = System.Random; using Random = System.Random;
@@ -25,7 +26,7 @@ namespace Core.Domain
public event Action OnSpawnNpc; public event Action OnSpawnNpc;
public event Action<PowerUpType, string> OnSpawnPowerUp; public event Action<PowerUpType, string> OnSpawnPowerUp;
private readonly List<Tile> _tiles; private readonly IReadOnlyList<Tile> _tiles;
private readonly IPersistenceService _persistenceService; private readonly IPersistenceService _persistenceService;
private readonly Random _rng = new(); private readonly Random _rng = new();
private int _playerFloorIndex = 0; private int _playerFloorIndex = 0;
@@ -40,7 +41,7 @@ namespace Core.Domain
public int ComboMultiplier { get; private set; } = 1; public int ComboMultiplier { get; private set; } = 1;
public event Action<int> OnComboUpdated; public event Action<int> OnComboUpdated;
public GameSession(List<Tile> tiles, IPersistenceService persistenceService) public GameSession(IReadOnlyList<Tile> tiles, IPersistenceService persistenceService)
{ {
_tiles = tiles; _tiles = tiles;
_persistenceService = persistenceService; _persistenceService = persistenceService;
@@ -124,15 +125,12 @@ namespace Core.Domain
private void SpawnNextOrb() private void SpawnNextOrb()
{ {
var validTiles = _tiles.FindAll(t => var validTiles = _tiles.Where(t =>
t.CurrentState == TileState.Stable && t.CurrentState == TileState.Stable &&
t.Floor == _playerFloorIndex t.Floor == _playerFloorIndex).ToList();
);
if (validTiles.Count == 0) if (validTiles.Count == 0)
{ validTiles = _tiles.Where(t => t.CurrentState == TileState.Stable).ToList();
validTiles = _tiles.FindAll(t => t.CurrentState == TileState.Stable);
}
if (validTiles.Count == 0) if (validTiles.Count == 0)
{ {
@@ -173,15 +171,12 @@ namespace Core.Domain
private void SpawnRandomPowerUp() private void SpawnRandomPowerUp()
{ {
var validTiles = _tiles.FindAll(t => var validTiles = _tiles.Where(t =>
t.CurrentState == TileState.Stable && t.CurrentState == TileState.Stable &&
t.Floor == _playerFloorIndex t.Floor == _playerFloorIndex).ToList();
);
if (validTiles.Count == 0) if (validTiles.Count == 0)
{ validTiles = _tiles.Where(t => t.CurrentState == TileState.Stable).ToList();
validTiles = _tiles.FindAll(t => t.CurrentState == TileState.Stable);
}
if (validTiles.Count == 0) return; if (validTiles.Count == 0) return;

View File

@@ -1,6 +1,5 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Core.Domain;
using UnityEngine; using UnityEngine;
namespace Infrastructure.Unity namespace Infrastructure.Unity
@@ -11,43 +10,12 @@ namespace Infrastructure.Unity
[SerializeField] private float hiddenAlpha = 0.1f; [SerializeField] private float hiddenAlpha = 0.1f;
[SerializeField] private float visibleAlpha = 1.0f; [SerializeField] private float visibleAlpha = 1.0f;
private GameSession _gameSession;
private List<List<TileViewAdapter>> _floors; private List<List<TileViewAdapter>> _floors;
private int _currentFloorIndex = -1; private int _currentFloorIndex = -1;
public void Initialize(GameSession gameSession, List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews, int totalFloors) public void Initialize(TileRegistry registry, int totalFloors)
{ {
_gameSession = gameSession; _floors = registry.GroupViewsByFloor(totalFloors);
_floors = new List<List<TileViewAdapter>>();
for (var i = 0; i < totalFloors; i++)
{
_floors.Add(new List<TileViewAdapter>());
}
foreach (var tile in allTiles)
{
if (tileViews.TryGetValue(tile.Id, out var view))
{
// Safety check for array bounds
if (tile.Floor < _floors.Count)
{
_floors[tile.Floor].Add(view);
}
}
}
}
private void Update()
{
if (_gameSession == null) return;
// Check if player changed floors
// We read the private field _playerFloorIndex via a public getter we need to add,
// OR we just track it locally if you updated GameSession to expose it.
// Assuming GameSession doesn't expose it publically yet, let's rely on GameBootstrap passing it or just hack it:
// Ideally, GameSession should emit an event 'OnFloorChanged'.
// For now, let's assume we can get it or we passed the player reference.
} }
// Call this from GameBootstrap.Update() // Call this from GameBootstrap.Update()

View File

@@ -1,5 +1,4 @@
using System.Collections; using System.Collections;
using System.Collections.Generic;
using Core.Domain; using Core.Domain;
using Core.Ports; using Core.Ports;
using TMPro; using TMPro;
@@ -36,8 +35,7 @@ namespace Infrastructure.Unity
[Header("Power Ups")] [Header("Power Ups")]
[SerializeField] private PowerUpViewAdapter powerUpPrefab; [SerializeField] private PowerUpViewAdapter powerUpPrefab;
private readonly List<Tile> _allTiles = new(); private readonly TileRegistry _tileRegistry = new();
private readonly Dictionary<string, TileViewAdapter> _tileViews = new();
private GameSession _gameSession; private GameSession _gameSession;
private IPersistenceService _persistenceService; private IPersistenceService _persistenceService;
private InputSystem_Actions _actions; private InputSystem_Actions _actions;
@@ -67,7 +65,7 @@ namespace Infrastructure.Unity
{ {
_inputBlockTimer = 0.5f; _inputBlockTimer = 0.5f;
_persistenceService = new PlayerPrefsPersistenceAdapter(); _persistenceService = new PlayerPrefsPersistenceAdapter();
_gameSession = new GameSession(_allTiles, _persistenceService); _gameSession = new GameSession(_tileRegistry.AllTiles, _persistenceService);
// Set Theme based on High Score // Set Theme based on High Score
ThemeManager.CurrentTheme = ThemeManager.GetTheme(_gameSession.HighScore); ThemeManager.CurrentTheme = ThemeManager.GetTheme(_gameSession.HighScore);
@@ -76,7 +74,7 @@ namespace Infrastructure.Unity
if (levelGenerator) if (levelGenerator)
{ {
StartCoroutine(levelGenerator.GenerateAsync(soundManager, _allTiles, _tileViews, cameraController, StartCoroutine(levelGenerator.GenerateAsync(soundManager, _tileRegistry, cameraController,
rumbleManager, rumbleManager,
() => () =>
{ {
@@ -84,7 +82,7 @@ namespace Infrastructure.Unity
{ {
floorVisibilityManager = gameObject.AddComponent<FloorVisibilityManager>(); floorVisibilityManager = gameObject.AddComponent<FloorVisibilityManager>();
} }
floorVisibilityManager.Initialize(_gameSession, _allTiles, _tileViews, floorsCount); floorVisibilityManager.Initialize(_tileRegistry, floorsCount);
SpawnDeathPlane(); SpawnDeathPlane();
SpawnPlayer(); SpawnPlayer();
@@ -155,9 +153,10 @@ namespace Infrastructure.Unity
// Hard Mode: Decay faster as score increases // Hard Mode: Decay faster as score increases
var decayMultiplier = 1.0f + (_gameSession.Score / 500f); var decayMultiplier = 1.0f + (_gameSession.Score / 500f);
for (var i = _allTiles.Count - 1; i >= 0; i--) var allTiles = _tileRegistry.AllTiles;
for (var i = allTiles.Count - 1; i >= 0; i--)
{ {
_allTiles[i].Tick(dt * dilation * decayMultiplier); allTiles[i].Tick(dt * dilation * decayMultiplier);
} }
} }
@@ -185,7 +184,7 @@ namespace Infrastructure.Unity
{ {
if (_currentOrbInstance) Destroy(_currentOrbInstance); if (_currentOrbInstance) Destroy(_currentOrbInstance);
if (!_tileViews.TryGetValue(tileId, out var tileView)) return; if (!_tileRegistry.TryGetView(tileId, out var tileView)) return;
if (!tileView) return; if (!tileView) return;
var spawnPos = tileView.transform.position + Vector3.up; var spawnPos = tileView.transform.position + Vector3.up;
@@ -303,18 +302,18 @@ namespace Infrastructure.Unity
private void SpawnNpc() private void SpawnNpc()
{ {
var validTiles = _allTiles.FindAll(t => t.Floor == 0 && t.CurrentState == TileState.Stable); var validTiles = _tileRegistry.FindTiles(t => t.Floor == 0 && t.CurrentState == TileState.Stable);
if (validTiles.Count == 0) if (validTiles.Count == 0)
{ {
validTiles = _allTiles.FindAll(t => t.CurrentState == TileState.Stable); validTiles = _tileRegistry.FindTiles(t => t.CurrentState == TileState.Stable);
} }
if (validTiles.Count == 0) return; if (validTiles.Count == 0) return;
var randomTile = validTiles[Random.Range(0, validTiles.Count)]; var randomTile = validTiles[Random.Range(0, validTiles.Count)];
if (!_tileViews.TryGetValue(randomTile.Id, out var tileView)) return; if (!_tileRegistry.TryGetView(randomTile.Id, out var tileView)) return;
if (!tileView) return; if (!tileView) return;
var spawnPos = tileView.transform.position + Vector3.up * 5f; var spawnPos = tileView.transform.position + Vector3.up * 5f;
@@ -359,7 +358,7 @@ namespace Infrastructure.Unity
private void SpawnPowerUp(PowerUpType type, string tileId) private void SpawnPowerUp(PowerUpType type, string tileId)
{ {
if (!_tileViews.TryGetValue(tileId, out var tileView)) return; if (!_tileRegistry.TryGetView(tileId, out var tileView)) return;
if (!tileView) return; if (!tileView) return;
var spawnPos = tileView.transform.position + Vector3.up * 0.5f; var spawnPos = tileView.transform.position + Vector3.up * 0.5f;
@@ -381,21 +380,22 @@ namespace Infrastructure.Unity
private void OnBeatMeasure() private void OnBeatMeasure()
{ {
if (_allTiles.Count == 0) return; var allTiles = _tileRegistry.AllTiles;
if (allTiles.Count == 0) return;
var pulseCount = 25; var pulseCount = 25;
for (var i = 0; i < pulseCount; i++) for (var i = 0; i < pulseCount; i++)
{ {
var randIndex = Random.Range(0, _allTiles.Count); var randIndex = Random.Range(0, allTiles.Count);
var tile = _allTiles[randIndex]; var tile = allTiles[randIndex];
if (tile.Floor < _currentPlayerFloorIndex) continue; if (tile.Floor < _currentPlayerFloorIndex) continue;
if (tile.Floor > _currentPlayerFloorIndex + 1) continue; if (tile.Floor > _currentPlayerFloorIndex + 1) continue;
if (tile.CurrentState != TileState.Stable) continue; if (tile.CurrentState != TileState.Stable) continue;
if (_tileViews.TryGetValue(tile.Id, out var tileView)) if (_tileRegistry.TryGetView(tile.Id, out var tileView))
{ {
tileView.PulseEmission(Random.Range(1.2f, 2.0f)); tileView.PulseEmission(Random.Range(1.2f, 2.0f));
} }

View File

@@ -32,23 +32,23 @@ namespace Infrastructure.Unity
private TilePool _tilePool; private TilePool _tilePool;
public IEnumerator GenerateAsync(SoundManager soundManager, List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews, CameraController camera, RumbleManager rumble, Action onComplete) public IEnumerator GenerateAsync(SoundManager soundManager, TileRegistry registry, CameraController camera, RumbleManager rumble, Action onComplete)
{ {
_tilePool = new TilePool(tilePrefab, transform); _tilePool = new TilePool(tilePrefab, transform);
var stopwatch = new Stopwatch(); var stopwatch = new Stopwatch();
stopwatch.Start(); stopwatch.Start();
yield return GenerateFloorAsync(0, MapPatterns.GenerateSquare(gridSizeX, gridSizeY), soundManager, allTiles, tileViews, camera, rumble, stopwatch); yield return GenerateFloorAsync(0, MapPatterns.GenerateSquare(gridSizeX, gridSizeY), soundManager, registry, camera, rumble, stopwatch);
yield return GenerateFloorAsync(1, MapPatterns.GenerateDonut(gridSizeX, Mathf.FloorToInt(gridSizeX / 3f)), soundManager, allTiles, tileViews, camera, rumble, stopwatch); yield return GenerateFloorAsync(1, MapPatterns.GenerateDonut(gridSizeX, Mathf.FloorToInt(gridSizeX / 3f)), soundManager, registry, camera, rumble, stopwatch);
yield return GenerateFloorAsync(2, MapPatterns.GenerateCircle(gridSizeX), soundManager, allTiles, tileViews, camera, rumble, stopwatch); yield return GenerateFloorAsync(2, MapPatterns.GenerateCircle(gridSizeX), soundManager, registry, camera, rumble, stopwatch);
stopwatch?.Stop(); stopwatch?.Stop();
onComplete?.Invoke(); onComplete?.Invoke();
} }
private IEnumerator GenerateFloorAsync(int floorIndex, List<Vector2Int> coordinates, SoundManager soundManager, private IEnumerator GenerateFloorAsync(int floorIndex, List<Vector2Int> coordinates, SoundManager soundManager,
List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews, CameraController camera, RumbleManager rumble, Stopwatch stopwatch) TileRegistry registry, CameraController camera, RumbleManager rumble, Stopwatch stopwatch)
{ {
var yOffset = -(floorIndex * floorHeightDistance); var yOffset = -(floorIndex * floorHeightDistance);
var xOffset = gridSizeX / 2f; var xOffset = gridSizeX / 2f;
@@ -59,7 +59,7 @@ namespace Infrastructure.Unity
foreach (var coord in coordinates) foreach (var coord in coordinates)
{ {
var pos = new Vector3(coord.x - xOffset, yOffset, coord.y - zOffset); var pos = new Vector3(coord.x - xOffset, yOffset, coord.y - zOffset);
CreateTile(pos, $"{floorIndex}_{coord.x}_{coord.y}", floorIndex, soundManager, allTiles, tileViews, camera, rumble); CreateTile(pos, $"{floorIndex}_{coord.x}_{coord.y}", floorIndex, soundManager, registry, camera, rumble);
if (stopwatch.ElapsedMilliseconds > frameBudgetMs) if (stopwatch.ElapsedMilliseconds > frameBudgetMs)
{ {
@@ -129,7 +129,7 @@ namespace Infrastructure.Unity
} }
private void CreateTile(Vector3 position, string id, int floorIndex, SoundManager soundManager, private void CreateTile(Vector3 position, string id, int floorIndex, SoundManager soundManager,
List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews, CameraController camera, RumbleManager rumble) TileRegistry registry, CameraController camera, RumbleManager rumble)
{ {
var go = _tilePool.Get(); var go = _tilePool.Get();
go.transform.position = position; go.transform.position = position;
@@ -172,8 +172,7 @@ namespace Infrastructure.Unity
} }
}; };
allTiles.Add(tileLogic); registry.Register(tileLogic, go);
tileViews.Add(id, go);
} }
} }
} }

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using Core.Domain;
namespace Infrastructure.Unity
{
public class TileRegistry
{
private readonly List<Tile> _tiles = new();
private readonly Dictionary<string, TileViewAdapter> _views = new();
public IReadOnlyList<Tile> AllTiles => _tiles;
public void Register(Tile tile, TileViewAdapter view = null)
{
_tiles.Add(tile);
if (view != null) _views[tile.Id] = view;
}
public bool TryGetView(string tileId, out TileViewAdapter view)
=> _views.TryGetValue(tileId, out view);
public List<Tile> FindTiles(Predicate<Tile> predicate)
=> _tiles.FindAll(predicate);
public List<List<TileViewAdapter>> GroupViewsByFloor(int floorCount)
{
var floors = new List<List<TileViewAdapter>>(floorCount);
for (var i = 0; i < floorCount; i++) floors.Add(new List<TileViewAdapter>());
foreach (var tile in _tiles)
{
if (tile.Floor < floorCount && _views.TryGetValue(tile.Id, out var view))
floors[tile.Floor].Add(view);
}
return floors;
}
}
}

View File

@@ -0,0 +1,49 @@
using NUnit.Framework;
using Core.Domain;
using Infrastructure.Unity;
namespace DecayGrid.Tests
{
public class TileRegistryTests
{
private TileRegistry _registry;
[SetUp]
public void SetUp() => _registry = new TileRegistry();
[Test]
public void Register_AddsToAllTiles()
{
var tile = new Tile("0_0_0", 0, 0.5f, 2f);
_registry.Register(tile);
Assert.AreEqual(1, _registry.AllTiles.Count);
}
[Test]
public void FindTiles_ReturnsMatchingTiles()
{
var tile = new Tile("0_0_0", 0, 0.5f, 2f);
_registry.Register(tile);
var result = _registry.FindTiles(t => t.CurrentState == TileState.Stable);
Assert.AreEqual(1, result.Count);
}
[Test]
public void FindTiles_ExcludesNonMatchingTiles()
{
var tile = new Tile("0_0_0", 0, 0.5f, 2f);
tile.StepOn(); // Now Warning
_registry.Register(tile);
var result = _registry.FindTiles(t => t.CurrentState == TileState.Stable);
Assert.AreEqual(0, result.Count);
}
[Test]
public void TryGetView_ReturnsFalse_WhenNoViewRegistered()
{
var tile = new Tile("0_0_0", 0, 0.5f, 2f);
_registry.Register(tile); // no view
Assert.IsFalse(_registry.TryGetView("0_0_0", out _));
}
}
}