diff --git a/client/Packages/com.beamable/CHANGELOG.md b/client/Packages/com.beamable/CHANGELOG.md index dba40a5952..1e6ce8335b 100644 --- a/client/Packages/com.beamable/CHANGELOG.md +++ b/client/Packages/com.beamable/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- Fixed the Content window Snapshot tab hanging at "Loading snapshots" when snapshots in different realm folders share a name (e.g. `LastPublished-global.json` auto snapshots from publishing to multiple realms) + ## [5.1.1] - 2026-06-29 ### Added diff --git a/client/Packages/com.beamable/Editor/UI/ContentWindow/ContentWindow_SnapshotManager.cs b/client/Packages/com.beamable/Editor/UI/ContentWindow/ContentWindow_SnapshotManager.cs index 9d5e70bfb6..9b9207f242 100644 --- a/client/Packages/com.beamable/Editor/UI/ContentWindow/ContentWindow_SnapshotManager.cs +++ b/client/Packages/com.beamable/Editor/UI/ContentWindow/ContentWindow_SnapshotManager.cs @@ -598,13 +598,27 @@ private async Task CacheSnapshots() _selectedSnapshotsPaths.Clear(); _snapshotSearchData = new SearchData(); _gatheringSnapshots = true; - var snapshotListResult = await _contentService.GetContentSnapshots(); - _sharedSnapshots = snapshotListResult.SharedSnapshots.ToDictionary(item => item.Name, item => item); - _localSnapshots = snapshotListResult.LocalSnapshots.ToDictionary(item => item.Name, item => item); - _allSnapshots.Clear(); - _localSnapshots.Values.ToList().ForEach(snapshot => _allSnapshots.Add(snapshot.Path, snapshot)); - _sharedSnapshots.Values.ToList().ForEach(snapshot => _allSnapshots.Add(snapshot.Path, snapshot)); - _gatheringSnapshots = false; + try + { + var snapshotListResult = await _contentService.GetContentSnapshots(); + _sharedSnapshots = BuildSnapshotLookup(snapshotListResult.SharedSnapshots); + _localSnapshots = BuildSnapshotLookup(snapshotListResult.LocalSnapshots); + _allSnapshots.Clear(); + _localSnapshots.Values.ToList().ForEach(snapshot => _allSnapshots.Add(snapshot.Path, snapshot)); + _sharedSnapshots.Values.ToList().ForEach(snapshot => _allSnapshots.Add(snapshot.Path, snapshot)); + } + finally + { + // Always clear the flag, otherwise the window is stuck on "Loading snapshots". + _gatheringSnapshots = false; + } + } + + // Snapshots in different realm folders can share a file name (auto snapshots are + // always named "LastPublished-"), so the lookup must key by path. + public static Dictionary BuildSnapshotLookup(IEnumerable snapshots) + { + return snapshots.ToDictionary(item => item.Path, item => item); } } diff --git a/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs b/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs new file mode 100644 index 0000000000..28928e13a1 --- /dev/null +++ b/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs @@ -0,0 +1,54 @@ +using Beamable.Editor.BeamCli.Commands; +using Beamable.Editor.UI.ContentWindow; +using NUnit.Framework; +using System.Linq; + +namespace Beamable.Editor.Tests +{ + public class ContentWindowSnapshotTests + { + private static BeamManifestSnapshotItem Snapshot(string name, string path) + { + return new BeamManifestSnapshotItem { Name = name, Path = path }; + } + + [Test] + public void BuildSnapshotLookup_DuplicateNamesAcrossRealms_KeepsAllSnapshots() + { + // Auto snapshots share the name "LastPublished-" across realm folders. + var snapshots = new[] + { + Snapshot("LastPublished-global", ".beamable/content-snapshots/DE_111/LastPublished-global.json"), + Snapshot("LastPublished-global", ".beamable/content-snapshots/DE_222/LastPublished-global.json"), + }; + + var lookup = ContentWindow.BuildSnapshotLookup(snapshots); + + Assert.AreEqual(2, lookup.Count); + CollectionAssert.AreEquivalent(snapshots.Select(item => item.Path), lookup.Keys); + } + + [Test] + public void BuildSnapshotLookup_KeysByPath() + { + var snapshots = new[] + { + Snapshot("my-snapshot", ".beamable/content-snapshots/DE_111/my-snapshot.json"), + Snapshot("other-snapshot", ".beamable/temp/content-snapshots/DE_111/other-snapshot.json"), + }; + + var lookup = ContentWindow.BuildSnapshotLookup(snapshots); + + Assert.AreSame(snapshots[0], lookup[snapshots[0].Path]); + Assert.AreSame(snapshots[1], lookup[snapshots[1].Path]); + } + + [Test] + public void BuildSnapshotLookup_EmptyInput_ReturnsEmptyLookup() + { + var lookup = ContentWindow.BuildSnapshotLookup(System.Array.Empty()); + + Assert.AreEqual(0, lookup.Count); + } + } +} diff --git a/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs.meta b/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs.meta new file mode 100644 index 0000000000..208d5f1efa --- /dev/null +++ b/client/Packages/com.beamable/Tests/Editor/ContentWindowSnapshotTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6826f76d647f44c6a110dcdee7992df0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: