From a2496d219e72faa9608bd1673547b0e1bc9be9af Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Mon, 30 Mar 2026 20:43:28 +0900 Subject: [PATCH] =?UTF-8?q?perf(game):=20setState=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EB=A6=AC=EB=B9=8C=EB=93=9C=20=E2=86=92=20ValueNotifier=20?= =?UTF-8?q?=EA=B5=AD=EC=86=8C=20=EB=A6=AC=EB=B9=8C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GameState를 ValueNotifier로 감싸 50ms마다 전체 트리 리빌드 방지 - build()에서 ValueListenableBuilder 사용 - _specialAnimation 등 UI 전용 상태만 setState 유지 - SchedulerBinding 의존성 제거 --- lib/src/features/game/game_play_screen.dart | 49 ++++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/lib/src/features/game/game_play_screen.dart b/lib/src/features/game/game_play_screen.dart index e260813..264e7b4 100644 --- a/lib/src/features/game/game_play_screen.dart +++ b/lib/src/features/game/game_play_screen.dart @@ -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 with WidgetsBindingObserver { AsciiAnimationType? _specialAnimation; + // 게임 상태를 국소적으로 갱신하기 위한 ValueNotifier + // (매 틱마다 전체 위젯 트리 리빌드 방지) + final _stateNotifier = ValueNotifier(null); + // Phase 8: 알림 서비스 (Notification Service) late final NotificationService _notificationService; @@ -207,8 +210,9 @@ class _GamePlayScreenState extends State 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 @override void dispose() { + _stateNotifier.dispose(); _notificationService.dispose(); WidgetsBinding.instance.removeObserver(this); widget.controller.removeListener(_onControllerChanged); @@ -355,17 +360,9 @@ class _GamePlayScreenState extends State _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,18 +528,26 @@ class _GamePlayScreenState extends State @override Widget build(BuildContext context) { - final state = widget.controller.state; - if (state == null) { - return const Scaffold(body: Center(child: CircularProgressIndicator())); - } + // ValueListenableBuilder로 게임 상태 변경만 국소 리빌드 + // (_specialAnimation 등 UI 전용 상태는 여전히 setState로 관리) + return ValueListenableBuilder( + 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)) { - return _buildMobileLayout(context, state, localeKey); - } + if (_shouldUseCarouselLayout(context)) { + return _buildMobileLayout(context, state, localeKey); + } - return _buildDesktopLayout(context, state, localeKey); + return _buildDesktopLayout(context, state, localeKey); + }, + ); } /// 모바일 캐로셀 레이아웃