add flood swe module and 2km building bundles

This commit is contained in:
2026-02-04 01:05:20 +01:00
parent 2c77c0d215
commit ff5af7a63a
57 changed files with 5369 additions and 44 deletions

View File

@@ -15,6 +15,7 @@ public class GeoTileAddressablesLoader : MonoBehaviour
[Header("Paths")]
[SerializeField] private string tileBundleFolderName = "TileBundles";
[SerializeField] private string manifestFileName = "TileManifest.json";
[SerializeField] private string buildingManifestFileName = "TileBuildingsManifest.json";
[SerializeField] private string buildTargetFolderOverride = "";
[Header("Streaming")]
@@ -24,12 +25,24 @@ public class GeoTileAddressablesLoader : MonoBehaviour
[SerializeField] private int maxConcurrentLoads = 2;
[SerializeField] private bool verboseLogging = false;
[Header("Buildings (2km blocks)")]
[SerializeField] private bool loadBuildingsFor2kmBlocks = true;
[SerializeField] private int buildingBlockSizeInTiles = 2;
private TileManifest manifest;
private TileManifest buildingManifest;
private readonly Dictionary<string, TileEntry> tiles = new Dictionary<string, TileEntry>();
private readonly Dictionary<string, GameObject> loaded = new Dictionary<string, GameObject>();
private readonly HashSet<string> loading = new HashSet<string>();
private readonly HashSet<string> queued = new HashSet<string>();
private readonly Queue<string> loadQueue = new Queue<string>();
private readonly Dictionary<string, TileEntry> buildingTiles = new Dictionary<string, TileEntry>();
private readonly Dictionary<string, GameObject> buildingLoaded = new Dictionary<string, GameObject>();
private readonly HashSet<string> buildingLoading = new HashSet<string>();
private readonly HashSet<string> buildingQueued = new HashSet<string>();
private readonly Queue<string> buildingLoadQueue = new Queue<string>();
private readonly Dictionary<string, string> tileBlockKey = new Dictionary<string, string>();
private readonly Dictionary<string, string> blockBuildingKey = new Dictionary<string, string>();
private bool initialized;
private float nextUpdateTime;
@@ -42,6 +55,8 @@ public class GeoTileAddressablesLoader : MonoBehaviour
maxConcurrentLoads = 1;
if (updateInterval < 0.05f)
updateInterval = 0.05f;
if (buildingBlockSizeInTiles < 1)
buildingBlockSizeInTiles = 1;
}
private void Awake()
@@ -94,6 +109,9 @@ public class GeoTileAddressablesLoader : MonoBehaviour
tiles[key] = tile;
}
LoadBuildingManifest(basePath);
BuildBuildingBlocks();
Log($"Manifest loaded. Tiles={tiles.Count} CatalogFile={manifest.catalogFile}");
var catalogPath = Path.Combine(basePath, manifest.catalogFile);
@@ -118,6 +136,38 @@ public class GeoTileAddressablesLoader : MonoBehaviour
nextUpdateTime = Time.time;
}
private void LoadBuildingManifest(string basePath)
{
buildingManifest = null;
buildingTiles.Clear();
if (!loadBuildingsFor2kmBlocks || string.IsNullOrWhiteSpace(buildingManifestFileName))
return;
var path = Path.Combine(basePath, buildingManifestFileName);
if (!File.Exists(path))
{
Log($"Building manifest not found: {path}");
return;
}
buildingManifest = JsonUtility.FromJson<TileManifest>(File.ReadAllText(path));
if (buildingManifest == null || buildingManifest.tiles == null || buildingManifest.tiles.Length == 0)
{
Log("Building manifest is empty or invalid.");
return;
}
foreach (var tile in buildingManifest.tiles)
{
var key = !string.IsNullOrWhiteSpace(tile.tileKey) ? tile.tileKey : tile.tileId;
if (!string.IsNullOrWhiteSpace(key))
buildingTiles[key] = tile;
}
Log($"Building manifest loaded. BuildingTiles={buildingTiles.Count}");
}
private void Update()
{
if (!initialized || player == null)
@@ -136,9 +186,14 @@ public class GeoTileAddressablesLoader : MonoBehaviour
{
var playerPos = player.position;
var tileSize = manifest.tileSizeMeters;
var wantedTiles = new HashSet<string>();
HashSet<string> wantedBlocks = (loadBuildingsFor2kmBlocks && buildingTiles.Count > 0)
? new HashSet<string>()
: null;
foreach (var kvp in tiles)
{
var tileKey = kvp.Key;
var tile = kvp.Value;
var tileCenter = new Vector3(
tile.offsetX + tileSize * 0.5f,
@@ -148,15 +203,65 @@ public class GeoTileAddressablesLoader : MonoBehaviour
new Vector2(playerPos.x, playerPos.z),
new Vector2(tileCenter.x, tileCenter.z));
if (distance <= loadRadiusMeters)
bool withinLoad = distance <= loadRadiusMeters;
bool withinKeep = distance <= unloadRadiusMeters;
if (withinLoad || (withinKeep && loaded.ContainsKey(tileKey)))
wantedTiles.Add(tileKey);
if (wantedBlocks != null && tileBlockKey.TryGetValue(tileKey, out var blockKey))
{
EnqueueTileLoad(kvp.Key);
}
else if (distance >= unloadRadiusMeters)
{
UnloadTile(kvp.Key);
if (withinLoad)
wantedBlocks.Add(blockKey);
else if (withinKeep && blockBuildingKey.TryGetValue(blockKey, out var buildingKey) && buildingLoaded.ContainsKey(buildingKey))
wantedBlocks.Add(blockKey);
}
}
foreach (var tileKey in wantedTiles)
EnqueueTileLoad(tileKey);
if (loaded.Count == 0)
{
if (wantedBlocks == null)
return;
}
var toUnload = new List<string>();
foreach (var kvp in loaded)
{
if (!wantedTiles.Contains(kvp.Key))
toUnload.Add(kvp.Key);
}
for (int i = 0; i < toUnload.Count; i++)
UnloadTile(toUnload[i]);
if (wantedBlocks == null)
return;
var wantedBuildingKeys = new HashSet<string>();
foreach (var blockKey in wantedBlocks)
{
if (blockBuildingKey.TryGetValue(blockKey, out var buildingKey))
wantedBuildingKeys.Add(buildingKey);
}
foreach (var buildingKey in wantedBuildingKeys)
EnqueueBuildingLoad(buildingKey);
if (buildingLoaded.Count == 0)
return;
var toUnloadBuildings = new List<string>();
foreach (var kvp in buildingLoaded)
{
if (!wantedBuildingKeys.Contains(kvp.Key))
toUnloadBuildings.Add(kvp.Key);
}
for (int i = 0; i < toUnloadBuildings.Count; i++)
UnloadBuilding(toUnloadBuildings[i]);
}
private void EnqueueTileLoad(string tileKey)
@@ -168,13 +273,38 @@ public class GeoTileAddressablesLoader : MonoBehaviour
queued.Add(tileKey);
}
private void EnqueueBuildingLoad(string buildingKey)
{
if (buildingLoaded.ContainsKey(buildingKey) || buildingLoading.Contains(buildingKey) || buildingQueued.Contains(buildingKey))
return;
buildingLoadQueue.Enqueue(buildingKey);
buildingQueued.Add(buildingKey);
}
private void ProcessQueue()
{
while (loading.Count < maxConcurrentLoads && loadQueue.Count > 0)
bool preferTiles = true;
while (loadQueue.Count > 0 || buildingLoadQueue.Count > 0)
{
var tileKey = loadQueue.Dequeue();
queued.Remove(tileKey);
StartLoad(tileKey);
int inFlight = loading.Count + buildingLoading.Count;
if (inFlight >= maxConcurrentLoads)
return;
if ((preferTiles && loadQueue.Count > 0) || buildingLoadQueue.Count == 0)
{
var tileKey = loadQueue.Dequeue();
queued.Remove(tileKey);
StartLoad(tileKey);
}
else
{
var buildingKey = buildingLoadQueue.Dequeue();
buildingQueued.Remove(buildingKey);
StartBuildingLoad(buildingKey);
}
preferTiles = !preferTiles;
}
}
@@ -205,6 +335,33 @@ public class GeoTileAddressablesLoader : MonoBehaviour
};
}
private void StartBuildingLoad(string buildingKey)
{
if (!buildingTiles.TryGetValue(buildingKey, out var tile))
return;
buildingLoading.Add(buildingKey);
Log($"Loading buildings {buildingKey}...");
var handle = Addressables.InstantiateAsync(buildingKey, tilesParent);
handle.Completed += op =>
{
buildingLoading.Remove(buildingKey);
if (op.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError($"[GeoTileAddressablesLoader] Building load failed for {buildingKey}: {op.OperationException}");
return;
}
var instance = op.Result;
instance.name = buildingKey;
instance.transform.position = new Vector3(tile.offsetX, tile.baseY, tile.offsetZ);
buildingLoaded[buildingKey] = instance;
Log($"Loaded buildings {buildingKey}. LoadedCount={buildingLoaded.Count}");
};
}
private void UnloadTile(string tileKey)
{
if (!loaded.TryGetValue(tileKey, out var instance))
@@ -215,6 +372,113 @@ public class GeoTileAddressablesLoader : MonoBehaviour
Log($"Unloaded tile {tileKey}. LoadedCount={loaded.Count}");
}
private void UnloadBuilding(string buildingKey)
{
if (!buildingLoaded.TryGetValue(buildingKey, out var instance))
return;
Addressables.ReleaseInstance(instance);
buildingLoaded.Remove(buildingKey);
Log($"Unloaded buildings {buildingKey}. LoadedCount={buildingLoaded.Count}");
}
private void BuildBuildingBlocks()
{
tileBlockKey.Clear();
blockBuildingKey.Clear();
if (!loadBuildingsFor2kmBlocks)
return;
int blockSize = Math.Max(1, buildingBlockSizeInTiles);
foreach (var kvp in tiles)
{
if (!TryGetTileXY(kvp.Value, out int x, out int y))
continue;
int blockX = x - (x % blockSize);
int blockY = y - (y % blockSize);
string blockKey = $"{blockX}_{blockY}";
tileBlockKey[kvp.Key] = blockKey;
}
if (buildingTiles.Count == 0)
return;
foreach (var kvp in buildingTiles)
{
if (!TryGetTileXY(kvp.Value, out int x, out int y))
continue;
int blockX = x - (x % blockSize);
int blockY = y - (y % blockSize);
string blockKey = $"{blockX}_{blockY}";
blockBuildingKey[blockKey] = kvp.Key;
}
}
private static bool TryGetTileXY(TileEntry tile, out int x, out int y)
{
if (!string.IsNullOrWhiteSpace(tile.tileKey) && TryParseTileKey(tile.tileKey, out x, out y))
return true;
return TryParseTileId(tile.tileId, out x, out y);
}
private static bool TryParseTileKey(string tileKey, out int x, out int y)
{
x = 0;
y = 0;
if (string.IsNullOrWhiteSpace(tileKey))
return false;
var parts = tileKey.Split('_');
if (parts.Length != 2)
return false;
return int.TryParse(parts[0], out x) && int.TryParse(parts[1], out y);
}
private static bool TryParseTileId(string tileId, out int x, out int y)
{
x = 0;
y = 0;
if (string.IsNullOrWhiteSpace(tileId))
return false;
var parts = tileId.Split('_');
var coords = new List<int>();
for (int i = 0; i < parts.Length; i++)
{
var part = parts[i];
if (part.Length < 3)
continue;
bool allDigits = true;
for (int j = 0; j < part.Length; j++)
{
if (!char.IsDigit(part[j]))
{
allDigits = false;
break;
}
}
if (!allDigits)
continue;
if (int.TryParse(part, out int value))
coords.Add(value);
}
if (coords.Count < 2)
return false;
x = coords[coords.Count - 2];
y = coords[coords.Count - 1];
return true;
}
private static string GetBuildTargetFolderName()
{
switch (Application.platform)
@@ -252,6 +516,14 @@ public class GeoTileAddressablesLoader : MonoBehaviour
loading.Clear();
queued.Clear();
loadQueue.Clear();
foreach (var instance in buildingLoaded.Values)
Addressables.ReleaseInstance(instance);
buildingLoaded.Clear();
buildingLoading.Clear();
buildingQueued.Clear();
buildingLoadQueue.Clear();
}
private void Log(string message)