using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace Superlazy.UI { public class SLUIList : SLUIObjectOnOff { public bool useSort = false; public bool descending = false; public string sortKey; public string bindMaxCount = ""; public string bindMinCount = ""; public SLValueComparison comparison; public float addDelay = 0.0f; [NonSerialized] public SLUIEntity listElement; private readonly Queue unused = new Queue(); private readonly List removeKeys = new List(); private Dictionary sortKeys; protected Dictionary sortValues; private int emptyObjectHandle = 0; protected Dictionary bindObjects = new Dictionary(); protected override bool ActiveSelf => isActiveAndEnabled; protected override void Validate() { listElement = GetComponentInChildren(true); } protected override void Init() { listElement.inherit = true; listElement.bindParent = this; listElement.useOnOff = false; listElement.gameObject.SetActive(false); if (useSort) { sortKeys = new Dictionary(); sortValues = new Dictionary(); } } protected override void Enable() { if (addDelay > 0) { StartCoroutine(UpdateList()); } else { SLGame.AddNotify(base.BindPath, OnChange); } if (bindParent == null || bindParent.Active == false) return; } protected override void Disable() { foreach (var obj in bindObjects.Values) { if (useSort) { var key = sortKeys[obj]; SLGame.RemoveNotify(key, SortList); sortKeys.Remove(obj); sortValues.Remove(obj); } obj.gameObject.SetActive(false); obj.name = "Unused"; unused.Enqueue(obj); } bindObjects.Clear(); if (unused.Count > 20) { foreach (var unusedItem in unused) { Destroy(unusedItem.gameObject); } unused.Clear(); } emptyObjectHandle = 0; if (addDelay <= 0) { SLGame.RemoveNotify(base.BindPath, OnChange); } } private void DisableItem(SLUIEntity obj) { if (useSort) { var key = sortKeys[obj]; SLGame.RemoveNotify(key, SortList); sortKeys.Remove(obj); } if (obj.OnRemoveByParent((_) => { obj.gameObject.SetActive(false); unused.Enqueue(obj); if (useSort) { sortValues.Remove(obj); } })) return; obj.gameObject.SetActive(false); unused.Enqueue(obj); if (useSort) { sortValues.Remove(obj); } } private void EnableItem(string key, SLUIEntity child) { child.SetupByParent(key); child.gameObject.SetActive(true); if (useSort) { var sort = base.BindPath.CombinePath(key).CombinePath(sortKey); sortKeys.Add(child, sort); sortValues[child] = 0; // sortList에서 업데이트 SLGame.AddNotify(sort, SortList); } } protected void RemoveEntity(string key) { DisableItem(bindObjects[key]); bindObjects.Remove(key); } protected void AddEntity(string key) { SLUIEntity child; if (unused != null && unused.Count != 0) { child = unused.Dequeue(); child.transform.SetAsLastSibling(); } else { child = Instantiate(listElement.gameObject, transform, false).GetComponent(); } EnableItem(key, child); bindObjects[key] = child; } private void OnChange() { if (bindParent == null || bindParent.Active == false) return; // base를 강제하는 이유는 이후 상속된 이벤트 등에서 다형성으로 적용되면 안되기 때문 var session = SLGame.SessionGet(base.BindPath); var needSort = false; removeKeys.Clear(); foreach (var key in bindObjects.Keys) { if (CheckRemoveKey(session, key)) { removeKeys.Add(key); needSort = true; } } foreach (var key in removeKeys) { OnRemoveKey(session, key); } foreach (var item in session) { if (CheckAddKey(session, item.ID)) { OnAddKey(session, item.ID); needSort = true; } } if (bindMinCount != string.Empty) { int minCount = SLGame.SessionGet(bindMinCount); if (bindObjects.Count < minCount) { var emptyCount = minCount - bindObjects.Count; for (var i = 0; i < emptyCount; ++i) { OnAddKey(session, $"__Empty{emptyObjectHandle}"); emptyObjectHandle += 1; } } } if (useSort && sortKey != null && needSort) { SortList(); } } private IEnumerator UpdateList() { do { if (bindParent == null || bindParent.Active == false) yield break; // 삭제는 전체를 진행 // base를 강제하는 이유는 이후 상속된 이벤트 등에서 다형성으로 적용되면 안되기 때문 var session = SLGame.SessionGet(base.BindPath); var added = false; removeKeys.Clear(); foreach (var key in bindObjects.Keys) { if (CheckRemoveKey(session, key)) { removeKeys.Add(key); } } foreach (var key in removeKeys) { OnRemoveKey(session, key); } var items = session.Where(s => CheckAddKey(session, s.ID)); if (items.Count() > 0) { SLEntity item; if (useSort && sortKey != null) { if (descending) { item = items.MaxBy(s => s[sortKey]); } else { item = items.MinBy(s => s[sortKey]); } } else { item = items.First(); } OnAddKey(session, item.ID); added = true; } if (bindMinCount != string.Empty) { int minCount = SLGame.SessionGet(bindMinCount); if (bindObjects.Count < minCount) { var emptyCount = minCount - bindObjects.Count; for (var i = 0; i < emptyCount; ++i) { OnAddKey(session, $"__Empty{emptyObjectHandle}"); emptyObjectHandle += 1; } } } if (added) { yield return new WaitForSeconds(addDelay); } else { yield return null; } } while (true); } private void SortList() { if (ActiveSelf == false) return; foreach (var obj in sortKeys) { var newData = SLGame.SessionGet(obj.Value); if (newData == false) continue; // 소트키가 삭제됨 sortValues[obj.Key] = newData; } IOrderedEnumerable> orderedList; if (descending) { orderedList = sortValues.OrderByDescending((u) => u.Value); } else { orderedList = sortValues.OrderBy((u) => u.Value); } var i = 0; foreach (var item in orderedList) { item.Key.transform.SetSiblingIndex(i); ++i; } } protected virtual bool CheckAddKey(SLEntity session, string key) { // TODO: Min,Max가 둘다 있는 상황에서 빈오브젝트가 있으면 추가가 안되거나 할 수 있음. if (bindMaxCount != string.Empty) { int maxCount = SLGame.SessionGet(bindMaxCount); if (bindObjects.Count >= maxCount) return false; } if (comparison.CheckPath(BindPath.CombinePath(key)) == false) return false; if (bindObjects.ContainsKey(key) == false) { return true; } return false; } protected virtual bool CheckRemoveKey(SLEntity session, string key) { if (session.HasChild(key) == false) { return true; } if (comparison.CheckPath(BindPath.CombinePath(key)) == false) return true; return false; } protected virtual void OnRemoveKey(SLEntity session, string key) { RemoveEntity(key); } protected virtual void OnAddKey(SLEntity session, string key) { AddEntity(key); } } }