using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace Jovian.InGameLogging.UI { public class GameLogView : MonoBehaviour { [SerializeField] ScrollRect scrollRect; [SerializeField] RectTransform content; [SerializeField] LogEntryView entryPrefab; [SerializeField] int poolSize = 20; IGameLogStore store; LogChannel? channelFilter; bool autoScroll = true; bool scrollingToBottom; Coroutine scrollCoroutine; readonly List activeEntries = new(); readonly Stack pool = new(); public void Initialize(IGameLogStore store, LogChannel? channelFilter = null) { this.store = store; this.channelFilter = channelFilter; WarmPool(); store.OnEntryAdded += HandleEntryAdded; store.OnCleared += HandleCleared; scrollRect.onValueChanged.AddListener(HandleScrollChanged); RebuildFromStore(); } void OnDestroy() { if(store != null) { store.OnEntryAdded -= HandleEntryAdded; store.OnCleared -= HandleCleared; } if(scrollRect != null) { scrollRect.onValueChanged.RemoveListener(HandleScrollChanged); } } void WarmPool() { for(int i = 0; i < poolSize; i++) { var entry = Instantiate(entryPrefab, content); entry.gameObject.SetActive(false); pool.Push(entry); } } LogEntryView GetFromPool() { LogEntryView entry; if(pool.Count > 0) { entry = pool.Pop(); } else { entry = activeEntries[0]; activeEntries.RemoveAt(0); } entry.gameObject.SetActive(true); entry.transform.SetAsLastSibling(); return entry; } void ReturnToPool(LogEntryView entry) { entry.ClearEntry(); entry.gameObject.SetActive(false); pool.Push(entry); } void HandleEntryAdded(LogEntry entry) { if(channelFilter.HasValue && entry.channel != channelFilter.Value) { return; } var view = GetFromPool(); view.SetEntry(in entry); activeEntries.Add(view); if(autoScroll) { RequestScrollToBottom(); } } void RequestScrollToBottom() { if(scrollCoroutine != null) { StopCoroutine(scrollCoroutine); } scrollCoroutine = StartCoroutine(ScrollToBottomRoutine()); } IEnumerator ScrollToBottomRoutine() { scrollingToBottom = true; yield return null; LayoutRebuilder.ForceRebuildLayoutImmediate(content); scrollRect.verticalNormalizedPosition = 0f; yield return null; scrollingToBottom = false; scrollCoroutine = null; } void HandleCleared() { for(int i = activeEntries.Count - 1; i >= 0; i--) { ReturnToPool(activeEntries[i]); } activeEntries.Clear(); if(store.Count > 0) { RebuildFromStore(); } } void RebuildFromStore() { var entries = store.GetEntries(); for(int i = 0; i < entries.Length; i++) { if(channelFilter.HasValue && entries[i].channel != channelFilter.Value) { continue; } var view = GetFromPool(); view.SetEntry(in entries[i]); activeEntries.Add(view); } if(autoScroll) { RequestScrollToBottom(); } } void HandleScrollChanged(Vector2 position) { if(scrollingToBottom) { return; } autoScroll = position.y <= 0.01f; } } }