using UnityEngine; using R3; using System.Collections.Generic; using System; using UnityEngine.UIElements; using UnityEngine.Pool; using KitsuneCafe.System; namespace KitsuneCafe.UI { public class UISceneManager : MonoBehaviour { [SerializeField] private UiOrchestrator orchestrator; [SerializeField] private Transform root; [SerializeField] private PanelSettings panelSettings; [SerializeField] private SerializableDuration despawnTimeout; [SerializeField] private GameObject prefab; private readonly Dictionary activeElements = new(); private readonly Dictionary> pools = new(); private void Awake() { var d = Disposable.CreateBuilder(); Observable.FromEvent, SpawnElementRequest>( h => (sender, e) => h(e), e => orchestrator.SpawnRequested += e, e => orchestrator.SpawnRequested -= e ).Subscribe(request => { SpawnElement(request.Id, request.Element, request.Position); }) .AddTo(ref d); Observable.FromEvent, DespawnElementRequest>( h => (sender, e) => h(e), e => orchestrator.DespawnRequested += e, e => orchestrator.DespawnRequested -= e ) .Select(request => request.Id) .Subscribe(DespawnElement) .AddTo(ref d); d.RegisterTo(destroyCancellationToken); } private IObjectPool GetOrCreatePool(IUiElement element) { if (!pools.TryGetValue(element.VisualTreeAsset, out var pool)) { pool = new ObjectPool( () => CreatePoolObject(pool), GetPoolObject, ReleasePoolObject, DestroyPoolObject, collectionCheck: false, defaultCapacity: 10, maxSize: 100 ); pools.Add(element.VisualTreeAsset, pool); } return pool; } private GameObject CreatePoolObject(IObjectPool pool) { GameObject obj; if (prefab != null) { obj = Instantiate(prefab); } else { obj = new GameObject( "Pooled Object", typeof(UIDocument), typeof(PooledObject), typeof(UiElementInstance) ); } var doc = obj.GetComponent(); doc.panelSettings = panelSettings; var poolObj = obj.GetComponent(); poolObj.objectPool = pool; var instance = obj.GetComponent(); instance.Document = doc; instance.PooledObject = poolObj; obj.transform.SetParent(root); return obj; } private void GetPoolObject(GameObject obj) { obj.SetActive(true); } private void ReleasePoolObject(GameObject obj) { obj.SetActive(false); } private void DestroyPoolObject(GameObject obj) { Destroy(obj); } private GameObject GetUiDocument(IUiElement element) { return GetOrCreatePool(element).Get(); } private void SpawnElement(ElementId id, IUiElement element, Vector3 worldPosition) { var obj = GetUiDocument(element); activeElements[id] = obj; var trans = obj.transform; trans.SetParent(root); trans.SetPositionAndRotation(worldPosition, Quaternion.identity); trans.localScale = Vector3.one; if (obj.TryGetComponent(out UiElementInstance instance)) { instance.Configure(element, panelSettings); } } // FIXME: track which pool each active element belongs to? private void DespawnElement(ElementId id) { if (activeElements.TryGetValue(id, out GameObject obj)) { if (obj.TryGetComponent(out var instance)) { instance.Despawn() .Race(Observable.Timer(despawnTimeout).Do(_ => instance.DespawnNow())) .Do(onCompleted: _ => activeElements.Remove(id)) .Subscribe() .AddTo(obj); } else if (obj.TryGetComponent(out PooledObject poolObj)) { poolObj.Release(); activeElements.Remove(id); } } } } }