Files
Emberwild/Assets/GAME/Script/Editor/BuildStressTest.cs
T
2026-07-22 12:56:13 +02:00

122 lines
5.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Diagnostics;
using UnityEditor;
using UnityEngine;
using UnityEngine.Profiling;
using Ashwild.Building;
using Debug = UnityEngine.Debug;
namespace Ashwild.EditorTools
{
/// <summary>
/// A load test for the decision to spawn every build as a NetworkObject: places a grid of structures
/// and reports how long it took and what it cost in memory, so the "can FishNet carry a whole base"
/// question can be answered with numbers instead of estimates.
///
/// Deliberately single-instance. Measuring a second client's join bandwidth would mean two peers, and
/// the session is wired to FishySteamworks — that costs far more setup than the risk it covers. Join
/// traffic is a one-shot cost of roughly a spawn packet per object anyway; what actually matters day
/// to day is the steady state, and that is exactly what this measures. Watch the profiler's frame time
/// after the spawn, not just the numbers logged here.
///
/// Caveat when reading the elapsed time: this places everything in a single frame, which no player
/// ever does. Treat it as a worst case for the spawn burst, not as a gameplay measurement.
/// </summary>
public static class BuildStressTest
{
#region Constants
private const float Spacing = 4f;
private const float GroundHeight = 0f;
#endregion
#region Menu
/// <summary>
/// Places 250 structures — a realistic co-op base.
/// </summary>
[MenuItem("Tools/Ashwild/Stress Test/Spawn 250 Builds")]
public static void Spawn250() => SpawnGrid(250);
/// <summary>
/// Places 1000 structures — an ambitious long-save base, the level the NetworkObject decision was
/// judged against.
/// </summary>
[MenuItem("Tools/Ashwild/Stress Test/Spawn 1000 Builds")]
public static void Spawn1000() => SpawnGrid(1000);
#endregion
#region Internal Helpers
/// <summary>
/// Commits <paramref name="count"/> builds of the first usable buildable on a grid around the
/// origin, then logs the elapsed time and the memory delta. Requires play mode with a live session,
/// since committing goes through the registry's server RPC exactly as a real placement would.
/// </summary>
private static void SpawnGrid(int count)
{
if (!Application.isPlaying)
{
Debug.LogError("[BuildStressTest] Enter play mode and host a session first — builds are spawned through the server.");
return;
}
if (BuildRegistry.Instance == null)
{
Debug.LogError("[BuildStressTest] No BuildRegistry in the session — is the scene's BuildRegistry present and the session started?");
return;
}
if (!TryResolveBuildable(out ushort id, out string label)) return;
int side = Mathf.CeilToInt(Mathf.Sqrt(count));
long memoryBefore = Profiler.GetTotalAllocatedMemoryLong();
Stopwatch watch = Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
Vector3 position = new Vector3((i % side) * Spacing, GroundHeight, (i / side) * Spacing);
BuildRegistry.Instance.RequestBuild(id, position, Quaternion.identity);
}
watch.Stop();
long memoryDelta = Profiler.GetTotalAllocatedMemoryLong() - memoryBefore;
Debug.Log($"[BuildStressTest] Requested {count}× '{label}' in {watch.ElapsedMilliseconds} ms " +
$"({memoryDelta / 1024f / 1024f:F1} MB allocated this frame). " +
"Spawns complete over the next frames — check the profiler's steady-state frame time now.");
}
/// <summary>
/// Picks the first buildable that can actually be spawned, so the test never fails halfway on a
/// half-authored asset. Reports clearly when the database is empty or unbuilt.
/// </summary>
private static bool TryResolveBuildable(out ushort id, out string label)
{
id = 0;
label = string.Empty;
BuildableDatabase database = BuildableDatabase.Instance;
if (database == null || database.Buildables == null || database.Buildables.Length == 0)
{
Debug.LogError("[BuildStressTest] BuildableDatabase is empty — run Tools ▸ Ashwild ▸ Rebuild Buildable Database.");
return false;
}
foreach (BuildableData buildable in database.Buildables)
{
if (buildable == null || buildable.BuiltPrefab == null) continue;
id = database.GetId(buildable);
label = buildable.name;
return true;
}
Debug.LogError("[BuildStressTest] No buildable with a BuiltPrefab found.");
return false;
}
#endregion
}
}