(Feat) Add Build

This commit is contained in:
2026-07-22 12:56:13 +02:00
parent 3657b870d8
commit 0052b0d98e
244 changed files with 35752 additions and 3112 deletions
@@ -0,0 +1,121 @@
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
}
}