Fixes "[SceneServices] Bartender: could not resolve dialogueRunner". Resolving a cross-scene dependency in Awake is a race by construction. The dialogue runner lives in Systems; an NPC lives in the level. Open the level on its own, or let the scenes load in the other order, and the NPC wakes with no Systems scene in sight. Worse, the failed lookup was cached into the serialized field, so it stayed broken for the rest of the session rather than recovering once Systems appeared. NPCStandIn and DialogueInteractable now resolve lazily through a property and cache only a successful result, so any load order works. Awake still does a silent best-effort pass, which keeps the common case free. SceneServices splits in two accordingly. TryResolve is silent and is what speculative callers use; Resolve logs an error and belongs only where the dependency is genuinely required. Both now include inactive objects — a system parked inactive is still the object we mean, and excluding it reported "not found" for something sitting in the hierarchy. The error text now says where the runner lives and how to open the scenes, instead of asking a question. BootstrapTests only ever covered the happy order, which is why it stayed green while this was broken. SceneOrderTests loads the level BEFORE Systems and holds that NPCs still reach the runner. It also asserts it is reproducing the race — it counts the NPCs whose Awake-time resolution failed and fails if that is zero, so the test cannot quietly start passing for the wrong reason. It currently reports 2 of 2, which is the bug this commit fixes. EditMode 42/42, PlayMode 7/7, YarnCheck 9 files / 35 nodes. Co-Authored-By: Claude Opus 5 <[email protected]>
70 lines
2.9 KiB
C#
70 lines
2.9 KiB
C#
using System.Collections;
|
|
using System.Linq;
|
|
using NUnit.Framework;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.TestTools;
|
|
|
|
namespace NightclubArcadia.PlayMode.Tests
|
|
{
|
|
/// <summary>
|
|
/// The level must survive being loaded BEFORE the systems it depends on.
|
|
///
|
|
/// BootstrapTests only covers the happy order — Bootstrap loads Systems, then the
|
|
/// level, so by the time an NPC awakes the dialogue runner is already there. The
|
|
/// Editor does not guarantee that. Open the level on its own, or open scenes in a
|
|
/// different order, press Play, and NPCStandIn.Awake runs with no Systems scene in
|
|
/// sight. That produced:
|
|
///
|
|
/// [SceneServices] Bartender: could not resolve dialogueRunner
|
|
///
|
|
/// Resolving a cross-scene dependency in Awake is a race by construction. The fix
|
|
/// is to resolve at the point of use instead, so any load order works — and this is
|
|
/// the test that holds that property.
|
|
/// </summary>
|
|
public class SceneOrderTests
|
|
{
|
|
const string Systems = "Systems";
|
|
const string Level = "SC101_ConferenceHall";
|
|
|
|
[UnityTearDown]
|
|
public IEnumerator TearDown()
|
|
{
|
|
yield return SceneManager.LoadSceneAsync("Bootstrap", LoadSceneMode.Single);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator LevelLoadedBeforeSystems_NpcsStillResolveTheirRunner()
|
|
{
|
|
// deliberately the wrong way round
|
|
yield return SceneManager.LoadSceneAsync(Level, LoadSceneMode.Single);
|
|
yield return SceneManager.LoadSceneAsync(Systems, LoadSceneMode.Additive);
|
|
for (var i = 0; i < 3; i++)
|
|
{
|
|
yield return null;
|
|
}
|
|
|
|
var npcs = Object.FindObjectsByType<NPCStandIn>(
|
|
FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
Assert.IsNotEmpty(npcs, "Expected NPCs in the level scene");
|
|
|
|
// Read the serialized field BEFORE touching the lazy property, which caches
|
|
// into it. If this is null, Awake really did run with no Systems scene — i.e.
|
|
// the race this test exists for is genuinely reproduced here, and the
|
|
// assertion below is not passing for free.
|
|
var eager = npcs.Count(n => n.dialogueRunner == null);
|
|
Debug.Log($"[Order] NPCs whose Awake-time resolution failed: {eager}/{npcs.Length}");
|
|
Assert.Greater(eager, 0,
|
|
"Awake-time resolution succeeded, so this test is not reproducing the race " +
|
|
"it was written for. Rewrite it before trusting it.");
|
|
|
|
foreach (var npc in npcs)
|
|
{
|
|
Assert.IsNotNull(npc.ResolvedDialogueRunner,
|
|
$"'{npc.name}' could not reach the DialogueRunner when the level loaded " +
|
|
"before Systems. Cross-scene resolution must not depend on load order.");
|
|
}
|
|
}
|
|
}
|
|
}
|