7.7 KiB
7.7 KiB
BlockDetailModal 통합 구현 작업지시서 (최종안)
1. 목표
BlockDetailModal의 3개 뷰(3D 모델, 계층 리스트, 간트 차트)를 통합하고, 모든 뷰가 상호 동기화되도록 완성한다.
2. 핵심 데이터 계약 및 전략
- 데이터 모델:
SHI.modal.ModelDetailListItemData를 핵심 데이터 단위로 사용한다. - 고유 식별자: 상속받은
UVC.UI.List.Tree.TreeListItemData의public Guid Id를 모든 동기화(선택, 포커스, 가시성)의 유일한 키로 사용한다.Name기반의 기존 로직은 모두Id기반으로 변경한다. - 데이터 정의:
ScheduleSegment:Guid ItemId,DateTime Start,DateTime End,float Progress(0-1),string TypeGanttChartData:List<ScheduleSegment> Segments
- 라이브러리: 3D 모델 로딩은 설치된
glTFast (6.14.1)를 사용한다.
3. 샘플 데이터 및 모델 파일 생성
- 샘플 glTF 모델:
Assets/StreamingAssets/block.glb파일을 사용(또는 배치). 테스트 시LoadData호출에Path.Combine(Application.streamingAssetsPath, "block.glb")전달. - 샘플 간트 데이터:
Assets/StreamingAssets/sample_gantt_data.json파일을 생성한다. (폴더가 없으면 생성) - JSON 내용:
GanttChartData구조에 맞는 샘플 JSON 데이터를 작성한다.ItemId는ModelDetailView에서 생성될ModelDetailListItemData의Id와 일치해야 하므로, 초기 테스트를 위해 몇 개의 고정된Guid를 사용한다.{ "Segments": [ { "ItemId": "f81d4fae-7dec-11d0-a765-00a0c91e6bf6", "Start": "2024-07-01T00:00:00Z", "End": "2024-07-10T00:00:00Z", "Progress": 0.5, "Type": "Task" }, { "ItemId": "b2a8f0e0-0e3a-4b1a-9b0a-0e1b9b0f0e1b", "Start": "2024-07-05T00:00:00Z", "End": "2024-07-15T00:00:00Z", "Progress": 0.2, "Type": "Milestone" } ] }
4. BlockDetailModal.cs 수정
- 필드 추가:
private CancellationTokenSource? _cts;private bool _isSelectionSuppressed;
- 메서드 추가/수정:
public async UniTask LoadData(string gltfPath, GanttChartData gantt, CancellationToken externalCt = default):- 기존
_cts가 있다면 취소하고 Dispose 한다._cts = CancellationTokenSource.CreateLinkedTokenSource(externalCt);로 새로 생성한다. - (구현) 로딩 UI를 표시한다.
modelView.LoadModelAsync(gltfPath, _cts.Token)을 호출하여IEnumerable<ModelDetailListItemData>를 받는다.listView.Populate(items)로 리스트를 채운다.chartView.LoadData(gantt)로 차트 데이터를 전달한다.- (구현) 로딩 UI를 숨긴다.
- 기존
private void OnDisable():_cts?.Cancel(); _cts?.Dispose();를 호출하여 비활성화 시 모든 비동기 작업을 취소한다.modelView와chartView의 리소스 정리(Dispose) 메서드를 호출한다.
- 이벤트 핸들러 수정 (
Start메서드 내):- 모든 이벤트 연결을
Id기반으로 변경하고, 재진입 방지 로직을 적용한다.// 예시: listView.OnItemSelected += ... listView.OnItemSelected += data => { if (_isSelectionSuppressed || data is not ModelDetailListItemData item) return; HandleSelection(item.Id, "ListView"); }; // modelView, chartView도 동일하게 수정 private void HandleSelection(Guid itemId, string source):_isSelectionSuppressed = true; if (source != "ListView") listView.SelectByItemId(itemId); if (source != "ModelView") modelView.FocusItemById(itemId); if (source != "ChartView") chartView.SelectByItemId(itemId); // UniTask.Yield() 또는 DelayFrame(1)을 사용하여 한 프레임 뒤 false로 설정, 즉각적인 재진입 방지 UniTask.Yield(PlayerLoopTiming.PostLateUpdate, this.GetCancellationTokenOnDestroy()).ContinueWith(() => _isSelectionSuppressed = false);
- 모든 이벤트 연결을
5. ModelDetailView.cs 구현
- 메서드 추가:
public async UniTask<IEnumerable<ModelDetailListItemData>> LoadModelAsync(string path, CancellationToken ct):glTFast를 사용하여 모델을 비동기 로드. 각GameObject에 대해ModelDetailListItemData를 생성하고Id를 부여하여Dictionary<Guid, GameObject>에 매핑 후, 데이터 목록을 반환한다.public void FocusItemById(Guid id):id에 해당하는GameObject의Bounds를 계산하여 카메라를 부드럽게 이동/줌한다. 객체를 하이라이트한다.public void SetVisibility(Guid id, bool isVisible):id에 해당하는GameObject를 활성화/비활성화한다.public void Dispose(): 로드된GameObject, 생성된 머티리얼 인스턴스 등 모든 리소스를 파괴한다.
- 구현 내용:
- Raycast를 통한 객체 선택 로직을 구현하고, 선택 시
OnItemSelected이벤트를Id가 포함된ModelDetailListItemData와 함께 발생시킨다. - 객체 하이라이트를 위한 머티리얼 인스턴싱 및 캐시 관리 로직을 구현한다.
- Raycast를 통한 객체 선택 로직을 구현하고, 선택 시
6. ModelDetailListView.cs 수정/구현
- 메서드 추가:
public void Populate(IEnumerable<ModelDetailListItemData> items): 기존 항목을 모두 지우고 새 데이터로 트리를 구성한다.public void SelectByItemId(Guid id):id에 해당하는 아이템을 찾아 선택 상태로 만든다.
- 가시성 처리:
Populate시, 각ModelDetailListItemData의OnClickVisibleAction에(data, isVisible) => { if(data is ModelDetailListItemData item) OnVisibilityChanged?.Invoke(item.Id, isVisible); }와 같은 람다를 할당한다.public event Action<Guid, bool> OnVisibilityChanged;이벤트를 추가하고,BlockDetailModal에서 이 이벤트를 구독하여modelView.SetVisibility를 호출하도록 연결한다.
7. ModelDetailChartView.cs 구현
- 메서드 추가:
public void LoadData(GanttChartData data): 차트 데이터를 받아 렌더링한다.public void SelectByItemId(Guid id):id에 해당하는 행을 찾아 하이라이트하고, 해당 위치로 스크롤한다.public event Action<Guid> OnRowClicked;(string에서Guid로 변경)public void Dispose(): UI Toolkit으로 생성된 동적 요소들을 정리한다.
- 구현 내용:
- UI Toolkit의
ListView를 사용하여 행 가상화를 구현한다. - 행 클릭 시
OnRowClicked이벤트를ItemId와 함께 발생시킨다.
- UI Toolkit의
8. 실행 우선순위
- 데이터 계약:
ScheduleSegment/GanttChartData클래스 생성 및 샘플 JSON/GLB 파일 작성. - ID 기반 리팩토링:
BlockDetailModal및 각 뷰의 이벤트와 메서드 시그니처를string name에서Guid Id로 모두 변경. - 뷰 구현:
ModelDetailView의 모델 로딩,ModelDetailListView의Populate,ModelDetailChartView의LoadData를 순서대로 구현. - 동기화 로직:
BlockDetailModal에 재진입 방지 로직(_isSelectionSuppressed)을 포함한HandleSelection구현. - 세부 기능: 하이라이트, 카메라 포커싱, 가시성 토글, 차트 스크롤링 순으로 구현.
- 생명주기:
OnDisable과 각 뷰의Dispose메서드에 리소스 정리 로직을 철저히 구현. - 테스트: 중복 이름 아이템 선택, 빠른 모달 닫기 등 엣지 케이스를 포함하여 테스트.