122 lines
5.0 KiB
C#
122 lines
5.0 KiB
C#
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
|
||
}
|
||
}
|