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