diff --git a/Assets/Cryville.EEW.Unity/Map/CameraController.cs b/Assets/Cryville.EEW.Unity/Map/CameraController.cs index ec571dd..fb40263 100644 --- a/Assets/Cryville.EEW.Unity/Map/CameraController.cs +++ b/Assets/Cryville.EEW.Unity/Map/CameraController.cs @@ -15,14 +15,18 @@ namespace Cryville.EEW.Unity.Map { MapElementManager m_layerElementSub; [SerializeField] GameObject m_prefabTile; + [SerializeField] + GameObject m_prefabBitmapHolder; readonly MapTileCacheManager _tiles = new(); float _elementLayerZ; void Start() { _camera = GetComponent(); + _tiles.ExtraCachedZoomLevel = 20; _tiles.Parent = m_layerTile; _tiles.PrefabTile = m_prefabTile; + _tiles.PrefabBitmapHolder = m_prefabBitmapHolder; _tiles.CacheDir = Application.temporaryCachePath; _camera.orthographicSize = 0.5f / MathF.Max(1, (float)_camera.pixelWidth / _camera.pixelHeight); _elementLayerZ = m_layerElement.transform.position.z; diff --git a/Assets/Cryville.EEW.Unity/Map/MapTile.cs b/Assets/Cryville.EEW.Unity/Map/MapTile.cs index d2c33e5..f9ac4e6 100644 --- a/Assets/Cryville.EEW.Unity/Map/MapTile.cs +++ b/Assets/Cryville.EEW.Unity/Map/MapTile.cs @@ -1,43 +1,21 @@ using Cryville.EEW.Core.Map; -using System; -using System.IO; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; using UnityEngine; -using UnityEngine.Networking; namespace Cryville.EEW.Unity.Map { [RequireComponent(typeof(SpriteRenderer))] sealed class MapTile : MonoBehaviour { - static readonly SemaphoreSlim _semaphore = new(2); - - static readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(10) }; - [SerializeField] Transform _idView; public MapTileIndex Index { get; set; } - public bool IsEmpty { get; private set; } - - Action _callback; SpriteRenderer _renderer; - UnityWebRequest _req; - DownloadHandlerTexture _texHandler; - Texture2D _tex; - Sprite _sprite; - void Awake() { _renderer = GetComponent(); } - FileInfo _localFile; - bool _downloadDone; - public void Load(MapTileIndex index, string cacheDir, Action onUpdated) { + public void Init(MapTileIndex index) { Index = index; - _callback = onUpdated; - _localFile = new(Path.Combine(cacheDir, $"map/{Index.Z}/{Index.NX}/{Index.NY}")); float z = 1 << index.Z; transform.localPosition = new(index.X / z, -(index.Y + 1) / z, -index.Z / 100f); transform.localScale = new Vector3(1 / z, 1 / z, 1); @@ -45,69 +23,22 @@ namespace Cryville.EEW.Unity.Map { byte e = SharedSettings.Instance.IdBytes[((index.X << 2) + index.Y) & 0x1f]; int ex = e >> 4, ey = e & 0xf; _idView.localPosition = new(ex / 16f, 1 - ey / 16f, -1 / 200f); - if (_localFile.Exists) { - _downloadDone = true; - } - else { - Task.Run(() => RunAsync($"https://server.arcgisonline.com/ArcGIS/rest/services/Ocean/World_Ocean_Base/MapServer/tile/{Index.Z}/{Index.NY}/{Index.NX}")); - } - } - async Task RunAsync(string url) { - await _semaphore.WaitAsync().ConfigureAwait(true); - try { - Directory.CreateDirectory(_localFile.DirectoryName); - using var webStream = await _httpClient.GetStreamAsync(new Uri(url)).ConfigureAwait(true); - using var fileStream = new FileStream(_localFile.FullName, FileMode.Create, FileAccess.Write); - await webStream.CopyToAsync(fileStream).ConfigureAwait(true); - } - finally { - _semaphore.Release(); - } - _downloadDone = true; } + public void Set(Sprite sprite) { + if (_renderer) { + _renderer.sprite = sprite; + } + } + + bool _isDestroyed; + public void Destroy() { + _isDestroyed = true; + } void Update() { - if (_downloadDone) { - try { - _texHandler = new DownloadHandlerTexture(); - _req = new UnityWebRequest($"file:///{_localFile}") { - downloadHandler = _texHandler, - disposeDownloadHandlerOnDispose = true, - }; - _req.SendWebRequest(); - } - catch (Exception ex) { - Debug.LogException(ex); - } - _downloadDone = false; + if (_isDestroyed) { + Destroy(gameObject); } - if (_req == null || !_req.isDone) return; - if (_texHandler.isDone) { - _tex = _texHandler.texture; - _tex.wrapMode = TextureWrapMode.Clamp; - _sprite = Sprite.Create(_tex, new Rect(0, 0, _tex.width, _tex.height), Vector2.zero, _tex.height, 0, SpriteMeshType.FullRect, Vector4.zero, false); - _renderer.sprite = _sprite; - } - else { - Debug.LogError(_req.error); - _localFile.Delete(); - IsEmpty = true; - } - _req.Dispose(); - _req = null; - _callback?.Invoke(this); - } - - void OnDestroy() { - if (_req != null) { - _req.Abort(); - _req.Dispose(); - _texHandler.Dispose(); - } - if (_sprite) Destroy(_sprite); - if (_tex) Destroy(_tex); - IsEmpty = true; - _callback?.Invoke(this); } } } diff --git a/Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolder.cs b/Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolder.cs new file mode 100644 index 0000000..08fe543 --- /dev/null +++ b/Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolder.cs @@ -0,0 +1,35 @@ +using Cryville.EEW.Core.Map; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; + +namespace Cryville.EEW.Unity.Map { + sealed class MapTileBitmapHolder : Core.Map.MapTileBitmapHolder { + MapTileBitmapHolderBehaviour _behaviour; + + public MapTileBitmapHolder(MapTileIndex index, GameObject gameObject) : base(index) { + _behaviour = gameObject.GetComponent(); + } + + protected override void Dispose(bool disposing) { + base.Dispose(disposing); + if (disposing) { + if (_behaviour) _behaviour.Destroy(); + } + } + + protected override Uri GetUri() => + new($"https://server.arcgisonline.com/ArcGIS/rest/services/Ocean/World_Ocean_Base/MapServer/tile/{Index.Z}/{Index.NY}/{Index.NX}"); + + protected override Task LoadBitmap(FileInfo file, CancellationToken cancellationToken) { + _behaviour.Load(file); + return Task.CompletedTask; + } + + public void Bind(MapTile tile) { + _behaviour.Bind(tile); + } + } +} diff --git a/Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolder.cs.meta b/Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolder.cs.meta new file mode 100644 index 0000000..a404621 --- /dev/null +++ b/Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7ad3a3ac7d829249ba21987d585b07f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolderBehaviour.cs b/Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolderBehaviour.cs new file mode 100644 index 0000000..f6e1879 --- /dev/null +++ b/Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolderBehaviour.cs @@ -0,0 +1,79 @@ +using System; +using System.IO; +using UnityEngine; +using UnityEngine.Networking; + +namespace Cryville.EEW.Unity.Map { + sealed class MapTileBitmapHolderBehaviour : MonoBehaviour { + Action _callback; + public void Bind(MapTile tile) { + if (_isDone) + tile.Set(_sprite); + else + _callback += tile.Set; + } + + UnityWebRequest _req; + DownloadHandlerTexture _texHandler; + Texture2D _tex; + Sprite _sprite; + + FileInfo _localFile; + bool _isReady; + bool _isDone; + public void Load(FileInfo file) { + _localFile = file; + _isReady = true; + } + void Update() { + if (_isDestroyed) { + Destroy(gameObject); + return; + } + if (_isReady) { + try { + _texHandler = new DownloadHandlerTexture(); + _req = new UnityWebRequest($"file:///{_localFile}") { + downloadHandler = _texHandler, + disposeDownloadHandlerOnDispose = true, + }; + _req.SendWebRequest(); + } + catch (Exception ex) { + Debug.LogException(ex); + } + _isReady = false; + } + if (_req == null || !_req.isDone) return; + if (_texHandler.isDone) { + _tex = _texHandler.texture; + _tex.wrapMode = TextureWrapMode.Clamp; + _sprite = Sprite.Create(_tex, new Rect(0, 0, _tex.width, _tex.height), Vector2.zero, _tex.height, 0, SpriteMeshType.FullRect, Vector4.zero, false); + } + else { + Debug.LogError(_req.error); + _localFile.Delete(); + } + _req.Dispose(); + _texHandler.Dispose(); + _req = null; + _callback?.Invoke(_sprite); + _isDone = true; + } + + + bool _isDestroyed; + public void Destroy() { + _isDestroyed = true; + } + void OnDestroy() { + if (_req != null) { + _req.Abort(); + _req.Dispose(); + _texHandler.Dispose(); + } + if (_sprite) Destroy(_sprite); + if (_tex) Destroy(_tex); + } + } +} diff --git a/Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolderBehaviour.cs.meta b/Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolderBehaviour.cs.meta new file mode 100644 index 0000000..ed37a45 --- /dev/null +++ b/Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolderBehaviour.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c17f6b26e9a6bd74e8b2d071c6951c41 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Cryville.EEW.Unity/Map/MapTileCacheManager.cs b/Assets/Cryville.EEW.Unity/Map/MapTileCacheManager.cs index 7847a03..5fcf8cb 100644 --- a/Assets/Cryville.EEW.Unity/Map/MapTileCacheManager.cs +++ b/Assets/Cryville.EEW.Unity/Map/MapTileCacheManager.cs @@ -1,165 +1,36 @@ using Cryville.EEW.Core.Map; -using System; using System.Collections.Generic; +using System.IO; using UnityEngine; namespace Cryville.EEW.Unity.Map { - sealed class TileZOrderComparer : IComparer, IComparer { - public static readonly TileZOrderComparer Instance = new(); - public int Compare(MapTileIndex a, MapTileIndex b) { - var c = a.Z.CompareTo(b.Z); - if (c != 0) return c; - c = a.Y.CompareTo(b.Y); - if (c != 0) return c; - return a.X.CompareTo(b.X); - } - public int Compare(MapTile a, MapTile b) { - if (a == null) return b == null ? 0 : -1; - if (b == null) return 1; - return Compare(a.Index, b.Index); - } - } - - sealed class MapTileCacheManager : IDisposable { - public int ExtraCachedZoomLevel { get; set; } = 20; - - GameObject m_prefabTile; - public GameObject PrefabTile { - get => m_prefabTile; - set { - m_prefabTile = value; - if (_dummyTask) GameObject.Destroy(_dummyTask.gameObject); - _dummyTask = GameObject.Instantiate(m_prefabTile, Parent, false).GetComponent(); - } - } + sealed class MapTileCacheManager : MapTileCacheManager { + public GameObject PrefabTile { get; set; } + public GameObject PrefabBitmapHolder { get; set; } public Transform Parent { get; set; } public string CacheDir { get; set; } - public event Action Updated; - void OnUpdated(MapTile tile) { - if (tile.IsEmpty) { - lock (ActiveTiles) { - if (_cache.Remove(tile.Index)) { - ActiveTiles.RemoveAt(ActiveTiles.BinarySearch(tile, TileZOrderComparer.Instance)); - } - } - } - Updated?.Invoke(); + protected override MapTileBitmapHolder CreateBitmapHolder(MapTileIndex index) => new(index, GameObject.Instantiate(PrefabBitmapHolder, Parent, false)); + + protected override string GetCacheFilePath(MapTileIndex index) => Path.Combine(CacheDir, $"map/{index.Z}/{index.NX}/{index.NY}"); + + readonly Dictionary, MapTile> _map = new(); + protected override void OnTileCreated(MapTile tile) { + base.OnTileCreated(tile); + var gameObject = GameObject.Instantiate(PrefabTile, Parent, false); + var uTile = gameObject.GetComponent(); + _map.Add(tile, uTile); + uTile.Init(tile.Index); + tile.BitmapHolder.Bind(uTile); } - public void Dispose() { - MonoBehaviour.Destroy(_dummyTask); - - lock (ActiveTiles) { - foreach (var task in ActiveTiles) - GameObject.Destroy(task.gameObject); - ActiveTiles.Clear(); - _cache.Clear(); - } - } - - readonly Dictionary _cache = new(); - public List ActiveTiles { get; } = new(); - - MapTileIndex _a, _b; - - public void MoveTo(MapTileIndex a, MapTileIndex b) { - if (a.Z != b.Z) throw new ArgumentException("Mismatched Z index."); - if (a.X >= b.X || a.Y >= b.Y) throw new ArgumentException("Incorrect relative X/Y index."); - - lock (ActiveTiles) { - UnloadTiles(a, b); - _a = a; _b = b; - LoadTiles(a, b); - } - } - void LoadTiles(MapTileIndex a, MapTileIndex b) { - for (int z = Math.Max(0, a.Z - ExtraCachedZoomLevel); z <= Math.Max(0, a.Z); z++) { - var ia = a.ZoomToLevel(z, Math.Floor); - var ib = b.ZoomToLevel(z, Math.Ceiling); - for (int x = ia.X; x < ib.X; x++) { - for (int y = ia.Y; y < ib.Y; y++) { - var index = new MapTileIndex(x, y, z); - if (_cache.ContainsKey(index)) continue; - var task = GameObject.Instantiate(PrefabTile, Parent, false).GetComponent(); - task.Load(index, CacheDir, OnUpdated); - _cache.Add(index, task); - var i = ~ActiveTiles.BinarySearch(task, TileZOrderComparer.Instance); - ActiveTiles.Insert(i, task); - } - } - } - } - void UnloadTiles(MapTileIndex a, MapTileIndex b) { - if (a.Z != _a.Z) { - for (int z = _a.Z - ExtraCachedZoomLevel; z < a.Z - ExtraCachedZoomLevel; z++) UnloadTilesAtZoomLevel(z); - for (int z = a.Z + 1; z <= _a.Z; z++) UnloadTilesAtZoomLevel(z); - } - if (a.X > _a.X) { - for (int z = Math.Max(0, a.Z); z >= Math.Max(0, a.Z - ExtraCachedZoomLevel); --z) { - var ia0 = _a.ZoomToLevel(z, Math.Floor); - var ib0 = _b.ZoomToLevel(z, Math.Ceiling); - var ia1 = a.ZoomToLevel(z, Math.Floor); - if (ia0.X == ia1.X) break; - UnloadTilesInRegion(ia0.X, ia0.Y, ia1.X, ib0.Y, z); - } - } - if (b.X < _b.X) { - for (int z = Math.Max(0, a.Z); z >= Math.Max(0, a.Z - ExtraCachedZoomLevel); --z) { - var ia0 = _a.ZoomToLevel(z, Math.Floor); - var ib0 = _b.ZoomToLevel(z, Math.Ceiling); - var ib1 = b.ZoomToLevel(z, Math.Ceiling); - if (ib0.X == ib1.X) break; - UnloadTilesInRegion(ib1.X, ia0.Y, ib0.X, ib0.Y, z); - } - } - if (a.Y > _a.Y) { - for (int z = Math.Max(0, a.Z); z >= Math.Max(0, a.Z - ExtraCachedZoomLevel); --z) { - var ia0 = _a.ZoomToLevel(z, Math.Floor); - var ib0 = _b.ZoomToLevel(z, Math.Ceiling); - var ia1 = a.ZoomToLevel(z, Math.Floor); - if (ia0.Y == ia1.Y) break; - UnloadTilesInRegion(ia0.X, ia0.Y, ib0.X, ia1.Y, z); - } - } - if (b.Y < _b.Y) { - for (int z = Math.Max(0, a.Z); z >= Math.Max(0, a.Z - ExtraCachedZoomLevel); --z) { - var ia0 = _a.ZoomToLevel(z, Math.Floor); - var ib0 = _b.ZoomToLevel(z, Math.Ceiling); - var ib1 = b.ZoomToLevel(z, Math.Ceiling); - if (ib0.Y == ib1.Y) break; - UnloadTilesInRegion(ia0.X, ib1.Y, ib0.X, ib0.Y, z); - } - } - } - void UnloadTilesInRegion(int x1, int y1, int x2, int y2, int z) { - for (int x = x1; x < x2; x++) { - for (int y = y1; y < y2; y++) { - var index = new MapTileIndex(x, y, z); - if (!_cache.TryGetValue(index, out var task)) continue; - GameObject.Destroy(task.gameObject); - } - } - } - - MapTile _dummyTask; - void UnloadTilesAtZoomLevel(int z) { - if (z < 0) return; - - _dummyTask.Index = new(int.MinValue, int.MinValue, z); - var i0 = ActiveTiles.BinarySearch(_dummyTask, TileZOrderComparer.Instance); - if (i0 < 0) i0 = ~i0; - - _dummyTask.Index = new(int.MinValue, int.MinValue, z + 1); - var i1 = ActiveTiles.BinarySearch(_dummyTask, TileZOrderComparer.Instance); - if (i1 < 0) i1 = ~i1; - - for (var i = i1 - 1; i >= i0; --i) { - var index = ActiveTiles[i].Index; - if (!_cache.TryGetValue(index, out var task)) continue; - GameObject.Destroy(task.gameObject); + protected override void OnTileDestroyed(MapTile tile) { + base.OnTileDestroyed(tile); + if (_map.TryGetValue(tile, out var uTile)) { + uTile.Destroy(); + _map.Remove(tile); } } } diff --git a/Assets/Main.unity b/Assets/Main.unity index fdafd8d..1c40e0c 100644 --- a/Assets/Main.unity +++ b/Assets/Main.unity @@ -677,6 +677,7 @@ MonoBehaviour: m_layerElement: {fileID: 1602500234} m_layerElementSub: {fileID: 303098821} m_prefabTile: {fileID: 7683017549812261837, guid: e090edd328c6750478f5849a43a9d278, type: 3} + m_prefabBitmapHolder: {fileID: 1455558857588368834, guid: 1a5cf693e0cf6b94390f72f521c151ba, type: 3} --- !u!1 &408286580 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Prefabs/Bitmap Holder.prefab b/Assets/Prefabs/Bitmap Holder.prefab new file mode 100644 index 0000000..fd59996 --- /dev/null +++ b/Assets/Prefabs/Bitmap Holder.prefab @@ -0,0 +1,46 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1455558857588368834 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8746876075196617235} + - component: {fileID: 122704584488816298} + m_Layer: 0 + m_Name: Bitmap Holder + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8746876075196617235 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1455558857588368834} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &122704584488816298 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1455558857588368834} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c17f6b26e9a6bd74e8b2d071c6951c41, type: 3} + m_Name: + m_EditorClassIdentifier: diff --git a/Assets/Prefabs/Bitmap Holder.prefab.meta b/Assets/Prefabs/Bitmap Holder.prefab.meta new file mode 100644 index 0000000..c0897b5 --- /dev/null +++ b/Assets/Prefabs/Bitmap Holder.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1a5cf693e0cf6b94390f72f521c151ba +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: