Cut 1: belts are evaluated when asked, not simulated every tick
Zone.Update no longer threads a per-belt Task.Run over a double-buffered Transforms/OrbitPosition pair. AsteroidBelt.Pose/Size are the one place a chunk pose is computed, a pure function of zone time; Zone.ChunkPose and Zone.EvaluateBelt read them at the zone's own time. MiningTool, ResourceScanner and ZoneRenderer (belt matrices and AsteroidBeltUI) all read through Zone instead of the deleted per-tick buffers; the renderer owns its own float4[] buffer, filled fresh every frame. Deleted: System.Threading/.Tasks usings, BeltUpdates, the wait loop, the per-belt copy+Task.Run, NearestAsteroid (dead/broken reader), the private UpdateAsteroidTransforms, and AsteroidBelt's four pose fields plus their allocations. ~65 lines, matching the cut map's estimate. miningprobe, densest zone of the operator's save (EAC-2265, 809 asteroids, 8 belts), rebuilt against this cut and rerun 10x each side: before (branch point, 8bd25f6f/e729e6d6): Zone.Update wall 541-3654 us/tick, CPU 120-391 us/tick (7 clean runs) 3/10 runs crashed: InvalidOperationException from Zone.GetOrbitPosition, the documented _updatedOrbits/Orbits data race between the main thread's Clear() and a belt Task.Run after (this commit): Zone.Update wall 95-185 us/tick, CPU 99-152 us/tick, in line with the 31-39 us "no belt work" baseline the map recorded -- belts are free per tick 0/10 races ChunkPose has no buffer to go stale: ~0.12 u of parent+asteroid motion is visible across one 17 ms tick instead of being one tick behind tests/Aetheria.Shared.Tests/MiningCut1Tests.cs (new, 260/260 suite green): ChunkPoseIsTheOrbitAtZoneTime, ChunkPoseDoesNotDependOnUpdateCadence, DamagedChunkShrinksAndABrokenOneHasNoSize -- each expected value is the closed-form orbit/size rule reimplemented in the test, never a call to AsteroidBelt.Pose/Size. Negative grep (Zone.cs, "Zone Display", ServerShared/Behaviors) for Task\.Run|System\.Threading|NewTransforms|\.Transforms\b|BeltUpdates| NearestAsteroid|\.OrbitPosition\b|NewOrbitPosition: one hit, the commented line in MineAsteroid that Cut 2 deletes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e729e6d6d5
commit
22a54ccbaf
5 changed files with 266 additions and 77 deletions
|
|
@ -56,10 +56,11 @@ public class MiningTool : Behavior
|
|||
public override bool Execute(float dt)
|
||||
{
|
||||
Range = Evaluate(_data.Range);
|
||||
var belt = Entity.Zone.AsteroidBelts[AsteroidBelt];
|
||||
if (AsteroidBelt.IsSet() &&
|
||||
Entity.Zone.AsteroidExists(AsteroidBelt, Asteroid) &&
|
||||
length(Entity.Position.xz - belt.Transforms[Asteroid].xy) - belt.Transforms[Asteroid].w < Range)
|
||||
if (!AsteroidBelt.IsSet() || !Entity.Zone.AsteroidExists(AsteroidBelt, Asteroid))
|
||||
return false;
|
||||
|
||||
var pose = Entity.Zone.ChunkPose(AsteroidBelt, Asteroid);
|
||||
if (length(Entity.Position.xz - pose.xy) - pose.w < Range)
|
||||
{
|
||||
Entity.Zone.MineAsteroid(
|
||||
Entity,
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior
|
|||
{
|
||||
if(Asteroid > -1 &&
|
||||
Asteroid < beltData.Asteroids.Length &&
|
||||
length(Entity.Position.xz - Entity.Zone.AsteroidBelts[ScanTarget].Transforms[Asteroid].xy) < Range)
|
||||
length(Entity.Position.xz - Entity.Zone.ChunkPose(ScanTarget, Asteroid).xy) < Range)
|
||||
{
|
||||
_scanTime += dt;
|
||||
if (_scanTime > ScanDuration)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GameCult.Caching;
|
||||
using UniRx;
|
||||
using CultMath;
|
||||
|
|
@ -43,8 +41,6 @@ public class Zone
|
|||
private int _nextShotId;
|
||||
public int NextShotId() => ++_nextShotId;
|
||||
|
||||
private List<Task> BeltUpdates = new List<Task>();
|
||||
|
||||
public float Time
|
||||
{
|
||||
get => (float) _time;
|
||||
|
|
@ -150,9 +146,6 @@ public class Zone
|
|||
{
|
||||
_time += deltaTime;
|
||||
_updatedOrbits.Clear();
|
||||
foreach (var t in BeltUpdates)
|
||||
t.Wait();
|
||||
BeltUpdates.Clear();
|
||||
foreach (var orbit in Orbits)
|
||||
{
|
||||
orbit.Value.PreviousPosition = orbit.Value.Position;
|
||||
|
|
@ -160,13 +153,6 @@ public class Zone
|
|||
orbit.Value.Velocity = (orbit.Value.Position - orbit.Value.PreviousPosition) / deltaTime;
|
||||
}
|
||||
|
||||
foreach (var belt in AsteroidBelts)
|
||||
{
|
||||
Array.Copy(belt.Value.NewTransforms, belt.Value.Transforms, belt.Value.Transforms.Length);
|
||||
belt.Value.OrbitPosition = belt.Value.NewOrbitPosition;
|
||||
BeltUpdates.Add(Task.Run(() => UpdateAsteroidTransforms(belt.Key)));
|
||||
}
|
||||
|
||||
foreach(var agent in Agents)
|
||||
agent.Update(deltaTime);
|
||||
|
||||
|
|
@ -223,55 +209,22 @@ public class Zone
|
|||
return float2.zero;
|
||||
}
|
||||
|
||||
public int NearestAsteroid(CultRecordKey planetDataID, float2 position)
|
||||
{
|
||||
var beltData = Planets[planetDataID] as AsteroidBeltData;
|
||||
|
||||
var asteroidPositions = AsteroidBelts[planetDataID].Transforms;
|
||||
|
||||
int nearest = 0;
|
||||
float nearestDistance = Single.MaxValue;
|
||||
for (int i = 0; i < beltData.Asteroids.Length; i++)
|
||||
{
|
||||
var dist = lengthsq(asteroidPositions[i].xz - position);
|
||||
if (AsteroidExists(planetDataID, i) && dist < nearestDistance)
|
||||
{
|
||||
nearest = i;
|
||||
nearestDistance = dist;
|
||||
}
|
||||
}
|
||||
|
||||
return nearest;
|
||||
}
|
||||
|
||||
public bool AsteroidExists(CultRecordKey planetDataID, int asteroid) => ((AsteroidBeltData) Planets[planetDataID]).Asteroids.Length > asteroid && asteroid >= 0;
|
||||
|
||||
private void UpdateAsteroidTransforms(CultRecordKey planetDataID)
|
||||
// Cut 1 (docs/mining-cut.md): a chunk's pose is a pure function of zone time, computed when asked. No stored
|
||||
// pose survives a tick; the sim and the renderer both read it fresh through here.
|
||||
public float4 ChunkPose(CultRecordKey belt, int index)
|
||||
{
|
||||
var beltData = Planets[planetDataID] as AsteroidBeltData;
|
||||
var beltData = Planets[belt] as AsteroidBeltData;
|
||||
var parentPosition = GetOrbitPosition(Orbits[beltData.Orbit.Key].Data.Parent.Key);
|
||||
return AsteroidBelts[belt].Pose(index, _time, parentPosition, Settings);
|
||||
}
|
||||
|
||||
var belt = AsteroidBelts[planetDataID];
|
||||
|
||||
var orbitData = Orbits[beltData.Orbit.Key].Data;
|
||||
belt.NewOrbitPosition = GetOrbitPosition(orbitData.Parent.Key);
|
||||
for (var i = 0; i < beltData.Asteroids.Length; i++)
|
||||
{
|
||||
float size;
|
||||
if(belt.RespawnTimers.ContainsKey(i)) size = 0;
|
||||
else if (belt.Damage.ContainsKey(i))
|
||||
{
|
||||
var asteroidHitpoints = Settings.AsteroidHitpoints.Evaluate(beltData.Asteroids[i].Size);
|
||||
var damage = (asteroidHitpoints - belt.Damage[i]) / asteroidHitpoints;
|
||||
size = Settings.AsteroidSize.Evaluate(damage * beltData.Asteroids[i].Size);
|
||||
}
|
||||
else size = Settings.AsteroidSize.Evaluate(beltData.Asteroids[i].Size);
|
||||
|
||||
var rot = (float) (_time * beltData.Asteroids[i].RotationSpeed % (PI * 2));
|
||||
var pos = OrbitData.Evaluate((float) frac(_time / Settings.OrbitPeriod.Evaluate(beltData.Asteroids[i].Distance) +
|
||||
beltData.Asteroids[i].Phase)) * beltData.Asteroids[i].Distance + belt.NewOrbitPosition;
|
||||
//belt.NewPositions[i] = float3(pos.x, GetHeight(pos) + Settings.AsteroidVerticalOffset, pos.y);
|
||||
belt.NewTransforms[i] = float4(pos.x, pos.y, rot, size);
|
||||
}
|
||||
public void EvaluateBelt(CultRecordKey belt, Span<float4> into)
|
||||
{
|
||||
var beltData = Planets[belt] as AsteroidBeltData;
|
||||
var parentPosition = GetOrbitPosition(Orbits[beltData.Orbit.Key].Data.Parent.Key);
|
||||
AsteroidBelts[belt].Evaluate(into, _time, parentPosition, Settings);
|
||||
}
|
||||
|
||||
public OrbitData CreateOrbit(CultRecordKey parent, float2 position)
|
||||
|
|
@ -502,11 +455,7 @@ public class Sun : GasGiant
|
|||
public class AsteroidBelt
|
||||
{
|
||||
public AsteroidBeltData Data;
|
||||
public float4[] Transforms; // x, y, rotation, scale
|
||||
public float4[] NewTransforms; // x, y, rotation, scale
|
||||
public float Radius { get; }
|
||||
public float2 OrbitPosition;
|
||||
public float2 NewOrbitPosition;
|
||||
public Dictionary<int, float> RespawnTimers = new Dictionary<int, float>();
|
||||
public Dictionary<int, float> Damage = new Dictionary<int, float>();
|
||||
public Dictionary<(Entity, int), float> MiningAccumulator = new Dictionary<(Entity, int), float>();
|
||||
|
|
@ -514,10 +463,38 @@ public class AsteroidBelt
|
|||
public AsteroidBelt(AsteroidBeltData data)
|
||||
{
|
||||
Data = data;
|
||||
Transforms = new float4[data.Asteroids.Length];
|
||||
NewTransforms = new float4[data.Asteroids.Length];
|
||||
Radius = data.Asteroids.Max(a => a.Distance);
|
||||
}
|
||||
|
||||
// Cut 1 (docs/mining-cut.md): the formula of Zone.cs:269-271 verbatim, `time` in double as `_time` was used.
|
||||
// The only place a chunk pose is computed; no per-tick copy survives it.
|
||||
public float4 Pose(int index, double time, float2 parentPosition, PlanetSettings settings)
|
||||
{
|
||||
var asteroid = Data.Asteroids[index];
|
||||
var rot = (float) (time * asteroid.RotationSpeed % (PI * 2));
|
||||
var pos = OrbitData.Evaluate((float) frac(time / settings.OrbitPeriod.Evaluate(asteroid.Distance) +
|
||||
asteroid.Phase)) * asteroid.Distance + parentPosition;
|
||||
return float4(pos.x, pos.y, rot, Size(index, settings));
|
||||
}
|
||||
|
||||
// Cut 1 (docs/mining-cut.md): the size rule of Zone.cs:259-267, moved unchanged.
|
||||
public float Size(int index, PlanetSettings settings)
|
||||
{
|
||||
if (RespawnTimers.ContainsKey(index)) return 0;
|
||||
if (Damage.ContainsKey(index))
|
||||
{
|
||||
var asteroidHitpoints = settings.AsteroidHitpoints.Evaluate(Data.Asteroids[index].Size);
|
||||
var damage = (asteroidHitpoints - Damage[index]) / asteroidHitpoints;
|
||||
return settings.AsteroidSize.Evaluate(damage * Data.Asteroids[index].Size);
|
||||
}
|
||||
return settings.AsteroidSize.Evaluate(Data.Asteroids[index].Size);
|
||||
}
|
||||
|
||||
public void Evaluate(Span<float4> into, double time, float2 parentPosition, PlanetSettings settings)
|
||||
{
|
||||
for (var i = 0; i < Data.Asteroids.Length; i++)
|
||||
into[i] = Pose(i, time, parentPosition, settings);
|
||||
}
|
||||
}
|
||||
|
||||
public class Orbit
|
||||
|
|
|
|||
Loading…
Reference in a new issue