Skip to content

Commit 70b46aa

Browse files
[Core] Added ExpandableContainer;
[Rendering] More optimization for GC and memory usage;
1 parent 61bd550 commit 70b46aa

File tree

6 files changed

+143
-104
lines changed

6 files changed

+143
-104
lines changed

Engine/Core/Audio/AudioSystem.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ private class AudioSourceInfo
7272
/// <summary>
7373
/// Thread lock for background usage
7474
/// </summary>
75-
private readonly object backgroundLock = new();
75+
private readonly Lock backgroundLock = new();
7676

7777
/// <summary>
7878
/// Pending actions for the background thrread
@@ -89,6 +89,11 @@ private class AudioSourceInfo
8989
/// </summary>
9090
private readonly List<AudioSourceInfo> audioSources = [];
9191

92+
/// <summary>
93+
/// List of audio sources to be removed
94+
/// </summary>
95+
private ExpandableContainer<AudioSourceInfo> removedAudioSources = new();
96+
9297
private SceneQuery<Transform, AudioListener> audioListeners;
9398

9499
public void Startup()
@@ -249,13 +254,13 @@ public void Update()
249254
}
250255
}
251256

252-
var removed = new List<AudioSourceInfo>();
257+
removedAudioSources.Clear();
253258

254259
foreach (var item in audioSources)
255260
{
256261
if (item.source.TryGetTarget(out var source) == false)
257262
{
258-
removed.Add(item);
263+
removedAudioSources.Add(item);
259264

260265
continue;
261266
}
@@ -342,9 +347,9 @@ public void Update()
342347
}
343348
}
344349

345-
foreach (var item in removed)
350+
for(var i = 0; i < removedAudioSources.Length; i++)
346351
{
347-
audioSources.Remove(item);
352+
audioSources.Remove(removedAudioSources.Contents[i]);
348353
}
349354
}
350355

Engine/Core/Rendering/Text/TextRenderer.cs

Lines changed: 63 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ public struct PosTexVertex
1414
public Vector2 uv;
1515
}
1616

17+
private ExpandableContainer<PosTexVertex> vertexCache = new();
18+
private ExpandableContainer<ushort> indexCache = new();
19+
1720
private TextFont defaultFont;
1821

1922
private TextFont DefaultFont
@@ -273,7 +276,7 @@ public void DrawText(string text, Matrix4x4 transform, TextParameters parameters
273276
if(VertexBuffer.TransientBufferHasSpace(vertices.Length, VertexLayout.Value) &&
274277
IndexBuffer.TransientBufferHasSpace(indices.Length, false))
275278
{
276-
var vertexBuffer = VertexBuffer.CreateTransient(vertices.AsSpan(), VertexLayout.Value);
279+
var vertexBuffer = VertexBuffer.CreateTransient(vertices, VertexLayout.Value);
277280
var indexBuffer = IndexBuffer.CreateTransient(indices);
278281

279282
if(vertexBuffer == null || indexBuffer == null)
@@ -289,7 +292,7 @@ public void DrawText(string text, Matrix4x4 transform, TextParameters parameters
289292
}
290293
}
291294

292-
public bool MakeTextGeometry(string text, TextParameters parameters, float scale, bool flipY, out PosTexVertex[] vertices, out ushort[] indices)
295+
public bool MakeTextGeometry(string text, TextParameters parameters, float scale, bool flipY, out Span<PosTexVertex> vertices, out Span<ushort> indices)
293296
{
294297
ArgumentNullException.ThrowIfNull(text);
295298

@@ -316,10 +319,10 @@ public bool MakeTextGeometry(string text, TextParameters parameters, float scale
316319

317320
var initialPosition = position;
318321

319-
var lines = text.Replace("\r", "").Split("\n".ToCharArray());
322+
var lines = text.Replace("\r", "").Split(['\n']);
320323

321-
var outVertices = new List<PosTexVertex>();
322-
var outIndices = new List<ushort>();
324+
vertexCache.Clear();
325+
indexCache.Clear();
323326

324327
foreach (var line in lines)
325328
{
@@ -352,60 +355,64 @@ public bool MakeTextGeometry(string text, TextParameters parameters, float scale
352355

353356
var p = position + new Vector2(glyph.xOffset * scale, yOffset * scale);
354357

355-
outIndices.AddRange([(ushort)outVertices.Count, (ushort)(outVertices.Count + 1), (ushort)(outVertices.Count + 2),
356-
(ushort)(outVertices.Count + 2), (ushort)(outVertices.Count + 3), (ushort)outVertices.Count]);
358+
indexCache.Add((ushort)vertexCache.Length);
359+
indexCache.Add((ushort)(vertexCache.Length + 1));
360+
indexCache.Add((ushort)(vertexCache.Length + 2));
361+
indexCache.Add((ushort)(vertexCache.Length + 2));
362+
indexCache.Add((ushort)(vertexCache.Length + 3));
363+
indexCache.Add((ushort)(vertexCache.Length));
357364

358-
if(flipY)
365+
if (flipY)
359366
{
360-
outVertices.AddRange([
361-
362-
new()
363-
{
364-
position = p + new Vector2(0, size.Y),
365-
uv = new Vector2(glyph.uvBounds.left, glyph.uvBounds.bottom)
366-
},
367-
new()
368-
{
369-
position = p,
370-
uv = new Vector2(glyph.uvBounds.left, glyph.uvBounds.top)
371-
},
372-
new()
373-
{
374-
position = p + new Vector2(size.X, 0),
375-
uv = new Vector2(glyph.uvBounds.right, glyph.uvBounds.top)
376-
},
377-
new()
378-
{
379-
position = p + size,
380-
uv = new Vector2(glyph.uvBounds.right, glyph.uvBounds.bottom)
381-
},
382-
]);
367+
vertexCache.Add(new()
368+
{
369+
position = p + new Vector2(0, size.Y),
370+
uv = new Vector2(glyph.uvBounds.left, glyph.uvBounds.bottom)
371+
});
372+
373+
vertexCache.Add(new()
374+
{
375+
position = p,
376+
uv = new Vector2(glyph.uvBounds.left, glyph.uvBounds.top)
377+
});
378+
379+
vertexCache.Add(new()
380+
{
381+
position = p + new Vector2(size.X, 0),
382+
uv = new Vector2(glyph.uvBounds.right, glyph.uvBounds.top)
383+
});
384+
385+
vertexCache.Add(new()
386+
{
387+
position = p + size,
388+
uv = new Vector2(glyph.uvBounds.right, glyph.uvBounds.bottom)
389+
});
383390
}
384391
else
385392
{
386-
outVertices.AddRange([
387-
388-
new()
389-
{
390-
position = p,
391-
uv = new Vector2(glyph.uvBounds.left, glyph.uvBounds.bottom)
392-
},
393-
new()
394-
{
395-
position = p + new Vector2(0, size.Y),
396-
uv = new Vector2(glyph.uvBounds.left, glyph.uvBounds.top)
397-
},
398-
new()
399-
{
400-
position = p + size,
401-
uv = new Vector2(glyph.uvBounds.right, glyph.uvBounds.top)
402-
},
403-
new()
404-
{
405-
position = p + new Vector2(size.X, 0),
406-
uv = new Vector2(glyph.uvBounds.right, glyph.uvBounds.bottom)
407-
},
408-
]);
393+
vertexCache.Add(new()
394+
{
395+
position = p,
396+
uv = new Vector2(glyph.uvBounds.left, glyph.uvBounds.bottom)
397+
});
398+
399+
vertexCache.Add(new()
400+
{
401+
position = p + new Vector2(0, size.Y),
402+
uv = new Vector2(glyph.uvBounds.left, glyph.uvBounds.top)
403+
});
404+
405+
vertexCache.Add(new()
406+
{
407+
position = p + size,
408+
uv = new Vector2(glyph.uvBounds.right, glyph.uvBounds.top)
409+
});
410+
411+
vertexCache.Add(new()
412+
{
413+
position = p + new Vector2(size.X, 0),
414+
uv = new Vector2(glyph.uvBounds.right, glyph.uvBounds.bottom)
415+
});
409416
}
410417

411418
position.X += advance;
@@ -423,8 +430,8 @@ public bool MakeTextGeometry(string text, TextParameters parameters, float scale
423430
position.Y += lineSpace;
424431
}
425432

426-
vertices = outVertices.ToArray();
427-
indices = outIndices.ToArray();
433+
vertices = vertexCache.Contents.AsSpan(0, vertexCache.Length);
434+
indices = indexCache.Contents.AsSpan(0, indexCache.Length);
428435

429436
return true;
430437
}

Engine/Core/StapleCore.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@
319319
<Compile Include="Serialization\Scene\SerializableScene.cs" />
320320
<Compile Include="Serialization\App\AppSettingsHeader.cs" />
321321
<Compile Include="Utilities\DictionaryExtensions.cs" />
322+
<Compile Include="Utilities\ExpandableContainer.cs" />
322323
<Compile Include="Utilities\GuidGenerator.cs" />
323324
<Compile Include="Utilities\IntLookupCache.cs" />
324325
<Compile Include="Utilities\ObjectCreation.cs" />

Engine/Core/UI/UIImage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public override void Render(Vector2Int position, ushort viewID)
7878

7979
var vertexBuffer = VertexBuffer.CreateTransient(vertices.AsSpan(), SpriteRenderSystem.vertexLayout.Value);
8080

81-
var indexBuffer = IndexBuffer.CreateTransient(indices.AsSpan());
81+
var indexBuffer = IndexBuffer.CreateTransient(indices);
8282

8383
if (vertexBuffer == null || indexBuffer == null)
8484
{

Engine/Core/UI/UIText.cs

Lines changed: 13 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,6 @@ public class UIText : UIElement
7777
private Color previousSecondaryTextColor;
7878
private int previousBorderSize;
7979
private Color previousBorderColor;
80-
private VertexBuffer vertexBuffer;
81-
private IndexBuffer indexBuffer;
82-
private int vertexCount;
83-
private int indexCount;
8480
private Vector2Int intrinsicSize;
8581

8682
private bool IsDirty
@@ -179,56 +175,31 @@ public override void Render(Vector2Int position, ushort viewID)
179175

180176
var parameters = Parameters();
181177

182-
if (IsDirty ||
183-
(vertexBuffer?.Disposed ?? true) ||
184-
(indexBuffer?.Disposed ?? true))
178+
if (IsDirty)
185179
{
186180
IsDirty = false;
187181

188-
vertexBuffer?.Destroy();
189-
indexBuffer?.Destroy();
190-
191-
if (TextRenderer.instance.MakeTextGeometry(text, parameters, 1, true, out var vertices, out var indices))
192-
{
193-
vertexBuffer = VertexBuffer.Create(vertices.AsSpan(), TextRenderer.VertexLayout.Value);
194-
195-
indexBuffer = IndexBuffer.Create(indices, RenderBufferFlags.None);
196-
197-
if(vertexBuffer == null || indexBuffer == null)
198-
{
199-
vertexBuffer?.Destroy();
200-
indexBuffer?.Destroy();
201-
202-
vertexBuffer = null;
203-
indexBuffer = null;
204-
vertexCount = 0;
205-
indexCount = 0;
206-
207-
return;
208-
}
209-
210-
vertexCount = vertices.Length;
211-
indexCount = indices.Length;
212-
}
213-
214182
UpdateFontSize();
215183

216184
var rect = TextRenderer.instance.MeasureTextSimple(text, Parameters());
217185

218186
intrinsicSize = new Vector2Int(rect.left + rect.Width, rect.top + rect.Height);
219187
}
220188

221-
if (vertexBuffer != null &&
222-
indexBuffer != null &&
223-
vertexBuffer.Disposed == false &&
224-
indexBuffer.Disposed == false &&
225-
material != null)
189+
if (material != null)
226190
{
227-
material.MainTexture = TextRenderer.instance.FontTexture(parameters);
191+
if (TextRenderer.instance.MakeTextGeometry(text, parameters, 1, true, out var vertices, out var indices))
192+
{
193+
var vertexBuffer = VertexBuffer.CreateTransient(vertices, TextRenderer.VertexLayout.Value);
228194

229-
Graphics.RenderGeometry(vertexBuffer, indexBuffer, 0, vertexCount, 0, indexCount, material, Vector3.Zero,
230-
Matrix4x4.CreateTranslation(new Vector3(position.X, position.Y, 0)), MeshTopology.Triangles, MaterialLighting.Unlit,
231-
viewID);
195+
var indexBuffer = IndexBuffer.CreateTransient(indices);
196+
197+
material.MainTexture = TextRenderer.instance.FontTexture(parameters);
198+
199+
Graphics.RenderGeometry(vertexBuffer, indexBuffer, 0, vertices.Length, 0, indices.Length, material, Vector3.Zero,
200+
Matrix4x4.CreateTranslation(new Vector3(position.X, position.Y, 0)), MeshTopology.Triangles, MaterialLighting.Unlit,
201+
viewID);
202+
}
232203
}
233204
}
234205
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace Staple;
5+
6+
/// <summary>
7+
/// An expandable container for a specific type of element.
8+
/// Unlike a regular list or array, this is optimized to never actually clear its data
9+
/// and to not act as IEnumerable, providing the raw array instead, for maximum performance.
10+
/// You should always use the Length property rather than the Contents Length property when iterating.
11+
/// This allows for fast reusable iterations with varying amounts of elements over each frame,
12+
/// reallocating the least amount possible.
13+
/// </summary>
14+
/// <typeparam name="T">The type to use</typeparam>
15+
public class ExpandableContainer<T>
16+
{
17+
private T[] contents = [];
18+
private int length = 0;
19+
20+
/// <summary>
21+
/// The amount of elements contained
22+
/// </summary>
23+
public int Length => length;
24+
25+
/// <summary>
26+
/// Gets the current contents.
27+
/// </summary>
28+
/// <remarks>Use the Length property of the ExpandableContainer, not the contents's Length</remarks>
29+
public T[] Contents => contents;
30+
31+
/// <summary>
32+
/// Clears the contents
33+
/// </summary>
34+
/// <remarks>This doesn't actually deallocate memory, just sets the length to 0.</remarks>
35+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
36+
public void Clear()
37+
{
38+
length = 0;
39+
}
40+
41+
/// <summary>
42+
/// Adds an item to the container
43+
/// </summary>
44+
/// <param name="item">The item to add</param>
45+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
46+
public void Add(T item)
47+
{
48+
if(length + 1 >= contents.Length)
49+
{
50+
Array.Resize(ref contents, (length + 1) * 2);
51+
}
52+
53+
contents[length++] = item;
54+
}
55+
}

0 commit comments

Comments
 (0)