Skip to content

Commit 621303a

Browse files
authored
Merge pull request #227 from Pryaxis/constileationprovider
Introduce a new tile provider: Constileation
2 parents 4266b69 + be5a01f commit 621303a

10 files changed

Lines changed: 306 additions & 45 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ jobs:
3131
run: dotnet build TSAPI.sln -c Release
3232

3333
- name: Run tests
34-
run: dotnet test
34+
run: dotnet test --filter "FullyQualifiedName!~TerrariaServerAPI.Tests.TileBenchmarks"
3535

3636
# example task for the release CI
3737
# - name: "Releasing to NuGet: TSAPI"
3838
# env:
3939
# NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
40-
# run: dotnet nuget push ./TerrariaServerAPI/bin/Release/TerrariaServer.*.nupkg --source https://api.nuget.org/v3/index.json --api-key "$env:NUGET_API_KEY"
40+
# run: dotnet nuget push ./TerrariaServerAPI/bin/Release/TerrariaServer.*.nupkg --source https://api.nuget.org/v3/index.json --api-key "$env:NUGET_API_KEY"

.github/workflows/nuget.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- name: Build
2222
run: dotnet build TSAPI.sln --configuration Release --no-restore
2323
- name: Test
24-
run: dotnet test --no-build --verbosity normal --configuration Release
24+
run: dotnet test --no-build --verbosity normal --configuration Release --filter "FullyQualifiedName!~TerrariaServerAPI.Tests.TileBenchmarks"
2525

2626
# Publish
2727
- name: Push TSAPI

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ Output/
1515
.idea/
1616
.vs/
1717
*.user
18+
19+
external/
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using NUnit.Framework;
2+
using System;
3+
using System.Threading;
4+
5+
namespace TerrariaServerAPI.Tests;
6+
7+
public class BaseTest
8+
{
9+
private static bool _initialized;
10+
11+
[OneTimeSetUp]
12+
public void EnsureInitialised()
13+
{
14+
if (!_initialized)
15+
{
16+
var are = new AutoResetEvent(false);
17+
Exception? error = null;
18+
On.Terraria.Main.hook_DedServ cb = (On.Terraria.Main.orig_DedServ orig, Terraria.Main instance) =>
19+
{
20+
instance.Initialize();
21+
are.Set();
22+
_initialized = true;
23+
};
24+
On.Terraria.Main.DedServ += cb;
25+
26+
global::TerrariaApi.Server.Program.Main(new string[] { });
27+
28+
_initialized = are.WaitOne(TimeSpan.FromSeconds(30));
29+
30+
On.Terraria.Main.DedServ -= cb;
31+
32+
Assert.That(_initialized, Is.True);
33+
Assert.That(error, Is.Null);
34+
}
35+
}
36+
}
Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,12 @@
1-
using Microsoft.VisualStudio.TestTools.UnitTesting;
2-
using System;
3-
using System.Diagnostics;
4-
using System.Threading;
1+
using NUnit.Framework;
52

6-
namespace TerrariaServerAPI.Tests
3+
namespace TerrariaServerAPI.Tests;
4+
5+
public class ServerInitTests : BaseTest
76
{
8-
[TestClass]
9-
public class ServerInitTests
7+
[Test]
8+
public void EnsureBoots()
109
{
11-
[TestMethod]
12-
public void EnsureBoots()
13-
{
14-
var are = new AutoResetEvent(false);
15-
On.Terraria.Main.hook_DedServ cb = (On.Terraria.Main.orig_DedServ orig, Terraria.Main instance) =>
16-
{
17-
are.Set();
18-
Debug.WriteLine("Server startup successful");
19-
};
20-
On.Terraria.Main.DedServ += cb;
21-
22-
new Thread(() => TerrariaApi.Server.Program.Main(new string[] { })).Start();
23-
24-
var hit = are.WaitOne(TimeSpan.FromSeconds(30));
25-
26-
On.Terraria.Main.DedServ -= cb;
27-
28-
Assert.AreEqual(true, hit);
29-
}
10+
EnsureInitialised();
3011
}
3112
}
Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,33 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
5-
<Nullable>enable</Nullable>
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<Nullable>enable</Nullable>
66

7-
<IsPackable>false</IsPackable>
8-
</PropertyGroup>
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
12+
<PackageReference Include="NUnit" Version="3.13.3" />
13+
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
14+
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
15+
<PackageReference Include="coverlet.collector" Version="3.1.2">
16+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
17+
<PrivateAssets>all</PrivateAssets>
18+
</PackageReference>
19+
</ItemGroup>
920

10-
<ItemGroup>
11-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
12-
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
13-
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
14-
<PackageReference Include="coverlet.collector" Version="3.1.2"><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
15-
<PrivateAssets>all</PrivateAssets>
16-
</PackageReference>
17-
</ItemGroup>
21+
<ItemGroup>
22+
<ProjectReference Include="..\TerrariaServerAPI\TerrariaServerAPI.csproj" />
23+
<!--<ProjectReference Include="..\external\Tiled-master\Tiled\Tiled.csproj" Name="Tiled" /> retained here since the name prop is important -->
24+
</ItemGroup>
1825

19-
<ItemGroup>
20-
<ProjectReference Include="..\TerrariaServerAPI\TerrariaServerAPI.csproj" />
21-
</ItemGroup>
26+
<ItemGroup Condition="@(ProjectReference-&gt;AnyHaveMetadataValue('Name', 'Tiled'))">
27+
<_DefineConstants Include="TILED_PLUGIN" />
28+
</ItemGroup>
29+
<PropertyGroup>
30+
<DefineConstants>$(DefineConstants);@(_DefineConstants)</DefineConstants>
31+
</PropertyGroup>
2232

2333
</Project>
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using ModFramework;
2+
using NUnit.Framework;
3+
using System;
4+
using System.Diagnostics;
5+
using Terraria;
6+
using Terraria.Utilities;
7+
using TerrariaApi.Server;
8+
9+
namespace TerrariaServerAPI.Tests;
10+
11+
public class TileBenchmarks : BaseTest
12+
{
13+
[TestCase(10)]
14+
public void Clearing_Stock(int cycles) => ClearWorld(() => new DefaultCollection<ITile>(Main.maxTilesX, Main.maxTilesY), cycles);
15+
16+
[TestCase(10)]
17+
public void Clearing_Heap(int cycles) => ClearWorld(() => new TileProvider(), cycles);
18+
19+
[TestCase(10)]
20+
public void Clearing_Constileation(int cycles) => ClearWorld(() => new ConstileationProvider(), cycles);
21+
22+
#if TILED_PLUGIN
23+
[TestCase(10)]
24+
public void Clearing_1d(int cycles) => ClearWorld(() => new Tiled.OneDimension.OneDimensionTileProvider(), cycles);
25+
26+
[TestCase(10)]
27+
public void Clearing_2d(int cycles) => ClearWorld(() => new Tiled.TwoDimensions.TwoDimensionTileProvider(), cycles);
28+
29+
[TestCase(10)]
30+
public void Clearing_Struct(int cycles) => ClearWorld(() => new Tiled.Struct.Structured1DTileProvider(), cycles);
31+
#endif
32+
33+
public void ClearWorld<T>(Func<T> requestProvider, int cycles)
34+
where T : ICollection<ITile>
35+
{
36+
SetWorldSmall();
37+
Main.tile = requestProvider();
38+
Console.WriteLine($"{Main.tile.GetType().Name}: Clearing world with {cycles} cycle(s)...");
39+
40+
var sw = new Stopwatch();
41+
sw.Start();
42+
for (var i = 0; i < cycles; i++)
43+
WorldGen.clearWorld();
44+
45+
sw.Stop();
46+
47+
Console.WriteLine($"{Main.tile.GetType().Name}: Clear took: {sw.Elapsed.TotalSeconds:#.00}s ({sw.Elapsed.TotalMilliseconds / cycles:#.00}ms p/c)");
48+
}
49+
50+
void SetWorldSmall()
51+
{
52+
Main.maxTilesX = 4200;
53+
Main.maxTilesY = 1200;
54+
}
55+
56+
public void Generate_Small<T>(Func<T> requestProvider)
57+
where T : ICollection<ITile>
58+
{
59+
SetWorldSmall();
60+
Main.tile = requestProvider();
61+
Console.WriteLine($"{Main.tile.GetType().Name}: Generate starting...");
62+
WorldGen.clearWorld();
63+
64+
var sw = new Stopwatch();
65+
sw.Start();
66+
CreateNewWorld();
67+
sw.Stop();
68+
69+
Console.WriteLine($"{Main.tile.GetType().Name}: Generate took: {sw.Elapsed.TotalSeconds:#.00}s");
70+
}
71+
72+
[Test]
73+
public void Generate_Small_Stock()
74+
=> Generate_Small(() => new DefaultCollection<ITile>(Main.maxTilesX, Main.maxTilesY));
75+
76+
[Test]
77+
public void Generate_Small_Heap()
78+
=> Generate_Small(() => new TileProvider());
79+
80+
[Test]
81+
public void Generate_Small_Constileation()
82+
=> Generate_Small(() => new ConstileationProvider());
83+
84+
#if TILED_PLUGIN
85+
[Test]
86+
public void Generate_Small_1d()
87+
=> Generate_Small(() => new Tiled.OneDimension.OneDimensionTileProvider());
88+
89+
[Test]
90+
public void Generate_Small_2d()
91+
=> Generate_Small(() => new Tiled.TwoDimensions.TwoDimensionTileProvider());
92+
93+
[Test]
94+
public void Generate_Small_Struct()
95+
=> Generate_Small(() => new Tiled.Struct.Structured1DTileProvider());
96+
#endif
97+
98+
static void CreateNewWorld()
99+
{
100+
WorldGen.generatingWorld = true;
101+
Main.rand = new UnifiedRandom(9999);
102+
WorldGen.gen = true;
103+
Main.menuMode = 888;
104+
WorldGen.GenerateWorld(9999);
105+
}
106+
}

TerrariaServerAPI/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
[assembly: ComVisible(false)]
88
[assembly: Guid("f571b16a-2c9b-44ab-b115-7c762c9e4e7e")]
99
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
10+
[assembly: InternalsVisibleTo("TerrariaServerAPI.Tests")]
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
using System.Runtime.CompilerServices;
2+
using System.Runtime.InteropServices;
3+
using Terraria;
4+
5+
namespace TerrariaApi.Server;
6+
7+
/// <summary>
8+
/// Cons"tile"ation provider is a tile adapter that will function similar to heaptile to reduce memory, but instead of using math to determine offsets, a TileData structure is used instead which already knows the layout.
9+
/// </summary>
10+
internal class ConstileationProvider : ModFramework.ICollection<ITile>
11+
{
12+
private TileData[] data = null;
13+
14+
public int Width => Main.maxTilesX + 1;
15+
public int Height => Main.maxTilesY + 1;
16+
17+
public ITile this[int x, int y]
18+
{
19+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
20+
get
21+
{
22+
data ??= new TileData[Width * Height];
23+
return new TileReference(ref data[Main.maxTilesY * x + y]);
24+
}
25+
26+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
27+
set => new TileReference(ref data[Main.maxTilesY * x + y]).CopyFrom(value);
28+
}
29+
}
30+
31+
/// <summary>
32+
/// Maps tile data to memory in an organised format
33+
/// </summary>
34+
/// <remarks>When updating this, refer to <see cref="ITile"/> properties, and adjust the Size accordingly. Size is likely not required, but it make it clear how many bytes we expect to consume</remarks>
35+
[StructLayout(LayoutKind.Sequential, Size = 14, Pack = 1)]
36+
internal struct TileData
37+
{
38+
public ushort type;
39+
public ushort wall;
40+
public byte liquid;
41+
public ushort sTileHeader;
42+
public byte bTileHeader;
43+
public byte bTileHeader2;
44+
public byte bTileHeader3;
45+
public short frameX;
46+
public short frameY;
47+
}
48+
49+
/// <summary>
50+
/// This class is intended to be issued back and forth between Terraria, while referring to a preallocated memory mapping of the tile data which avoids duplicating data and storing offsets.
51+
/// </summary>
52+
internal unsafe sealed class TileReference : Tile
53+
{
54+
public override ushort type
55+
{
56+
get => _tile->type;
57+
set => _tile->type = value;
58+
}
59+
60+
public override ushort wall
61+
{
62+
get => _tile->wall;
63+
set => _tile->wall = value;
64+
}
65+
66+
public override byte liquid
67+
{
68+
get => _tile->liquid;
69+
set => _tile->liquid = value;
70+
}
71+
72+
public override ushort sTileHeader
73+
{
74+
get => _tile->sTileHeader;
75+
set => _tile->sTileHeader = value;
76+
}
77+
78+
public override byte bTileHeader
79+
{
80+
get => _tile->bTileHeader;
81+
set => _tile->bTileHeader = value;
82+
}
83+
84+
public override byte bTileHeader2
85+
{
86+
get => _tile->bTileHeader2;
87+
set => _tile->bTileHeader2 = value;
88+
}
89+
90+
public override byte bTileHeader3
91+
{
92+
get => _tile->bTileHeader3;
93+
set => _tile->bTileHeader3 = value;
94+
}
95+
96+
public override short frameX
97+
{
98+
get => _tile->frameX;
99+
set => _tile->frameX = value;
100+
}
101+
102+
public override short frameY
103+
{
104+
get => _tile->frameY;
105+
set => _tile->frameY = value;
106+
}
107+
108+
public override void Initialise()
109+
{
110+
// this is called in ctor. we dont want to run the default code here.
111+
}
112+
113+
readonly TileData* _tile;
114+
115+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
116+
public TileReference(ref TileData tile)
117+
{
118+
_tile = (TileData*)Unsafe.AsPointer(ref tile);
119+
}
120+
}

TerrariaServerAPI/TerrariaApi.Server/HookManager.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ public void AttachOTAPIHooks(string[] args)
4545
ServerApi.LogWriter.ServerWriteLine($"Using {nameof(HeapTile)} for tile implementation", TraceLevel.Info);
4646
Terraria.Main.tile = new TileProvider();
4747
}
48+
if (args.Any(x => x == "-constileation" || x == "-c"))
49+
{
50+
ServerApi.LogWriter.ServerWriteLine($"Using {nameof(ConstileationProvider)} for tile implementation", TraceLevel.Info);
51+
Main.tile = new ConstileationProvider();
52+
}
4853

4954
Hooking.GameHooks.AttachTo(this);
5055
Hooking.ItemHooks.AttachTo(this);

0 commit comments

Comments
 (0)