diff --git a/My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/GameBootstrap.cs b/My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/GameBootstrap.cs index 32526a6..9ab9e7c 100644 --- a/My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/GameBootstrap.cs +++ b/My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/GameBootstrap.cs @@ -25,8 +25,15 @@ namespace FlowScope.Samples.MainMenuP0 private ContainerType _root; private GameFlow _gameFlow; private ISaveService _saveService; + private UIPreloadService _preloadService; + private SampleResourceBackend _sampleBackend; private PlayerData _playerData; private CancellationTokenSource _lifetime; + private bool _started; + private bool _shutdown; + + public int ReleasedResourceCount => _sampleBackend?.ReleaseCount ?? 0; + public bool IsShutdown => _shutdown; private async void Awake() { @@ -36,35 +43,61 @@ namespace FlowScope.Samples.MainMenuP0 public async Task StartupAsync(CancellationToken cancellationToken) { + if (_started) + { + return; + } + + _started = true; + _shutdown = false; _root = new ContainerType(); _gameFlow = new GameFlow(); - var resources = new SampleResourceService(); - resources.Register( + _sampleBackend = new SampleResourceBackend(); + _sampleBackend.Register( "FlowScope/Samples/MainMenuP0/MainMenuPanel", ResolveMainMenuPanelPrefab().gameObject); + var resources = new ResourceService(_sampleBackend); + _preloadService = new UIPreloadService(resources); + var uiManager = new UIManager(resources, _preloadService); + uiManager.RegisterLayer("hud", ResolveHudCanvas(), 0, PanelStrategy.Destroy); + var screenNavigator = new UIScreenNavigator(uiManager); + var migrations = new SaveMigrationRegistry(); + migrations.Register(new PlayerDataV1ToV2Migration()); _saveService = new SaveService( new FileSaveStorage(), - new JsonSaveSerializer()); + new JsonSaveSerializer(2, migrations)); _playerData = await _saveService.LoadAsync( SaveKey, new PlayerData { Gold = new R3.ReactiveProperty(defaultGold) }, cancellationToken); - var uiManager = new UIManager(resources); - uiManager.RegisterLayer("hud", ResolveHudCanvas(), 0, PanelStrategy.Cache); + await _preloadService.PreloadAsync(cancellationToken); var audioService = gameObject.AddComponent().Initialize(resources); _root.RegisterInstance(_root); _root.RegisterInstance( - new JsonConfigProvider(Array.Empty())); + new JsonConfigProvider(new[] + { + JsonConfigProvider.Mapping( + "main_menu.json", + new InMemoryConfigSource( + new Dictionary + { + { "main_menu.json", "[{\"Id\":1,\"Title\":\"Main Menu P1\"}]" } + }, + "MainMenuP0"), + new JsonConfigParser()) + })); _root.RegisterInstance(resources); _root.RegisterInstance(_saveService); _root.RegisterInstance(audioService); _root.RegisterInstance(uiManager); + _root.RegisterInstance(_preloadService); + _root.RegisterInstance(screenNavigator); _root.RegisterInstance(_playerData); - _root.RegisterFactory(_ => new MainMenuFeature(uiManager, _playerData)); + _root.RegisterFactory(_ => new MainMenuFeature(screenNavigator, _playerData)); await _gameFlow.StartupAsync(_root, cancellationToken); } @@ -80,6 +113,9 @@ namespace FlowScope.Samples.MainMenuP0 { await _gameFlow.ShutdownAsync(cancellationToken); } + + _preloadService?.ReleaseAll(); + _shutdown = true; } private async void OnApplicationQuit() @@ -137,16 +173,19 @@ namespace FlowScope.Samples.MainMenuP0 return mainMenuPanelPrefab; } - private sealed class SampleResourceService : IResourceService + private sealed class SampleResourceBackend : IResourceBackend { private readonly Dictionary _prefabs = new(); + public string Name => "MainMenuP0Sample"; + public int ReleaseCount { get; private set; } + public void Register(string key, GameObject prefab) { _prefabs[key] = prefab; } - public Task> LoadAsync( + public Task LoadAsync( string key, CancellationToken cancellationToken) where T : class @@ -155,36 +194,32 @@ namespace FlowScope.Samples.MainMenuP0 if (typeof(T) == typeof(GameObject) && _prefabs.TryGetValue(key, out var prefab)) { - return Task.FromResult>( - new SampleResourceHandle(key, prefab as T)); + return Task.FromResult(prefab as T); } throw new InvalidOperationException($"Sample resource is not registered: {key}"); } - public IResourceGroup CreateGroup() + public void Release(string key, T asset) + where T : class { - return new ResourceGroup(); + ReleaseCount++; } } - private sealed class SampleResourceHandle : IResourceHandle where T : class + private sealed class SampleMenuConfig : IConfigRow { - public SampleResourceHandle(string key, T asset) - { - Key = key; - Asset = asset; - } + public int Id { get; set; } + public string Title { get; set; } + } - public string Key { get; } - public T Asset { get; private set; } - public bool IsDisposed { get; private set; } + private sealed class PlayerDataV1ToV2Migration : ISaveMigration + { + public string Key => typeof(PlayerData).FullName; + public int FromVersion => 1; + public int ToVersion => 2; - public void Dispose() - { - IsDisposed = true; - Asset = null; - } + public string Migrate(string json) => json; } } } diff --git a/My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuFeature.cs b/My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuFeature.cs index 207f714..61248ad 100644 --- a/My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuFeature.cs +++ b/My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuFeature.cs @@ -6,13 +6,13 @@ namespace FlowScope.Samples.MainMenuP0 { public sealed class MainMenuFeature : FeatureBase { - private readonly UIManager _uiManager; + private readonly IUIScreenNavigator _screenNavigator; private readonly PlayerData _playerData; private MainMenuViewModel _viewModel; - public MainMenuFeature(UIManager uiManager, PlayerData playerData) + public MainMenuFeature(IUIScreenNavigator screenNavigator, PlayerData playerData) { - _uiManager = uiManager; + _screenNavigator = screenNavigator; _playerData = playerData; } @@ -25,14 +25,14 @@ namespace FlowScope.Samples.MainMenuP0 public override async Task EnterAsync(FeatureContext context) { - await _uiManager.OpenAsync( + await _screenNavigator.PushAsync( _viewModel, context.CancellationToken); } public override Task ExitAsync(FeatureContext context) { - _uiManager.Close(); + _screenNavigator.Clear(); return base.ExitAsync(context); } diff --git a/My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuPanel.cs b/My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuPanel.cs index 629d3a5..3cd3a5f 100644 --- a/My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuPanel.cs +++ b/My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuPanel.cs @@ -4,7 +4,7 @@ using UnityEngine.UI; namespace FlowScope.Samples.MainMenuP0 { - [UIPanel("hud", "FlowScope/Samples/MainMenuP0/MainMenuPanel", PanelStrategy.Cache)] + [UIPanel("hud", "FlowScope/Samples/MainMenuP0/MainMenuPanel")] public sealed class MainMenuPanel : UIPanelBase { [SerializeField] private Text goldText; diff --git a/My project/Assets/FlowScope/Tests/PlayMode/Samples/MainMenuP0Tests.cs b/My project/Assets/FlowScope/Tests/PlayMode/Samples/MainMenuP0Tests.cs index cc89af8..1bb9539 100644 --- a/My project/Assets/FlowScope/Tests/PlayMode/Samples/MainMenuP0Tests.cs +++ b/My project/Assets/FlowScope/Tests/PlayMode/Samples/MainMenuP0Tests.cs @@ -27,13 +27,11 @@ namespace FlowScope.Tests.PlayMode.Samples } [UnityTest] - public IEnumerator Bootstrap_StartClickShutdownAndRestart_RestoresSavedGold() + public IEnumerator Bootstrap_StartsAndOpensVisibleMainMenuPanel() { DeleteSampleSave(); - var root = new GameObject("bootstrap-test"); - root.SetActive(false); + var root = CreateInactiveBootstrap("bootstrap-visible-test", out var bootstrap); - var bootstrap = root.AddComponent(); yield return AwaitTask(bootstrap.StartupAsync(CancellationToken.None)); var panel = Object.FindObjectOfType(true); @@ -42,17 +40,49 @@ namespace FlowScope.Tests.PlayMode.Samples var button = panel.GetComponentInChildren