using System.Collections.Generic; using System.ComponentModel; using System.Runtime.CompilerServices; using UnityEngine; namespace DDD { public abstract class SimpleViewModel : MonoBehaviour, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public virtual void Initialize() { } public virtual void Cleanup() { } /// /// PropertyChanged 이벤트 발생 /// /// 변경된 속성 이름 (자동으로 설정됨) protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { if (string.IsNullOrEmpty(propertyName)) return; if (_updateDepth > 0) { // 배치 업데이트 중: 나중에 일괄 발행 _pendingNotifications.Add(propertyName); return; } PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// /// 필드 값 변경 및 PropertyChanged 이벤트 발생 /// /// 필드 타입 /// 변경할 필드 참조 /// 새로운 값 /// 속성 이름 (자동으로 설정됨) /// 값이 실제로 변경되었는지 여부 protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(propertyName); return true; } private int _updateDepth; private readonly HashSet _pendingNotifications = new(); protected void BeginUpdate() { _updateDepth++; } protected void EndUpdate() { if (_updateDepth == 0) { Debug.LogWarning("EndUpdate called without matching BeginUpdate."); return; } _updateDepth--; // 아직 상위 배치가 진행 중이면 플러시하지 않음 if (_updateDepth > 0) return; if (_pendingNotifications.Count > 0) { // 복사 후 클리어(핸들러 내부에서 BeginUpdate가 호출되어도 안전) var toNotify = new List(_pendingNotifications); _pendingNotifications.Clear(); foreach (var prop in toNotify) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop)); } } } } }