Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Benchmarks/Shared/Rendering/RenderBenchmarkFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using ChangeTrace.Rendering.States.Edges;
using ChangeTrace.Rendering.States.Nodes;
using ChangeTrace.Rendering.States.Particles;
using ChangeTrace.Rendering.States.Scene;

namespace ChangeTrace.Benchmarks.Shared.Rendering;

Expand Down Expand Up @@ -155,7 +156,7 @@ public SceneSnapshot AssembleSceneSnapshot()
var edges = _edgeSnapshots.Assemble(Scene, nodeIndex);
var particles = _particleSnapshots.Assemble(Animation);

return new SceneSnapshot(nodes, avatars, edges, particles);
return SceneSnapshotMaterializer.Create(nodes, avatars, edges, particles);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using ChangeTrace.Benchmarks.Rendering;
using ChangeTrace.Benchmarks.Shared.Rendering;
using ChangeTrace.Rendering.Snapshots;
using ChangeTrace.Rendering.States.Scene;

namespace ChangeTrace.Benchmarks.Subsystem.Rendering;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using ChangeTrace.Rendering.States.Edges;
using ChangeTrace.Rendering.States.Nodes;
using ChangeTrace.Rendering.States.Particles;
using ChangeTrace.Rendering.States.Scene;

namespace ChangeTrace.Benchmarks.Subsystem.Rendering;

Expand Down Expand Up @@ -89,7 +90,7 @@ public int AssembleParticleSnapshots()
[Benchmark]
public int MaterializeSceneSnapshot()
{
var snapshot = new SceneSnapshot(
var snapshot = SceneSnapshotMaterializer.Create(
_prebuiltNodes,
_prebuiltAvatars,
_prebuiltEdges,
Expand Down
63 changes: 63 additions & 0 deletions Tests/Unit/Rendering/States/SceneSnapshotTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Numerics;
using ChangeTrace.Rendering;
using ChangeTrace.Rendering.Enums;
using ChangeTrace.Rendering.Scene;
using ChangeTrace.Rendering.Snapshots;
using ChangeTrace.Rendering.States.Scene;
using Xunit;

namespace ChangeTrace.Tests.Rendering.States;

/// <summary>Tests immutable scene snapshot materialization behavior.</summary>
public sealed class SceneSnapshotTests
{
[Fact]
public void Constructor_SortsNodesAndFindNodeUsesSortedIndex()
{
var nodes = new[]
{
new NodeSnapshot("src/Program.cs", new Vec2(2, 0), 4f, new Vector4(1f), 0.1f, NodeKind.File, "Program.cs", false, "src"),
new NodeSnapshot(SceneIds.Root, new Vec2(0, 0), 18f, new Vector4(1f), 1f, NodeKind.Root, SceneIds.RootLabel, true),
new NodeSnapshot("src", new Vec2(1, 0), 7f, new Vector4(1f), 0.2f, NodeKind.Branch, "src", true, "__root_files__")
};

var snapshot = SceneSnapshotMaterializer.Create(nodes, [], [], []);

Assert.Collection(
snapshot.Nodes,
node => Assert.Equal(SceneIds.Root, node.Id),
node => Assert.Equal("src", node.Id),
node => Assert.Equal("src/Program.cs", node.Id));

var branch = snapshot.FindNode("src");
Assert.NotNull(branch);
Assert.Equal(NodeKind.Branch, branch.Value.Kind);
}

[Fact]
public void Constructor_RemapsEdgeIndexesAfterNodeSortingAndDropsInvalidDuplicates()
{
var nodes = new[]
{
new NodeSnapshot("src/Program.cs", new Vec2(2, 0), 4f, new Vector4(1f), 0.1f, NodeKind.File, "Program.cs", false, "src"),
new NodeSnapshot(SceneIds.Root, new Vec2(0, 0), 18f, new Vector4(1f), 1f, NodeKind.Root, SceneIds.RootLabel, true),
new NodeSnapshot("src", new Vec2(1, 0), 7f, new Vector4(1f), 0.2f, NodeKind.Branch, "src", true, "__root_files__")
};

var edges = new[]
{
new EdgeSnapshotIndexed(2, 0, EdgeKind.Hierarchy, 1f, new Vector4(1f)),
new EdgeSnapshotIndexed(2, 0, EdgeKind.Hierarchy, 1f, new Vector4(1f)),
new EdgeSnapshotIndexed(9, 0, EdgeKind.Hierarchy, 1f, new Vector4(1f))
};

var snapshot = SceneSnapshotMaterializer.Create(nodes, [], edges, []);

var edge = Assert.Single(snapshot.Edges);
Assert.Equal("src", snapshot.Nodes[edge.FromIndex].Id);
Assert.Equal("src/Program.cs", snapshot.Nodes[edge.ToIndex].Id);

Assert.Equal("src", snapshot.Nodes[snapshot.Edges[0].FromIndex].Id);
Assert.Equal("src/Program.cs", snapshot.Nodes[snapshot.Edges[0].ToIndex].Id);
}
}
5 changes: 3 additions & 2 deletions src/Rendering/Interfaces/IAnimationSystem.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Numerics;
using ChangeTrace.Rendering.Animation;
using ChangeTrace.Rendering.Snapshots;

namespace ChangeTrace.Rendering.Interfaces;

Expand All @@ -12,7 +13,7 @@ internal interface IAnimationSystem
/// <summary>
/// Snapshots all active particles into the provided list without allocation.
/// </summary>
void SnapshotParticles(List<Snapshots.ParticleSnapshot> target);
void SnapshotParticles(List<ParticleSnapshot> target);

/// <summary>
/// Gets the number of active particles.
Expand Down Expand Up @@ -67,4 +68,4 @@ void TweenFloat(float from, float to, float duration, EasingFn easing,
/// Resets system state for a fresh start.
/// </summary>
void Clear();
}
}
1 change: 0 additions & 1 deletion src/Rendering/Snapshots/NodeSnapshot.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Numerics;
using ChangeTrace.Rendering.Enums;
using ChangeTrace.Rendering.Scene;

namespace ChangeTrace.Rendering.Snapshots;

Expand Down
26 changes: 26 additions & 0 deletions src/Rendering/Snapshots/OrderedNodeSnapshotList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Collections;

namespace ChangeTrace.Rendering.Snapshots;

/// <summary>
/// Lightweight ordered node snapshot view over an existing node buffer.
/// </summary>
internal sealed class OrderedNodeSnapshotList(
IReadOnlyList<NodeSnapshot> source,
int[] order)
: IReadOnlyList<NodeSnapshot>
{
public int Count => order.Length;

public NodeSnapshot this[int index] =>
source[order[index]];

public IEnumerator<NodeSnapshot> GetEnumerator()
{
foreach (var item in order)
yield return source[item];
}

IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
37 changes: 37 additions & 0 deletions src/Rendering/Snapshots/RemappedEdgeSnapshotList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Collections;

namespace ChangeTrace.Rendering.Snapshots;

/// <summary>
/// Lightweight remapped edge snapshot view over an existing edge buffer.
/// </summary>
internal sealed class RemappedEdgeSnapshotList(
IReadOnlyList<EdgeSnapshotIndexed> source,
int[] edgeIndexes,
int[] remap)
: IReadOnlyList<EdgeSnapshotIndexed>
{
public int Count => edgeIndexes.Length;

public EdgeSnapshotIndexed this[int index]
{
get
{
var edge = source[edgeIndexes[index]];
return edge with
{
FromIndex = remap[edge.FromIndex],
ToIndex = remap[edge.ToIndex]
};
}
}

public IEnumerator<EdgeSnapshotIndexed> GetEnumerator()
{
for (var i = 0; i < edgeIndexes.Length; i++)
yield return this[i];
}

IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
79 changes: 19 additions & 60 deletions src/Rendering/Snapshots/SceneSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,63 +14,27 @@ internal sealed class SceneSnapshot : ISceneSnapshot
/// <summary>
/// Fast node lookup index.
/// </summary>
private readonly Dictionary<string, NodeSnapshot> _nodeIndex;
private readonly Dictionary<string, int> _nodePositionIndex;

internal SceneSnapshot(
IReadOnlyList<NodeSnapshot> nodes,
IReadOnlyList<AvatarSnapshot> avatars,
IReadOnlyList<EdgeSnapshotIndexed> edges,
IReadOnlyList<ParticleSnapshot> particles)
IReadOnlyList<ParticleSnapshot> particles,
Dictionary<string, int> nodePositionIndex)
{
_nodeIndex =
new Dictionary<string, NodeSnapshot>(
nodes.Count);
_nodePositionIndex =
new Dictionary<string, int>(
nodes.Count);

foreach (NodeSnapshot node in nodes)
_nodeIndex[node.Id] = node;

Nodes =
_nodeIndex.Values
.OrderBy(n => n.Kind switch
{
NodeKind.Root => 0,
NodeKind.Branch => 1,
NodeKind.File => 2,
_ => 1
})
.ThenBy(n => n.Id)
.ToArray();

for (var i = 0; i < Nodes.Count; i++)
_nodePositionIndex[Nodes[i].Id] = i;

Avatars =
avatars;

Edges =
edges
.Where(e =>
e.FromIndex >= 0 &&
e.ToIndex >= 0 &&
e.FromIndex < Nodes.Count &&
e.ToIndex < Nodes.Count)
.DistinctBy(e =>
(e.FromIndex, e.ToIndex, e.Kind))
.ToArray();

Particles =
particles;
Nodes = nodes;
Avatars = avatars;
Edges = edges;
Particles = particles;
_nodePositionIndex = nodePositionIndex;
}

/// <summary>
/// Empty reusable scene snapshot.
/// </summary>
internal static SceneSnapshot Empty { get; } =
new([], [], [], []);
new([], [], [], [], new Dictionary<string, int>());

/// <summary>
/// Scene nodes.
Expand Down Expand Up @@ -145,7 +109,9 @@ internal SceneSnapshot(
if (string.IsNullOrWhiteSpace(id))
return null;

return _nodeIndex.GetValueOrDefault(id);
return _nodePositionIndex.TryGetValue(id, out var index)
? Nodes[index]
: null;
}

/// <summary>
Expand All @@ -157,22 +123,19 @@ public IEnumerable<NodeSnapshot> NodesOfKind(NodeKind kind) =>
/// <summary>
/// Returns glowing nodes above a threshold.
/// </summary>
public IEnumerable<NodeSnapshot> GlowingNodes(
float threshold = 0.05f) =>
public IEnumerable<NodeSnapshot> GlowingNodes(float threshold = 0.05f) =>
Nodes.Where(n => n.Glow > threshold);

/// <summary>
/// Returns active avatars above an activity threshold.
/// </summary>
public IEnumerable<AvatarSnapshot> ActiveAvatars(
float activityThreshold = 0.1f) =>
public IEnumerable<AvatarSnapshot> ActiveAvatars(float activityThreshold = 0.1f) =>
Avatars.Where(a => a.ActivityLevel > activityThreshold);

/// <summary>
/// Returns visible avatars above an alpha threshold.
/// </summary>
public IEnumerable<AvatarSnapshot> VisibleAvatars(
float alphaThreshold = 0.05f) =>
public IEnumerable<AvatarSnapshot> VisibleAvatars(float alphaThreshold = 0.05f) =>
Avatars.Where(a =>
a.Alpha * a.ActivityLevel > alphaThreshold);

Expand Down Expand Up @@ -213,8 +176,7 @@ public IEnumerable<EdgeSnapshotIndexed> EdgesOfKind(EdgeKind kind) =>
/// <summary>
/// Returns visible edges above an alpha threshold.
/// </summary>
public IEnumerable<EdgeSnapshotIndexed> VisibleEdges(
float alphaThreshold = 0.02f) =>
public IEnumerable<EdgeSnapshotIndexed> VisibleEdges(float alphaThreshold = 0.02f) =>
Edges.Where(e => e.Alpha > alphaThreshold);

/// <summary>
Expand All @@ -225,9 +187,7 @@ public IEnumerable<EdgeSnapshotIndexed> VisibleEdges(
if (Nodes.Count == 0)
return null;

Vec2 sum =
Vec2.Zero;

Vec2 sum = Vec2.Zero;
foreach (NodeSnapshot node in Nodes)
sum += node.Position;

Expand All @@ -245,13 +205,11 @@ public IEnumerable<EdgeSnapshotIndexed> VisibleEdges(
NodeSnapshot? best =
null;

float bestDist =
float.MaxValue;
float bestDist = float.MaxValue;

foreach (NodeSnapshot node in Nodes)
{
float dist =
(node.Position - point).LengthSq;
float dist = (node.Position - point).LengthSq;

if (!(dist < bestDist))
continue;
Expand All @@ -278,4 +236,5 @@ public SceneStats ComputeStats() =>
Nodes.Count(n => n.Glow > 0.05f),
VisibleEdges:
Edges.Count(e => e.Alpha > 0.02f));

}
2 changes: 1 addition & 1 deletion src/Rendering/Snapshots/SceneStats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ internal sealed record SceneStats(
int ActiveAvatars,
int GlowingNodes,
int VisibleEdges
);
);
3 changes: 2 additions & 1 deletion src/Rendering/States/RenderStateAssembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using ChangeTrace.Rendering.States.Hud;
using ChangeTrace.Rendering.States.Nodes;
using ChangeTrace.Rendering.States.Particles;
using ChangeTrace.Rendering.States.Scene;
using Microsoft.Extensions.DependencyInjection;

namespace ChangeTrace.Rendering.States;
Expand Down Expand Up @@ -87,7 +88,7 @@ public RenderState Assemble(
leaderboard);

var sceneSnapshot =
new SceneSnapshot(
SceneSnapshotMaterializer.Create(
nodeSnapshots,
avatarSnapshots,
edgeSnapshots,
Expand Down
Loading
Loading