perf(game): setState 전체 리빌드 → ValueNotifier 국소 리빌드
- GameState를 ValueNotifier로 감싸 50ms마다 전체 트리 리빌드 방지 - build()에서 ValueListenableBuilder 사용 - _specialAnimation 등 UI 전용 상태만 setState 유지 - SchedulerBinding 의존성 제거
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/foundation.dart'
|
||||
show kIsWeb, defaultTargetPlatform, TargetPlatform;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart' show SchedulerBinding, SchedulerPhase;
|
||||
|
||||
import 'package:asciineverdie/data/game_text_l10n.dart' as game_l10n;
|
||||
import 'package:asciineverdie/src/core/infrastructure/iap_service.dart';
|
||||
@@ -60,6 +59,10 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
with WidgetsBindingObserver {
|
||||
AsciiAnimationType? _specialAnimation;
|
||||
|
||||
// 게임 상태를 국소적으로 갱신하기 위한 ValueNotifier
|
||||
// (매 틱마다 전체 위젯 트리 리빌드 방지)
|
||||
final _stateNotifier = ValueNotifier<GameState?>(null);
|
||||
|
||||
// Phase 8: 알림 서비스 (Notification Service)
|
||||
late final NotificationService _notificationService;
|
||||
|
||||
@@ -207,8 +210,9 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
widget.controller.addListener(_onControllerChanged);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
// 초기 상태 설정
|
||||
// 초기 상태 설정 (ValueNotifier 포함)
|
||||
final state = widget.controller.state;
|
||||
_stateNotifier.value = state;
|
||||
if (state != null) {
|
||||
_lastLevel = state.traits.level;
|
||||
_lastQuestCount = state.progress.questCount;
|
||||
@@ -242,6 +246,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_stateNotifier.dispose();
|
||||
_notificationService.dispose();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
widget.controller.removeListener(_onControllerChanged);
|
||||
@@ -355,17 +360,9 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
_checkSpecialEvents(state);
|
||||
}
|
||||
|
||||
// WASM 안정성: 프레임 빌드 중이면 다음 프레임까지 대기
|
||||
if (SchedulerBinding.instance.schedulerPhase ==
|
||||
SchedulerPhase.persistentCallbacks) {
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setState(() {});
|
||||
}
|
||||
// ValueNotifier로 게임 상태만 국소 갱신 (전체 setState 제거)
|
||||
// _checkSpecialEvents 내부의 setState(_specialAnimation)는 유지
|
||||
_stateNotifier.value = state;
|
||||
}
|
||||
|
||||
/// 캐로셀 레이아웃 사용 여부 판단
|
||||
@@ -531,9 +528,15 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final state = widget.controller.state;
|
||||
// ValueListenableBuilder로 게임 상태 변경만 국소 리빌드
|
||||
// (_specialAnimation 등 UI 전용 상태는 여전히 setState로 관리)
|
||||
return ValueListenableBuilder<GameState?>(
|
||||
valueListenable: _stateNotifier,
|
||||
builder: (context, state, _) {
|
||||
if (state == null) {
|
||||
return const Scaffold(body: Center(child: CircularProgressIndicator()));
|
||||
return const Scaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
final localeKey = ValueKey(game_l10n.currentGameLocale);
|
||||
@@ -543,6 +546,8 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
||||
}
|
||||
|
||||
return _buildDesktopLayout(context, state, localeKey);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 모바일 캐로셀 레이아웃
|
||||
|
||||
Reference in New Issue
Block a user