mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
381 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2694dce076 | ||
|
|
8960d1d995 | ||
|
|
0a4683d33e | ||
|
|
379bcfabe0 | ||
|
|
1d91838166 | ||
|
|
a5d4b8096f | ||
|
|
a77eee5658 | ||
|
|
156187a0dd | ||
|
|
852f002f59 | ||
|
|
9dc49c1904 | ||
|
|
1995b13e5d | ||
|
|
f985d10ed9 | ||
|
|
ae6cebbfbb | ||
|
|
ef0bc1a2e4 | ||
|
|
72ba484f5b | ||
|
|
a70e511fcb | ||
|
|
e7f9e95525 | ||
|
|
bd908f9db6 | ||
|
|
f8cb1729a3 | ||
|
|
fd9d5c8aa8 | ||
|
|
4677296934 | ||
|
|
708f5dd376 | ||
|
|
4a06acda32 | ||
|
|
e7beb2032b | ||
|
|
c7bd75f800 | ||
|
|
b4165e8661 | ||
|
|
ad339b5bfd | ||
|
|
e1197af8ce | ||
|
|
102cadf3a6 | ||
|
|
e7723b61bc | ||
|
|
a9d17337a3 | ||
|
|
74622bac83 | ||
|
|
a3047b1687 | ||
|
|
3a55118143 | ||
|
|
3c5fbc648a | ||
|
|
f9c39bce0b | ||
|
|
0e8c803c0f | ||
|
|
6bb7b88c69 | ||
|
|
9e0fc7017c | ||
|
|
76317b7ab3 | ||
|
|
d5f4d4bf2f | ||
|
|
156d1a6b14 | ||
|
|
3fa456fd44 | ||
|
|
bf9bb46154 | ||
|
|
fb3da0b53c | ||
|
|
5c635c09b4 | ||
|
|
fad539212d | ||
|
|
7275302639 | ||
|
|
5057ff97a3 | ||
|
|
754d5a1fbb | ||
|
|
7cee5b67a7 | ||
|
|
21729e7e48 | ||
|
|
394d1e6cc2 | ||
|
|
f73d7f7285 | ||
|
|
e505cfffd8 | ||
|
|
d0fe3591ef | ||
|
|
1868f32457 | ||
|
|
ca82767b07 | ||
|
|
76024330a7 | ||
|
|
21e8107eb1 | ||
|
|
bcaa97a79b | ||
|
|
19727f6a25 | ||
|
|
2102b96323 | ||
|
|
59b3ffda4f | ||
|
|
aae929966c | ||
|
|
0cf842cacc | ||
|
|
96885c5b53 | ||
|
|
d45ce7742e | ||
|
|
50e27fd204 | ||
|
|
af98933173 | ||
|
|
e357dada65 | ||
|
|
19c48862e2 | ||
|
|
448ce94b35 | ||
|
|
3681b7f0d5 | ||
|
|
7b3c883653 | ||
|
|
c81004ddb4 | ||
|
|
f844011348 | ||
|
|
0094040d68 | ||
|
|
dfb5369664 | ||
|
|
2462c906b3 | ||
|
|
8cbc05840f | ||
|
|
510846321d | ||
|
|
ac60567583 | ||
|
|
7592997f4e | ||
|
|
c68b3dccb7 | ||
|
|
da2a2ce4ff | ||
|
|
538418ea93 | ||
|
|
2bf284bce8 | ||
|
|
b51cb06d53 | ||
|
|
bab6c29fbe | ||
|
|
9502c86a65 | ||
|
|
359811f71e | ||
|
|
9de5840017 | ||
|
|
45af00096f | ||
|
|
2f0283edb7 | ||
|
|
d142393221 | ||
|
|
ac147dc2a1 | ||
|
|
16af5cff9c | ||
|
|
7725dbff78 | ||
|
|
f2f8824678 | ||
|
|
f2e9ed0b73 | ||
|
|
9e6d0aa44a | ||
|
|
1758fced75 | ||
|
|
31e2a3efe4 | ||
|
|
371db7db2f | ||
|
|
bb23bc6acc | ||
|
|
270326e188 | ||
|
|
7d27835543 | ||
|
|
cfb88b2e8e | ||
|
|
8a83787d58 | ||
|
|
6dd9f9e0f5 | ||
|
|
7f3445b1c6 | ||
|
|
5c15f26f09 | ||
|
|
b8fbe5e465 | ||
|
|
32049e34f2 | ||
|
|
26b09283f3 | ||
|
|
6c6360e50a | ||
|
|
9877323195 | ||
|
|
b6f52f4c27 | ||
|
|
c1789cbbaf | ||
|
|
f44f5b5a98 | ||
|
|
f4faa1ad3d | ||
|
|
97d03c6954 | ||
|
|
8accbc700a | ||
|
|
9f013534b3 | ||
|
|
0b8febf6a6 | ||
|
|
188985ecc2 | ||
|
|
42434d1f49 | ||
|
|
87492cb0c3 | ||
|
|
517ae7f1bd | ||
|
|
73357f022b | ||
|
|
012faa0a16 | ||
|
|
7a8abb3db7 | ||
|
|
d6ce7e950b | ||
|
|
e39b249070 | ||
|
|
5b889936be | ||
|
|
712809195d | ||
|
|
64b5d6e323 | ||
|
|
baa607532d | ||
|
|
ff064dd859 | ||
|
|
8305bffcac | ||
|
|
17a8972052 | ||
|
|
56e03eae3e | ||
|
|
1f948e17c4 | ||
|
|
efaed42b24 | ||
|
|
be2e31ff9d | ||
|
|
a891cacae5 | ||
|
|
e9e0117402 | ||
|
|
24114d87e6 | ||
|
|
7cd78f3f4e | ||
|
|
ca64aae7f0 | ||
|
|
be0fb4250c | ||
|
|
eba58cb893 | ||
|
|
ced6d5a8b0 | ||
|
|
495671576e | ||
|
|
0611674915 | ||
|
|
e6ab61fe42 | ||
|
|
5232e61d38 | ||
|
|
a3e90aa04e | ||
|
|
7a719d9f61 | ||
|
|
61385b7c21 | ||
|
|
2503f9c4e0 | ||
|
|
eb092e90ef | ||
|
|
eb556c8728 | ||
|
|
2e398ded08 | ||
|
|
75cf15caa2 | ||
|
|
8d7d6a26ba | ||
|
|
d593ffbb47 | ||
|
|
15b377dbd6 | ||
|
|
d212479689 | ||
|
|
2d3379d7f4 | ||
|
|
7b171b2212 | ||
|
|
7e8a5e199f | ||
|
|
ae74a5d7d4 | ||
|
|
9446ab76f9 | ||
|
|
bb5cb10d57 | ||
|
|
d6ccaee6d4 | ||
|
|
ce8982e371 | ||
|
|
371cd7ddf1 | ||
|
|
6573531d5d | ||
|
|
b1e1f27aa3 | ||
|
|
80ce583454 | ||
|
|
2b4a428f9f | ||
|
|
41ee330828 | ||
|
|
f82452c855 | ||
|
|
785e2f84f6 | ||
|
|
2bdc1d77ca | ||
|
|
51205beb56 | ||
|
|
ef325b4780 | ||
|
|
ede337a869 | ||
|
|
d416344aef | ||
|
|
fb98eb1a0c | ||
|
|
ed9a0b4812 | ||
|
|
e7c6151310 | ||
|
|
005c2e784a | ||
|
|
1009dd3ea0 | ||
|
|
87b82160b0 | ||
|
|
fc318c9ecd | ||
|
|
ac957ca7fc | ||
|
|
1bdd82b0bf | ||
|
|
0150c5e6ff | ||
|
|
b2cc90d00f | ||
|
|
1f8b89e92f | ||
|
|
33d394295e | ||
|
|
4934a9c5a5 | ||
|
|
91ebc3eb02 | ||
|
|
bc84590a33 | ||
|
|
e3944dc6fb | ||
|
|
6a77f4c27b | ||
|
|
6246ae412e | ||
|
|
ac86accc20 | ||
|
|
19ff7f25ca | ||
|
|
3f83733a03 | ||
|
|
438fed2f0e | ||
|
|
816a535a92 | ||
|
|
01df42aa8f | ||
|
|
a200d73ef9 | ||
|
|
8d30735ffb | ||
|
|
2686150f9d | ||
|
|
d720e9393b | ||
|
|
f5b1c26bec | ||
|
|
3204002c72 | ||
|
|
4c79d0c6d0 | ||
|
|
e0d38fb8bd | ||
|
|
250f6ca7db | ||
|
|
773365c185 | ||
|
|
9a1e6af586 | ||
|
|
dc96318379 | ||
|
|
31a3f145de | ||
|
|
331e1fcc81 | ||
|
|
dc7a51e582 | ||
|
|
bfe8e687da | ||
|
|
04b6d60d76 | ||
|
|
5bc5bfd58a | ||
|
|
56899b4e64 | ||
|
|
a23915e0dd | ||
|
|
726d91c5e8 | ||
|
|
d8a8783680 | ||
|
|
8839dd9a3b | ||
|
|
0296d9635c | ||
|
|
f24d9751d4 | ||
|
|
58ac82ae55 | ||
|
|
b9130bf236 | ||
|
|
a2cd33afe5 | ||
|
|
1772651049 | ||
|
|
826fa4d131 | ||
|
|
0b712ae86c | ||
|
|
22528fc484 | ||
|
|
20a411e6ce | ||
|
|
6f9ed8a242 | ||
|
|
790f4c1309 | ||
|
|
0b62cb6445 | ||
|
|
9d0f4d8a08 | ||
|
|
5069b0ccf9 | ||
|
|
12cfdb2175 | ||
|
|
b5e079815d | ||
|
|
ca3a3279c5 | ||
|
|
003752a161 | ||
|
|
55e51cba9c | ||
|
|
2cd829f4f6 | ||
|
|
43138669ec | ||
|
|
3fe30bc00f | ||
|
|
3ccbdeac6a | ||
|
|
9eb9c91da6 | ||
|
|
28d2b47a2c | ||
|
|
049ffa05e4 | ||
|
|
2f36a0a5fc | ||
|
|
41d03db59d | ||
|
|
68df887a65 | ||
|
|
e0bbcd7b08 | ||
|
|
525815427e | ||
|
|
70224ac100 | ||
|
|
dabb090dc2 | ||
|
|
ace8334a3e | ||
|
|
d8e70b4d52 | ||
|
|
2fca0e03ee | ||
|
|
b6980964b6 | ||
|
|
34d02256fd | ||
|
|
34637fb430 | ||
|
|
d905ef2a50 | ||
|
|
c3f7ef1b5c | ||
|
|
ced2a5c6cd | ||
|
|
9ec927543f | ||
|
|
215fc8c229 | ||
|
|
f82ff9e581 | ||
|
|
906f4598a2 | ||
|
|
0eb3c37bd8 | ||
|
|
9cc7cf80ba | ||
|
|
7ba02b5ca6 | ||
|
|
3ca7121f5b | ||
|
|
d3b31c1d58 | ||
|
|
92b5bb4660 | ||
|
|
33caf9c1ba | ||
|
|
58da8a6001 | ||
|
|
962f5dc650 | ||
|
|
c324562513 | ||
|
|
f5a2a710f0 | ||
|
|
357283e2bc | ||
|
|
5991bfa106 | ||
|
|
4160b120e0 | ||
|
|
98a1fa1fba | ||
|
|
fb08451849 | ||
|
|
ebea0d7572 | ||
|
|
eb6f28cce0 | ||
|
|
a1d02d7c55 | ||
|
|
777ab85cff | ||
|
|
d33a8465b0 | ||
|
|
6572fdb404 | ||
|
|
6273b1b80d | ||
|
|
a09a60efe9 | ||
|
|
d3339964ee | ||
|
|
3ffef625ec | ||
|
|
4fd9b2bc3b | ||
|
|
adc5051841 | ||
|
|
2733435218 | ||
|
|
24b0165ec9 | ||
|
|
7b9aa09b18 | ||
|
|
7bee6f6fc1 | ||
|
|
ff75495894 | ||
|
|
4cb51af733 | ||
|
|
89c1e90646 | ||
|
|
b6cadfedd5 | ||
|
|
9f57b705d7 | ||
|
|
68be9712ad | ||
|
|
aaa446254c | ||
|
|
5e2d2ab317 | ||
|
|
20ae63fbbd | ||
|
|
a92c0cbef4 | ||
|
|
95649a2dd0 | ||
|
|
861807f8b4 | ||
|
|
bd73f1c05a | ||
|
|
7dce51e2cf | ||
|
|
d9b0f3a227 | ||
|
|
05766a2eaa | ||
|
|
a761fbc09e | ||
|
|
f69440b3f2 | ||
|
|
b459d2ce21 | ||
|
|
202182e3d4 | ||
|
|
96cb52e5d2 | ||
|
|
82e0c0baeb | ||
|
|
54d6552164 | ||
|
|
c21b6c993c | ||
|
|
2fe4a8b859 | ||
|
|
8325966dbb | ||
|
|
2459a9d688 | ||
|
|
2cd2d1edd6 | ||
|
|
b982350851 | ||
|
|
4a50bc2154 | ||
|
|
4c85e205b9 | ||
|
|
0b447d9d82 | ||
|
|
ceb205ad52 | ||
|
|
a48ff3dbf1 | ||
|
|
2b85fa88c1 | ||
|
|
19564a421b | ||
|
|
b3f0e467ee | ||
|
|
216292c849 | ||
|
|
68753d15e0 | ||
|
|
2a357051ae | ||
|
|
58e0b62145 | ||
|
|
14cc273997 | ||
|
|
93f4428635 | ||
|
|
164bf68aca | ||
|
|
773b87672b | ||
|
|
eecf834039 | ||
|
|
325fe46aa3 | ||
|
|
2f6c29ab43 | ||
|
|
aab1a2dba9 | ||
|
|
f36fbd9c83 | ||
|
|
126c863f45 | ||
|
|
618a8491bf | ||
|
|
2743b64a2b | ||
|
|
28cc91934c | ||
|
|
eadfcd4c09 | ||
|
|
7871b0010e | ||
|
|
3da04ed17e | ||
|
|
170d192791 | ||
|
|
dcd9939554 | ||
|
|
98ef58eca6 | ||
|
|
ab1e99a0df | ||
|
|
499c236798 | ||
|
|
8dc2345ceb |
@@ -10,5 +10,8 @@ charset = utf-8
|
||||
[*.{csproj,xml,yml,dll.config,targets,props}]
|
||||
indent_size = 2
|
||||
|
||||
[nuget.config]
|
||||
indent_size = 2
|
||||
|
||||
[*.gdsl]
|
||||
indent_style = tab
|
||||
|
||||
2
.github/workflows/build-docfx.yml
vendored
2
.github/workflows/build-docfx.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
||||
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
dotnet-version: 8.0.x
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
|
||||
2
.github/workflows/publish-client.yml
vendored
2
.github/workflows/publish-client.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Package client
|
||||
run: Tools/package_client_build.py -p windows mac linux
|
||||
|
||||
2
.github/workflows/test-content.yml
vendored
2
.github/workflows/test-content.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
dotnet-version: 8.0.x
|
||||
- name: Disable submodule autoupdate
|
||||
run: touch BuildChecker/DISABLE_SUBMODULE_AUTOUPDATE
|
||||
|
||||
|
||||
64
Directory.Packages.props
Normal file
64
Directory.Packages.props
Normal file
@@ -0,0 +1,64 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageVersion Include="ILReader.Core" Version="1.0.0.4" />
|
||||
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
|
||||
<PackageVersion Include="JetBrains.Profiler.Api" Version="1.4.0" />
|
||||
<PackageVersion Include="Linguini.Bundle" Version="0.1.3" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1"/>
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit" Version="1.1.1"/>
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeCoverage" Version="17.8.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.DotNet.RemoteExecutor" Version="8.0.0-beta.24059.4" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Primitives" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.ILVerification" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
|
||||
<PackageVersion Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageVersion Include="Moq" Version="4.20.70" />
|
||||
<PackageVersion Include="NUnit" Version="4.0.1" />
|
||||
<PackageVersion Include="NUnit.Analyzers" Version="3.10.0" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||
<PackageVersion Include="Nett" Version="0.15.0" />
|
||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
|
||||
<PackageVersion Include="Pidgin" Version="3.2.2" />
|
||||
<PackageVersion Include="Robust.Natives" Version="0.1.1" />
|
||||
<PackageVersion Include="Robust.Natives.Cef" Version="120.1.9" />
|
||||
<PackageVersion Include="Robust.Shared.AuthLib" Version="0.1.2" />
|
||||
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.7" />
|
||||
<PackageVersion Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.7" />
|
||||
<PackageVersion Include="Serilog" Version="3.1.1" />
|
||||
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.2" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
<PackageVersion Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
<PackageVersion Include="System.Memory" Version="4.5.5" />
|
||||
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.22621.5" />
|
||||
<PackageVersion Include="TerraFX.Interop.Xlib" Version="6.4.0" />
|
||||
<PackageVersion Include="VorbisPizza" Version="1.3.0" />
|
||||
<PackageVersion Include="YamlDotNet" Version="13.7.1" />
|
||||
<PackageVersion Include="prometheus-net" Version="8.2.1" />
|
||||
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
|
||||
<PackageVersion Include="PolySharp" Version="1.14.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// So I wanted to mess with NetIncomingMessage and NetOutgoingMessage from tests.
|
||||
// Now.. the instructors are internal...
|
||||
// Unless...
|
||||
// I mean we have this project here from the weird way we're compiling Lidgren.
|
||||
// I could just put this in here... it wouldn't touch the main Lidgren repo at all...
|
||||
[assembly: InternalsVisibleTo("Robust.UnitTesting")]
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: f19cea8010...45f89ca263
@@ -10,9 +10,14 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
<SkipRobustAnalyzer>true</SkipRobustAnalyzer>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="CursedHorrorsBeyondOurWildestImagination.cs" />
|
||||
|
||||
<Compile Include="Lidgren.Network\Lidgren.Network\**\*.cs">
|
||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
</Compile>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project>
|
||||
<!-- Engine-specific properties. Content should not use this file. -->
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<LangVersion>11</LangVersion>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<!-- Import this at the end of any project files in Robust and Content. -->
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="Robust.Custom.targets" Condition="Exists('Robust.Custom.targets')"/>
|
||||
@@ -27,5 +28,5 @@
|
||||
<Import Project="Robust.Analyzers.targets" Condition="'$(SkipRobustAnalyzer)' != 'true'" />
|
||||
|
||||
<!-- serialization generator -->
|
||||
<Import Project="Robust.Serialization.Generator.targets" />
|
||||
<Import Project="Robust.Serialization.Generator.targets" Condition="'$(SkipRobustAnalyzer)' != 'true'" />
|
||||
</Project>
|
||||
|
||||
Submodule NetSerializer updated: 7224829e87...7f51deaeca
@@ -61,18 +61,5 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GLFWException"/> class with the specified context
|
||||
/// and the serialization information.
|
||||
/// </summary>
|
||||
/// <param name="info">The <see cref="SerializationInfo"/> associated with this exception.</param>
|
||||
/// <param name="context">
|
||||
/// A <see cref="StreamingContext"/> that represents the context of this exception.
|
||||
/// </param>
|
||||
protected GLFWException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1016
RELEASE-NOTES.md
1016
RELEASE-NOTES.md
File diff suppressed because it is too large
Load Diff
@@ -12,3 +12,8 @@
|
||||
id: bgra
|
||||
kind: source
|
||||
path: "/Shaders/Internal/bgra.swsl"
|
||||
|
||||
- type: shader
|
||||
id: ColorPicker
|
||||
kind: source
|
||||
path: "/Shaders/color_picker.swsl"
|
||||
|
||||
@@ -9,6 +9,7 @@ cmd-parse-failure-float = {$arg} is not a valid float.
|
||||
cmd-parse-failure-bool = {$arg} is not a valid bool.
|
||||
cmd-parse-failure-uid = {$arg} is not a valid entity UID.
|
||||
cmd-parse-failure-mapid = {$arg} is not a valid MapId.
|
||||
cmd-parse-failure-grid = {$arg} is not a valid grid.
|
||||
cmd-parse-failure-entity-exist = UID {$arg} does not correspond to an existing entity.
|
||||
|
||||
cmd-error-file-not-found = Could not find file: {$file}.
|
||||
@@ -561,3 +562,7 @@ cmd-vfs_ls-hint-path = <path>
|
||||
|
||||
cmd-reloadtiletextures-desc = Reloads the tile texture atlas to allow hot reloading tile sprites
|
||||
cmd-reloadtiletextures-help = Usage: reloadtiletextures
|
||||
|
||||
cmd-audio_length-desc = Shows the length of an audio file
|
||||
cmd-audio_length-help = Usage: audio_length { cmd-audio_length-arg-file-name }
|
||||
cmd-audio_length-arg-file-name = <file name>
|
||||
|
||||
8
Resources/Locale/en-US/physics/grid_merging.ftl
Normal file
8
Resources/Locale/en-US/physics/grid_merging.ftl
Normal file
@@ -0,0 +1,8 @@
|
||||
cmd-merge_grids-desc = Combines 2 grids into 1 grid
|
||||
cmd-merge_grids-help = merge_grids <gridUid1> <gridUid2> <offsetX> <offsetY> [angle]
|
||||
|
||||
cmd-merge_grids-hintA = Grid A
|
||||
cmd-merge_grids-hintB = Grid B
|
||||
cmd-merge_grids-xOffset = X offset
|
||||
cmd-merge_grids-yOffset = Y offset
|
||||
cmd-merge_grids-angle = [Angle]
|
||||
46
Resources/Shaders/color_picker.swsl
Normal file
46
Resources/Shaders/color_picker.swsl
Normal file
@@ -0,0 +1,46 @@
|
||||
// Simple shader for creating a box with colours varying along the x and y axes.
|
||||
|
||||
uniform highp vec2 size;
|
||||
uniform highp vec2 offset;
|
||||
|
||||
uniform highp vec4 xAxis;
|
||||
uniform highp vec4 yAxis;
|
||||
uniform highp vec4 baseColor;
|
||||
|
||||
uniform bool hsv;
|
||||
|
||||
void fragment()
|
||||
{
|
||||
// Calculate local uv coordinates.
|
||||
// I.e., if using this shader to draw a box to the screen, (0,0) is the bottom left of the box.
|
||||
|
||||
highp float yCoords = 1.0/SCREEN_PIXEL_SIZE.y - FRAGCOORD.y;
|
||||
highp vec2 uv = vec2(FRAGCOORD.x - offset.x, yCoords - offset.y);
|
||||
uv /= size;
|
||||
uv.y = 1.0 - uv.y;
|
||||
|
||||
highp vec4 modulate = baseColor + uv.x * xAxis + uv.y * yAxis;
|
||||
|
||||
if (hsv)
|
||||
{
|
||||
modulate.xyz = hsv2rgb(modulate.xyz);
|
||||
}
|
||||
|
||||
// The UV used for the texture lookup is the TEXTURE UV coordinate, which is different from the coordinates computed above.
|
||||
COLOR = zTexture(UV) * modulate;
|
||||
}
|
||||
|
||||
|
||||
// hsv to RGB conversion taken from www.shadertoy.com/view/MsS3Wc
|
||||
|
||||
// The MIT License
|
||||
// Copyright © 2014 Inigo Quilez
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// https://www.youtube.com/c/InigoQuilez
|
||||
// https://iquilezles.org
|
||||
|
||||
highp vec3 hsv2rgb( in highp vec3 c )
|
||||
{
|
||||
highp vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );
|
||||
return c.z * mix( vec3(1.0), rgb, c.y);
|
||||
}
|
||||
8
Robust.Analyzers.Tests/Aliases.cs
Normal file
8
Robust.Analyzers.Tests/Aliases.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
// OH BOY. TURNS OUT IT GETS EVEN MORE CURSED.
|
||||
//
|
||||
// So because we're compiling a copy of Robust.Roslyn.Shared into every analyzer project,
|
||||
// the test project sees multiple copies of it. This would make it impossible to use.
|
||||
// UNLESS you use this obscure C# feature called "extern alias"
|
||||
// that I guarantee you you've never heard of before, and are now concerned about.
|
||||
|
||||
extern alias SerializationGenerator;
|
||||
340
Robust.Analyzers.Tests/ComponentPauseGeneratorTest.cs
Normal file
340
Robust.Analyzers.Tests/ComponentPauseGeneratorTest.cs
Normal file
@@ -0,0 +1,340 @@
|
||||
extern alias SerializationGenerator;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using NUnit.Framework;
|
||||
using SerializationGenerator::Robust.Roslyn.Shared;
|
||||
using SerializationGenerator::Robust.Serialization.Generator;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(ComponentPauseGenerator))]
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
public sealed class ComponentPauseGeneratorTest
|
||||
{
|
||||
private const string TypesCode = """
|
||||
global using System;
|
||||
global using Robust.Shared.Analyzers;
|
||||
global using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Analyzers
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public sealed class AutoGenerateComponentPauseAttribute : Attribute
|
||||
{
|
||||
public bool Dirty = false;
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public sealed class AutoPausedFieldAttribute : Attribute;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public sealed class AutoNetworkedFieldAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public interface IComponent;
|
||||
}
|
||||
""";
|
||||
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
[AutoPausedField]
|
||||
public TimeSpan Foo;
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoDiagnostics(result);
|
||||
ExpectSource(
|
||||
result,
|
||||
"""
|
||||
// <auto-generated />
|
||||
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
|
||||
}
|
||||
|
||||
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
component.Foo += args.PausedTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNullable()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
[AutoPausedField]
|
||||
public TimeSpan? Foo;
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoDiagnostics(result);
|
||||
ExpectSource(
|
||||
result,
|
||||
"""
|
||||
// <auto-generated />
|
||||
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
|
||||
}
|
||||
|
||||
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
if (component.Foo.HasValue)
|
||||
component.Foo = component.Foo.Value + args.PausedTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAutoState()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
[AutoPausedField, AutoNetworkedField]
|
||||
public TimeSpan Foo;
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoDiagnostics(result);
|
||||
ExpectSource(
|
||||
result,
|
||||
"""
|
||||
// <auto-generated />
|
||||
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
|
||||
}
|
||||
|
||||
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
component.Foo += args.PausedTime;
|
||||
Dirty(uid, component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExplicitDirty()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause(Dirty = true)]
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
[AutoPausedField]
|
||||
public TimeSpan Foo;
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoDiagnostics(result);
|
||||
ExpectSource(
|
||||
result,
|
||||
"""
|
||||
// <auto-generated />
|
||||
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
|
||||
}
|
||||
|
||||
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
component.Foo += args.PausedTime;
|
||||
Dirty(uid, component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDiagnosticNotIComponent()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class FooComponent
|
||||
{
|
||||
[AutoPausedField]
|
||||
public TimeSpan Foo;
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoSource(result);
|
||||
ExpectDiagnostics(result, [
|
||||
(Diagnostics.IdComponentPauseNotComponent, new LinePositionSpan(new LinePosition(1, 28), new LinePosition(1, 40)))
|
||||
]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDiagnosticNoFields()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
public TimeSpan Foo;
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoSource(result);
|
||||
ExpectDiagnostics(result, [
|
||||
(Diagnostics.IdComponentPauseNoFields, new LinePositionSpan(new LinePosition(1, 28), new LinePosition(1, 40)))
|
||||
]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDiagnosticNoParentAttribute()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
[AutoPausedField]
|
||||
public TimeSpan Foo, Fooz;
|
||||
|
||||
[AutoPausedField]
|
||||
public TimeSpan Bar { get; set; }
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoSource(result);
|
||||
ExpectDiagnostics(result, [
|
||||
(Diagnostics.IdComponentPauseNoParentAttribute, new LinePositionSpan(new LinePosition(3, 20), new LinePosition(3, 23))),
|
||||
(Diagnostics.IdComponentPauseNoParentAttribute, new LinePositionSpan(new LinePosition(3, 25), new LinePosition(3, 29))),
|
||||
(Diagnostics.IdComponentPauseNoParentAttribute, new LinePositionSpan(new LinePosition(6, 20), new LinePosition(6, 23)))
|
||||
]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDiagnosticWrongType()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
[AutoPausedField]
|
||||
public int Foo, Fooz;
|
||||
|
||||
[AutoPausedField]
|
||||
public int Bar { get; set; }
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoSource(result);
|
||||
ExpectDiagnostics(result, [
|
||||
(Diagnostics.IdComponentPauseWrongTypeAttribute, new LinePositionSpan(new LinePosition(4, 15), new LinePosition(4, 18))),
|
||||
(Diagnostics.IdComponentPauseWrongTypeAttribute, new LinePositionSpan(new LinePosition(4, 20), new LinePosition(4, 24))),
|
||||
(Diagnostics.IdComponentPauseWrongTypeAttribute, new LinePositionSpan(new LinePosition(7, 15), new LinePosition(7, 18)))
|
||||
]);
|
||||
}
|
||||
|
||||
private static void ExpectSource(GeneratorRunResult result, string expected)
|
||||
{
|
||||
Assert.That(result.GeneratedSources, Has.Length.EqualTo(1));
|
||||
|
||||
var source = result.GeneratedSources[0];
|
||||
|
||||
Assert.That(source.SourceText.ToString(), Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
private static void ExpectNoSource(GeneratorRunResult result)
|
||||
{
|
||||
Assert.That(result.GeneratedSources, Is.Empty);
|
||||
}
|
||||
|
||||
private static void ExpectNoDiagnostics(GeneratorRunResult result)
|
||||
{
|
||||
Assert.That(result.Diagnostics, Is.Empty);
|
||||
}
|
||||
|
||||
private static void ExpectDiagnostics(GeneratorRunResult result, (string code, LinePositionSpan span)[] diagnostics)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(result.Diagnostics, Has.Length.EqualTo(diagnostics.Length));
|
||||
foreach (var (code, span) in diagnostics)
|
||||
{
|
||||
Assert.That(
|
||||
result.Diagnostics.Any(x => x.Id == code && x.Location.GetLineSpan().Span == span),
|
||||
$"Expected diagnostic with code {code} and location {span}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static GeneratorRunResult RunGenerator(string source)
|
||||
{
|
||||
var compilation = (Compilation)CSharpCompilation.Create("compilation",
|
||||
new[]
|
||||
{
|
||||
CSharpSyntaxTree.ParseText(source, path: "Source.cs"),
|
||||
CSharpSyntaxTree.ParseText(TypesCode, path: "Types.cs")
|
||||
},
|
||||
new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) },
|
||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
|
||||
var generator = new ComponentPauseGenerator();
|
||||
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
|
||||
driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out _);
|
||||
var result = driver.GetRunResult();
|
||||
|
||||
return result.Results[0];
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\MSBuild\Robust.Engine.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<SkipRobustAnalyzer>true</SkipRobustAnalyzer>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets"/>
|
||||
<Import Project="..\MSBuild\Robust.Engine.props"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit" Version="1.1.1"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1"/>
|
||||
<PackageReference Include="NUnit" Version="3.13.2"/>
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.15.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.3.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageVersion Update="NUnit" Version="3.14.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzer.Testing"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces"/>
|
||||
<PackageReference Include="NUnit"/>
|
||||
<PackageReference Include="NUnit3TestAdapter"/>
|
||||
<PackageReference Include="NUnit.Analyzers"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Robust.Analyzers\Robust.Analyzers.csproj"/>
|
||||
<ProjectReference Include="..\Robust.Serialization.Generator\Robust.Serialization.Generator.csproj" Aliases="SerializationGenerator" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
using Robust.Shared.Analyzers.Implementation;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
using static Microsoft.CodeAnalysis.SymbolEqualityComparer;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
@@ -16,7 +17,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor ByRefEventSubscribedByValueRule = new(
|
||||
Diagnostics.IdByRefEventSubscribedByValue,
|
||||
"By-ref event subscribed to by value",
|
||||
"Tried to subscribe to a by-ref event '{0}' by value.",
|
||||
"Tried to subscribe to a by-ref event '{0}' by value",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
@@ -26,7 +27,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor ByRefEventRaisedByValueRule = new(
|
||||
Diagnostics.IdByRefEventRaisedByValue,
|
||||
"By-ref event raised by value",
|
||||
"Tried to raise a by-ref event '{0}' by value.",
|
||||
"Tried to raise a by-ref event '{0}' by value",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
@@ -36,7 +37,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor ByValueEventRaisedByRefRule = new(
|
||||
Diagnostics.IdValueEventRaisedByRef,
|
||||
"Value event raised by-ref",
|
||||
"Tried to raise a value event '{0}' by-ref.",
|
||||
"Tried to raise a value event '{0}' by-ref",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
@@ -18,7 +19,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
|
||||
Diagnostics.IdDataDefinitionPartial,
|
||||
"Type must be partial",
|
||||
"Type {0} is a DataDefinition but is not partial.",
|
||||
"Type {0} is a DataDefinition but is not partial",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
@@ -28,7 +29,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor NestedDataDefinitionPartialRule = new(
|
||||
Diagnostics.IdNestedDataDefinitionPartial,
|
||||
"Type must be partial",
|
||||
"Type {0} contains nested data definition {1} but is not partial.",
|
||||
"Type {0} contains nested data definition {1} but is not partial",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
@@ -38,7 +39,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor DataFieldWritableRule = new(
|
||||
Diagnostics.IdDataFieldWritable,
|
||||
"Data field must not be readonly",
|
||||
"Data field {0} in data definition {1} is readonly.",
|
||||
"Data field {0} in data definition {1} is readonly",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
@@ -48,7 +49,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
|
||||
Diagnostics.IdDataFieldPropertyWritable,
|
||||
"Data field property must have a setter",
|
||||
"Data field property {0} in data definition {1} does not have a setter.",
|
||||
"Data field property {0} in data definition {1} does not have a setter",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
|
||||
@@ -9,7 +9,7 @@ using Microsoft.CodeAnalysis.CodeFixes;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using static Microsoft.CodeAnalysis.CSharp.SyntaxKind;
|
||||
using static Robust.Analyzers.Diagnostics;
|
||||
using static Robust.Roslyn.Shared.Diagnostics;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CodeFixes;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
using Document = Microsoft.CodeAnalysis.Document;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
@@ -31,7 +32,7 @@ public sealed class NotNullableFlagAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
private static readonly DiagnosticDescriptor InvalidNotNullableImplementationRule = new (
|
||||
Diagnostics.IdInvalidNotNullableFlagImplementation,
|
||||
"Invalid NotNullable flag implementation.",
|
||||
"Invalid NotNullable flag implementation",
|
||||
"NotNullable flag is either not typed as bool, or does not have a default value equaling false",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
@@ -41,7 +42,7 @@ public sealed class NotNullableFlagAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor InvalidNotNullableTypeRule = new (
|
||||
Diagnostics.IdInvalidNotNullableFlagType,
|
||||
"Failed to resolve type parameter",
|
||||
"Failed to resolve type parameter \"{0}\".",
|
||||
"Failed to resolve type parameter \"{0}\"",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
@@ -49,7 +50,7 @@ public sealed class NotNullableFlagAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
private static readonly DiagnosticDescriptor NotNullableFlagValueTypeRule = new (
|
||||
Diagnostics.IdNotNullableFlagValueType,
|
||||
"NotNullable flag not supported for value types.",
|
||||
"NotNullable flag not supported for value types",
|
||||
"Value types as generic arguments are not supported for NotNullable flags",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
|
||||
@@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>10</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Needed for NotNullableFlagAnalyzer. -->
|
||||
<Compile Include="..\Robust.Shared\Analyzers\NotNullableFlagAttribute.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Needed for FriendAnalyzer. -->
|
||||
<Compile Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" />
|
||||
@@ -27,4 +16,10 @@
|
||||
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CodeFixes;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using BenchmarkDotNet.Analysers;
|
||||
using BenchmarkDotNet.Columns;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using BenchmarkDotNet.EventProcessors;
|
||||
using BenchmarkDotNet.Exporters;
|
||||
using BenchmarkDotNet.Filters;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using BenchmarkDotNet.Order;
|
||||
using BenchmarkDotNet.Reports;
|
||||
using BenchmarkDotNet.Running;
|
||||
using BenchmarkDotNet.Validators;
|
||||
using Robust.Benchmarks.Exporters;
|
||||
|
||||
@@ -44,10 +47,16 @@ public sealed class DefaultSQLConfig : IConfig
|
||||
|
||||
public IEnumerable<BenchmarkLogicalGroupRule> GetLogicalGroupRules() => DefaultConfig.Instance.GetLogicalGroupRules();
|
||||
|
||||
public IEnumerable<EventProcessor> GetEventProcessors() => DefaultConfig.Instance.GetEventProcessors();
|
||||
|
||||
public IEnumerable<IColumnHidingRule> GetColumnHidingRules() => DefaultConfig.Instance.GetColumnHidingRules();
|
||||
public IOrderer Orderer => DefaultConfig.Instance.Orderer!;
|
||||
public ICategoryDiscoverer? CategoryDiscoverer => DefaultConfig.Instance.CategoryDiscoverer;
|
||||
public SummaryStyle SummaryStyle => DefaultConfig.Instance.SummaryStyle;
|
||||
public ConfigUnionRule UnionRule => DefaultConfig.Instance.UnionRule;
|
||||
public string ArtifactsPath => DefaultConfig.Instance.ArtifactsPath;
|
||||
public CultureInfo CultureInfo => DefaultConfig.Instance.CultureInfo!;
|
||||
public ConfigOptions Options => DefaultConfig.Instance.Options;
|
||||
public TimeSpan BuildTimeout => DefaultConfig.Instance.BuildTimeout;
|
||||
public IReadOnlyList<Conclusion> ConfigAnalysisConclusion => DefaultConfig.Instance.ConfigAnalysisConclusion;
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
<ProjectReference Include="..\Robust.UnitTesting\Robust.UnitTesting.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
|
||||
<PackageReference Include="BenchmarkDotNet" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Server.GameStates;
|
||||
@@ -11,7 +12,9 @@ using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.UnitTesting.Server;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.UnitTesting;
|
||||
|
||||
namespace Robust.Benchmarks.Transform;
|
||||
|
||||
@@ -19,114 +22,177 @@ namespace Robust.Benchmarks.Transform;
|
||||
/// This benchmark tests various transform/move related functions with an entity that has many children.
|
||||
/// </summary>
|
||||
[Virtual, MemoryDiagnoser]
|
||||
public class RecursiveMoveBenchmark
|
||||
public class RecursiveMoveBenchmark : RobustIntegrationTest
|
||||
{
|
||||
private ISimulation _simulation = default!;
|
||||
private IEntityManager _entMan = default!;
|
||||
private SharedTransformSystem _transform = default!;
|
||||
private ContainerSystem _container = default!;
|
||||
private PvsSystem _pvs = default!;
|
||||
private EntityCoordinates _mapCoords;
|
||||
private EntityCoordinates _gridCoords;
|
||||
private EntityCoordinates _gridCoords2;
|
||||
private EntityUid _ent;
|
||||
private EntityUid _child;
|
||||
private TransformComponent _childXform = default!;
|
||||
private EntityQuery<TransformComponent> _query;
|
||||
private ICommonSession[] _players = default!;
|
||||
private PvsSession _session = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_simulation = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.InitializeInstance();
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
var server = StartServer();
|
||||
var client = StartClient();
|
||||
|
||||
if (!_simulation.Resolve<IConfigurationManager>().GetCVar(CVars.NetPVS))
|
||||
throw new InvalidOperationException("PVS must be enabled");
|
||||
Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()).Wait();
|
||||
|
||||
var mapMan = server.ResolveDependency<IMapManager>();
|
||||
_entMan = server.ResolveDependency<IEntityManager>();
|
||||
var confMan = server.ResolveDependency<IConfigurationManager>();
|
||||
var sPlayerMan = server.ResolveDependency<ISharedPlayerManager>();
|
||||
|
||||
_entMan = _simulation.Resolve<IEntityManager>();
|
||||
_transform = _entMan.System<SharedTransformSystem>();
|
||||
_container = _entMan.System<ContainerSystem>();
|
||||
_pvs = _entMan.System<PvsSystem>();
|
||||
_query = _entMan.GetEntityQuery<TransformComponent>();
|
||||
|
||||
// Create map & grid
|
||||
var mapMan = _simulation.Resolve<IMapManager>();
|
||||
var mapSys = _entMan.System<SharedMapSystem>();
|
||||
var mapId = mapMan.CreateMap();
|
||||
var map = mapMan.GetMapEntityId(mapId);
|
||||
var gridComp = mapMan.CreateGridEntity(mapId);
|
||||
var grid = gridComp.Owner;
|
||||
_gridCoords = new EntityCoordinates(grid, .5f, .5f);
|
||||
_mapCoords = new EntityCoordinates(map, 100, 100);
|
||||
mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1));
|
||||
|
||||
// Next, we will spawn our test entity. This entity will have a complex transform/container hierarchy.
|
||||
// This is intended to be representative of a typical SS14 player entity, with organs. clothing, and a full backpack.
|
||||
_ent = _entMan.Spawn();
|
||||
var netMan = client.ResolveDependency<IClientNetManager>();
|
||||
client.SetConnectTarget(server);
|
||||
client.Post(() => netMan.ClientConnect(null!, 0, null!));
|
||||
server.Post(() => confMan.SetCVar(CVars.NetPVS, true));
|
||||
|
||||
// Quick check that SetCoordinates actually changes the parent as expected
|
||||
// I.e., ensure that grid-traversal code doesn't just dump the entity on the map.
|
||||
_transform.SetCoordinates(_ent, _gridCoords);
|
||||
if (_query.GetComponent(_ent).ParentUid != _gridCoords.EntityId)
|
||||
throw new Exception("Grid traversal error.");
|
||||
|
||||
_transform.SetCoordinates(_ent, _mapCoords);
|
||||
if (_query.GetComponent(_ent).ParentUid != _mapCoords.EntityId)
|
||||
throw new Exception("Grid traversal error.");
|
||||
|
||||
// Add 5 direct children in slots to represent clothing.
|
||||
for (var i = 0; i < 5; i++)
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var id = $"inventory{i}";
|
||||
_container.EnsureContainer<ContainerSlot>(_ent, id);
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, id, out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
server.WaitRunTicks(1).Wait();
|
||||
client.WaitRunTicks(1).Wait();
|
||||
}
|
||||
|
||||
// body parts
|
||||
_container.EnsureContainer<Container>(_ent, "body");
|
||||
for (var i = 0; i < 5; i++)
|
||||
// Ensure client & server ticks are synced.
|
||||
// Client runs 1 tick ahead
|
||||
{
|
||||
// Simple organ
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
var sTick = (int)server.Timing.CurTick.Value;
|
||||
var cTick = (int)client.Timing.CurTick.Value;
|
||||
var delta = cTick - sTick;
|
||||
|
||||
// body part that has another body part / limb
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out var limb))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
if (delta > 1)
|
||||
server.WaitRunTicks(delta - 1).Wait();
|
||||
else if (delta < 1)
|
||||
client.WaitRunTicks(1 - delta).Wait();
|
||||
|
||||
_container.EnsureContainer<ContainerSlot>(limb.Value, "limb");
|
||||
if (!_entMan.TrySpawnInContainer(null, limb.Value, "limb", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
sTick = (int)server.Timing.CurTick.Value;
|
||||
cTick = (int)client.Timing.CurTick.Value;
|
||||
delta = cTick - sTick;
|
||||
if (delta != 1)
|
||||
throw new Exception("Failed setup");
|
||||
}
|
||||
|
||||
// Backpack
|
||||
_container.EnsureContainer<ContainerSlot>(_ent, "inventory-backpack");
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "inventory-backpack", out var backpack))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
|
||||
// Misc backpack contents.
|
||||
var backpackStorage = _container.EnsureContainer<Container>(backpack.Value, "storage");
|
||||
for (var i = 0; i < 10; i++)
|
||||
// Set up map and spawn player
|
||||
server.WaitPost(() =>
|
||||
{
|
||||
if (!_entMan.TrySpawnInContainer(null, backpack.Value, "storage", out _))
|
||||
var mapId = mapMan.CreateMap();
|
||||
var map = mapMan.GetMapEntityId(mapId);
|
||||
var gridComp = mapMan.CreateGridEntity(mapId);
|
||||
var grid = gridComp.Owner;
|
||||
mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1));
|
||||
_gridCoords = new EntityCoordinates(grid, .5f, .5f);
|
||||
_gridCoords2 = new EntityCoordinates(grid, .5f, .6f);
|
||||
_mapCoords = new EntityCoordinates(map, 100, 100);
|
||||
|
||||
var playerUid = _entMan.SpawnEntity(null, _mapCoords);
|
||||
|
||||
// Attach player.
|
||||
var session = sPlayerMan.Sessions.First();
|
||||
server.PlayerMan.SetAttachedEntity(session, playerUid);
|
||||
sPlayerMan.JoinGame(session);
|
||||
|
||||
// Next, we will spawn our test entity. This entity will have a complex transform/container hierarchy.
|
||||
// This is intended to be representative of a typical SS14 player entity, with organs. clothing, and a full backpack.
|
||||
_ent = _entMan.Spawn();
|
||||
|
||||
// Quick check that SetCoordinates actually changes the parent as expected
|
||||
// I.e., ensure that grid-traversal code doesn't just dump the entity on the map.
|
||||
_transform.SetCoordinates(_ent, _gridCoords);
|
||||
if (_query.GetComponent(_ent).ParentUid != _gridCoords.EntityId)
|
||||
throw new Exception("Grid traversal error.");
|
||||
|
||||
_transform.SetCoordinates(_ent, _mapCoords);
|
||||
if (_query.GetComponent(_ent).ParentUid != _mapCoords.EntityId)
|
||||
throw new Exception("Grid traversal error.");
|
||||
|
||||
// Add 5 direct children in slots to represent clothing.
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var id = $"inventory{i}";
|
||||
_container.EnsureContainer<ContainerSlot>(_ent, id);
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, id, out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
}
|
||||
|
||||
// body parts
|
||||
_container.EnsureContainer<Container>(_ent, "body");
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
// Simple organ
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
|
||||
// body part that has another body part / limb
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out var limb))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
|
||||
_container.EnsureContainer<ContainerSlot>(limb.Value, "limb");
|
||||
if (!_entMan.TrySpawnInContainer(null, limb.Value, "limb", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
}
|
||||
|
||||
// Backpack
|
||||
_container.EnsureContainer<ContainerSlot>(_ent, "inventory-backpack");
|
||||
if (!_entMan.TrySpawnInContainer(null, _ent, "inventory-backpack", out var backpack))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
|
||||
// Misc backpack contents.
|
||||
var backpackStorage = _container.EnsureContainer<Container>(backpack.Value, "storage");
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
if (!_entMan.TrySpawnInContainer(null, backpack.Value, "storage", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
}
|
||||
|
||||
// Emergency box inside of the backpack
|
||||
var box = backpackStorage.ContainedEntities.First();
|
||||
var boxContainer = _container.EnsureContainer<Container>(box, "storage");
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
if (!_entMan.TrySpawnInContainer(null, box, "storage", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
}
|
||||
|
||||
// Deepest child.
|
||||
_child = boxContainer.ContainedEntities.First();
|
||||
_childXform = _query.GetComponent(_child);
|
||||
|
||||
_players = new[] {session};
|
||||
_session = _pvs.PlayerData[session];
|
||||
}).Wait();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
server.WaitRunTicks(1).Wait();
|
||||
client.WaitRunTicks(1).Wait();
|
||||
}
|
||||
|
||||
// Emergency box inside of the backpack
|
||||
var box = backpackStorage.ContainedEntities.First();
|
||||
var boxContainer = _container.EnsureContainer<Container>(box, "storage");
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
if (!_entMan.TrySpawnInContainer(null, box, "storage", out _))
|
||||
throw new Exception($"Failed to setup entity");
|
||||
}
|
||||
PvsTick();
|
||||
PvsTick();
|
||||
}
|
||||
|
||||
// Deepest child.
|
||||
_child = boxContainer.ContainedEntities.First();
|
||||
_childXform = _query.GetComponent(_child);
|
||||
|
||||
_pvs.ProcessCollections();
|
||||
private void PvsTick()
|
||||
{
|
||||
_session.ClearState();
|
||||
_pvs.CacheSessionData(_players);
|
||||
_pvs.GetVisibleChunks();
|
||||
_pvs.ProcessVisibleChunksSequential();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -140,6 +206,13 @@ public class RecursiveMoveBenchmark
|
||||
_transform.SetCoordinates(_ent, _mapCoords);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void MoveEntityASmidge()
|
||||
{
|
||||
_transform.SetCoordinates(_ent, _gridCoords);
|
||||
_transform.SetCoordinates(_ent, _gridCoords2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="MoveEntity"/>, but also processes queued PVS chunk updates.
|
||||
/// </summary>
|
||||
@@ -147,9 +220,18 @@ public class RecursiveMoveBenchmark
|
||||
public void MoveAndUpdateChunks()
|
||||
{
|
||||
_transform.SetCoordinates(_ent, _gridCoords);
|
||||
_pvs.ProcessCollections();
|
||||
PvsTick();
|
||||
_transform.SetCoordinates(_ent, _mapCoords);
|
||||
_pvs.ProcessCollections();
|
||||
PvsTick();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void MoveASmidgeAndUpdateChunk()
|
||||
{
|
||||
_transform.SetCoordinates(_ent, _gridCoords);
|
||||
PvsTick();
|
||||
_transform.SetCoordinates(_ent, _gridCoords2);
|
||||
PvsTick();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build.Framework" Version="17.0.0" />
|
||||
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
|
||||
<PackageReference Include="Microsoft.Build.Framework" Version="17.8.3" />
|
||||
<PackageReference Include="Mono.Cecil" Version="0.11.5" />
|
||||
<PackageReference Include="Pidgin" Version="2.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Link="XamlX\filename" Include="../XamlX/src/XamlX/**/*.cs" />
|
||||
<Compile Remove="../XamlX/src/XamlX/**/SreTypeSystem.cs" />
|
||||
<Compile Remove="../XamlX/src/XamlX/obj/**" />
|
||||
<Compile Include="..\Robust.Client\UserInterface\ControlPropertyAccess.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- XamlX doesn't do NRTs. -->
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
85
Robust.Client.WebView/Cef/BaseRobustCefClient.cs
Normal file
85
Robust.Client.WebView/Cef/BaseRobustCefClient.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef;
|
||||
|
||||
/// <summary>
|
||||
/// Shared functionality for all <see cref="CefClient"/> implementations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Locks down a bunch of CEF functionality we absolutely do not need content to do right now.
|
||||
/// </remarks>
|
||||
internal abstract class BaseRobustCefClient : CefClient
|
||||
{
|
||||
private readonly RobustCefPrintHandler _printHandler = new();
|
||||
private readonly RobustCefPermissionHandler _permissionHandler = new();
|
||||
private readonly RobustCefDialogHandler _dialogHandler = new();
|
||||
private readonly RobustCefDragHandler _dragHandler = new();
|
||||
|
||||
protected override CefPrintHandler GetPrintHandler() => _printHandler;
|
||||
protected override CefPermissionHandler GetPermissionHandler() => _permissionHandler;
|
||||
protected override CefDialogHandler GetDialogHandler() => _dialogHandler;
|
||||
protected override CefDragHandler GetDragHandler() => _dragHandler;
|
||||
|
||||
private sealed class RobustCefPrintHandler : CefPrintHandler
|
||||
{
|
||||
protected override void OnPrintSettings(CefBrowser browser, CefPrintSettings settings, bool getDefaults)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool OnPrintDialog(CefBrowser browser, bool hasSelection, CefPrintDialogCallback callback)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool OnPrintJob(CefBrowser browser, string documentName, string pdfFilePath, CefPrintJobCallback callback)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnPrintReset(CefBrowser browser)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RobustCefPermissionHandler : CefPermissionHandler
|
||||
{
|
||||
protected override bool OnRequestMediaAccessPermission(
|
||||
CefBrowser browser,
|
||||
CefFrame frame,
|
||||
string requestingOrigin,
|
||||
CefMediaAccessPermissionTypes requestedPermissions,
|
||||
CefMediaAccessCallback callback)
|
||||
{
|
||||
callback.Cancel();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RobustCefDialogHandler : CefDialogHandler
|
||||
{
|
||||
protected override bool OnFileDialog(
|
||||
CefBrowser browser,
|
||||
CefFileDialogMode mode,
|
||||
string title,
|
||||
string defaultFilePath,
|
||||
string[] acceptFilters,
|
||||
CefFileDialogCallback callback)
|
||||
{
|
||||
callback.Cancel();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RobustCefDragHandler : CefDragHandler
|
||||
{
|
||||
protected override bool OnDragEnter(CefBrowser browser, CefDragData dragData, CefDragOperationsMask mask)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnDraggableRegionsChanged(CefBrowser browser, CefFrame frame, CefDraggableRegion[] regions)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
@@ -19,6 +22,8 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
var mainArgs = new CefMainArgs(argv);
|
||||
|
||||
StartWatchThread();
|
||||
|
||||
// This will block executing until the subprocess is shut down.
|
||||
var code = CefRuntime.ExecuteProcess(mainArgs, new RobustCefApp(null), IntPtr.Zero);
|
||||
|
||||
@@ -29,5 +34,44 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
private static void StartWatchThread()
|
||||
{
|
||||
//
|
||||
// CEF has this nasty habit of not shutting down all its processes if the parent crashes.
|
||||
// Great!
|
||||
//
|
||||
// We use a separate thread in each CEF child process to watch the main PID.
|
||||
// If it exits, we kill ourselves after a couple seconds.
|
||||
//
|
||||
|
||||
if (Environment.GetEnvironmentVariable("ROBUST_CEF_BROWSER_PROCESS_ID") is not { } parentIdString)
|
||||
return;
|
||||
|
||||
if (Environment.GetEnvironmentVariable("ROBUST_CEF_BROWSER_PROCESS_MODULE") is not { } parentModuleString)
|
||||
return;
|
||||
|
||||
if (!int.TryParse(parentIdString, CultureInfo.InvariantCulture, out var parentId))
|
||||
return;
|
||||
|
||||
var process = Process.GetProcessById(parentId);
|
||||
if ((process.MainModule?.FileName ?? "") != parentModuleString)
|
||||
{
|
||||
process.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread(() => WatchThread(process)) { Name = "CEF Watch Thread", IsBackground = true }
|
||||
.Start();
|
||||
}
|
||||
|
||||
private static void WatchThread(Process p)
|
||||
{
|
||||
p.WaitForExit();
|
||||
|
||||
Thread.Sleep(3000);
|
||||
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,12 +76,9 @@ namespace Robust.Client.WebView.Cef
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override unsafe bool Read(IntPtr dataOut, int bytesToRead, out int bytesRead, CefResourceReadCallback callback)
|
||||
protected override bool Read(Span<byte> response, out int bytesRead, CefResourceReadCallback callback)
|
||||
{
|
||||
var byteSpan = new Span<byte>((void*) dataOut, bytesToRead);
|
||||
|
||||
bytesRead = _stream.Read(byteSpan);
|
||||
|
||||
bytesRead = _stream.Read(response);
|
||||
return bytesRead != 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,12 @@ namespace Robust.Client.WebView.Cef
|
||||
// Disable zygote on Linux.
|
||||
commandLine.AppendSwitch("--no-zygote");
|
||||
|
||||
// Work around https://bitbucket.org/chromiumembedded/cef/issues/3213/ozone-egl-initialization-does-not-have
|
||||
// Work around https://github.com/chromiumembedded/cef/issues/3213
|
||||
// Desktop GL force makes Chromium not try to load its own ANGLE/Swiftshader so load paths aren't problematic.
|
||||
if (OperatingSystem.IsLinux())
|
||||
commandLine.AppendSwitch("--use-gl", "desktop");
|
||||
// UPDATE: That bug got fixed and now this workaround breaks CEF.
|
||||
// Keeping all this comment history in case I ever wanan remember what the `--use-gl` flag is.
|
||||
//if (OperatingSystem.IsLinux())
|
||||
// commandLine.AppendSwitch("--use-gl", "desktop");
|
||||
|
||||
// commandLine.AppendSwitch("--single-process");
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using Xilium.CefGlue;
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
// Simple CEF client.
|
||||
internal sealed class RobustCefClient : CefClient
|
||||
internal sealed class RobustCefClient : BaseRobustCefClient
|
||||
{
|
||||
private readonly CefRenderHandler _renderHandler;
|
||||
private readonly CefRequestHandler _requestHandler;
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace Robust.Client.WebView.Cef
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WindowCefClient : CefClient
|
||||
private sealed class WindowCefClient : BaseRobustCefClient
|
||||
{
|
||||
private readonly CefLifeSpanHandler _lifeSpanHandler;
|
||||
private readonly CefRequestHandler _requestHandler;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
@@ -85,6 +86,10 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
_app = new RobustCefApp(_sawmill);
|
||||
|
||||
var process = Process.GetCurrentProcess();
|
||||
Environment.SetEnvironmentVariable("ROBUST_CEF_BROWSER_PROCESS_ID", process.Id.ToString());
|
||||
Environment.SetEnvironmentVariable("ROBUST_CEF_BROWSER_PROCESS_MODULE", process.MainModule?.FileName ?? "");
|
||||
|
||||
// So these arguments look like nonsense, but it turns out CEF is just *like that*.
|
||||
// The first argument is literally nonsense, but it needs to be there as otherwise the second argument doesn't apply
|
||||
// The second argument turns off CEF's bullshit error handling, which breaks dotnet's error handling.
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
|
||||
<PackageReference Include="Robust.Natives.Cef" Version="102.0.9" />
|
||||
<PackageReference Include="JetBrains.Annotations" />
|
||||
<PackageReference Include="Robust.Natives.Cef" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
28
Robust.Client.WebView/TestBrowseWindow.cs
Normal file
28
Robust.Client.WebView/TestBrowseWindow.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Robust.Client.WebView;
|
||||
|
||||
internal sealed class TestBrowseWindow : DefaultWindow
|
||||
{
|
||||
protected override Vector2 ContentsMinimumSize => new Vector2(640, 480);
|
||||
|
||||
public TestBrowseWindow()
|
||||
{
|
||||
var wv = new WebViewControl();
|
||||
wv.Url = "https://spacestation14.io";
|
||||
|
||||
Contents.AddChild(wv);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TestBrowseWindowCommand : LocalizedCommands
|
||||
{
|
||||
public override string Command => "test_browse_window";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
new TestBrowseWindow().Open();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Robust.Client.WebView.Headless;
|
||||
using Robust.Client.WebViewHook;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
[assembly: WebViewManagerImpl(typeof(WebViewManager))]
|
||||
@@ -22,6 +23,9 @@ namespace Robust.Client.WebView
|
||||
var cfg = dependencies.Resolve<IConfigurationManagerInternal>();
|
||||
cfg.LoadCVarsFromAssembly(typeof(WebViewManager).Assembly);
|
||||
|
||||
var refl = dependencies.Resolve<IReflectionManager>();
|
||||
refl.LoadAssemblies(typeof(WebViewManager).Assembly);
|
||||
|
||||
dependencies.RegisterInstance<IWebViewManager>(this);
|
||||
dependencies.RegisterInstance<IWebViewManagerInternal>(this);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using OpenTK.Audio.OpenAL;
|
||||
using Robust.Client.Audio.Sources;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.AudioLoading;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -59,6 +60,12 @@ internal partial class AudioManager
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetVelocity(Vector2 velocity)
|
||||
{
|
||||
AL.Listener(ALListener3f.Velocity, velocity.X, velocity.Y, 0f);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
@@ -78,9 +85,9 @@ internal partial class AudioManager
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AudioStream LoadAudioOggVorbis(Stream stream, string? name = null)
|
||||
public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null)
|
||||
{
|
||||
var vorbis = _readOggVorbis(stream);
|
||||
var vorbis = AudioLoaderOgg.LoadAudioData(stream);
|
||||
|
||||
var buffer = AL.GenBuffer();
|
||||
|
||||
@@ -90,11 +97,11 @@ internal partial class AudioManager
|
||||
// check the git history, I originally used libvorbisfile which worked and loaded 16 bit LPCM.
|
||||
if (vorbis.Channels == 1)
|
||||
{
|
||||
format = ALFormat.MonoFloat32Ext;
|
||||
format = ALFormat.Mono16;
|
||||
}
|
||||
else if (vorbis.Channels == 2)
|
||||
{
|
||||
format = ALFormat.StereoFloat32Ext;
|
||||
format = ALFormat.Stereo16;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -103,9 +110,9 @@ internal partial class AudioManager
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (float* ptr = vorbis.Data.Span)
|
||||
fixed (short* ptr = vorbis.Data.Span)
|
||||
{
|
||||
AL.BufferData(buffer, format, (IntPtr) ptr, vorbis.Data.Length * sizeof(float),
|
||||
AL.BufferData(buffer, format, (IntPtr) ptr, vorbis.Data.Length * sizeof(short),
|
||||
(int) vorbis.SampleRate);
|
||||
}
|
||||
}
|
||||
@@ -119,9 +126,9 @@ internal partial class AudioManager
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AudioStream LoadAudioWav(Stream stream, string? name = null)
|
||||
public AudioStream LoadAudioWav(Stream stream, string? name = null)
|
||||
{
|
||||
var wav = _readWav(stream);
|
||||
var wav = AudioLoaderWav.LoadAudioData(stream);
|
||||
|
||||
var buffer = AL.GenBuffer();
|
||||
|
||||
@@ -178,7 +185,7 @@ internal partial class AudioManager
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
|
||||
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
|
||||
{
|
||||
var fmt = channels switch
|
||||
{
|
||||
@@ -207,9 +214,28 @@ internal partial class AudioManager
|
||||
return new AudioStream(handle, length, channels, name);
|
||||
}
|
||||
|
||||
public void SetMasterVolume(float newVolume)
|
||||
public void SetMasterGain(float newGain)
|
||||
{
|
||||
AL.Listener(ALListenerf.Gain, newVolume);
|
||||
if (newGain < 0f)
|
||||
{
|
||||
OpenALSawmill.Error("Tried to set master gain below 0, clamping to 0");
|
||||
AL.Listener(ALListenerf.Gain, 0f);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
#region Platform hack for MacOS
|
||||
// HACK/BUG: Apple's OpenAL implementation has a bug where values of 0f for listener gain don't actually
|
||||
// HACK/BUG: prevent sound playback. Workaround is to cap the minimum gain at a value just above 0.
|
||||
if (OperatingSystem.IsMacOS() && newGain == 0f)
|
||||
{
|
||||
OpenALSawmill.Verbose("Not setting gain to 0 because Apple can't write an OpenAL implementation");
|
||||
AL.Listener(ALListenerf.Gain, float.Epsilon);
|
||||
return;
|
||||
}
|
||||
#endregion Platform hack for MacOS
|
||||
|
||||
AL.Listener(ALListenerf.Gain, newGain);
|
||||
}
|
||||
|
||||
public void SetAttenuation(Attenuation attenuation)
|
||||
@@ -271,25 +297,37 @@ internal partial class AudioManager
|
||||
|
||||
var audioSource = new AudioSource(this, source, stream);
|
||||
_audioSources.Add(source, new WeakReference<BaseAudioSource>(audioSource));
|
||||
ApplyDefaultParams(audioSource);
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
public IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false)
|
||||
/// <inheritdoc/>
|
||||
IBufferedAudioSource? IAudioInternal.CreateBufferedAudioSource(int buffers, bool floatAudio=false)
|
||||
{
|
||||
var source = AL.GenSource();
|
||||
|
||||
if (!AL.IsSource(source))
|
||||
{
|
||||
OpenALSawmill.Error("Failed to generate source. Too many simultaneous audio streams? {0}", Environment.StackTrace);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
|
||||
var audioSource = new BufferedAudioSource(this, source, AL.GenBuffers(buffers), floatAudio);
|
||||
_bufferedAudioSources.Add(source, new WeakReference<BufferedAudioSource>(audioSource));
|
||||
ApplyDefaultParams(audioSource);
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
private void ApplyDefaultParams(IAudioSource source)
|
||||
{
|
||||
source.MaxDistance = AudioParams.Default.MaxDistance;
|
||||
source.Pitch = AudioParams.Default.Pitch;
|
||||
source.ReferenceDistance = AudioParams.Default.ReferenceDistance;
|
||||
source.RolloffFactor = AudioParams.Default.RolloffFactor;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StopAllAudio()
|
||||
{
|
||||
@@ -317,7 +355,6 @@ internal partial class AudioManager
|
||||
{
|
||||
if (source.TryGetTarget(out var target))
|
||||
{
|
||||
target.Playing = false;
|
||||
target.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -328,7 +365,6 @@ internal partial class AudioManager
|
||||
{
|
||||
if (source.TryGetTarget(out var target))
|
||||
{
|
||||
target.Playing = false;
|
||||
target.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
internal sealed partial class AudioManager : SharedAudioManager, IAudioInternal
|
||||
internal sealed partial class AudioManager : IAudioInternal
|
||||
{
|
||||
[Shared.IoC.Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Shared.IoC.Dependency] private readonly ILogManager _logMan = default!;
|
||||
@@ -115,7 +115,7 @@ internal sealed partial class AudioManager : SharedAudioManager, IAudioInternal
|
||||
|
||||
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
|
||||
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterGain, true);
|
||||
}
|
||||
|
||||
internal bool IsMainThread()
|
||||
@@ -140,6 +140,18 @@ internal sealed partial class AudioManager : SharedAudioManager, IAudioInternal
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like _checkAlError but allows custom data to be passed in as relevant.
|
||||
/// </summary>
|
||||
internal void LogALError(string message, [CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = AL.GetError();
|
||||
if (error != ALError.NoError)
|
||||
{
|
||||
OpenALSawmill.Error("[{0}:{1}] AL error: {2}, {3}. Stacktrace is {4}", callerMember, callerLineNumber, error, message, Environment.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
public void _checkAlError([CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = AL.GetError();
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class AudioOverlay : Overlay
|
||||
|
||||
private Font _font;
|
||||
|
||||
public AudioOverlay(IEntityManager entManager, IPlayerManager playerManager, IClientResourceCache cache, AudioSystem audio, SharedTransformSystem transform)
|
||||
public AudioOverlay(IEntityManager entManager, IPlayerManager playerManager, IResourceCache cache, AudioSystem audio, SharedTransformSystem transform)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_playerManager = playerManager;
|
||||
@@ -39,7 +39,7 @@ public sealed class AudioOverlay : Overlay
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var localPlayer = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
var localPlayer = _playerManager.LocalEntity;
|
||||
|
||||
if (args.ViewportControl == null || localPlayer == null)
|
||||
return;
|
||||
@@ -69,7 +69,7 @@ public sealed class AudioOverlay : Overlay
|
||||
|
||||
var screenPos = args.ViewportControl.WorldToScreen(audioPos);
|
||||
var distance = audioPos - listenerPos.Position;
|
||||
var posOcclusion = _audio.GetOcclusion(uid, listenerPos, distance, distance.Length());
|
||||
var posOcclusion = _audio.GetOcclusion(listenerPos, distance, distance.Length(), uid);
|
||||
|
||||
output.Clear();
|
||||
output.AppendLine("Audio Source");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using Robust.Shared.Graphics;
|
||||
|
||||
namespace Robust.Shared.Audio;
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
/// <summary>
|
||||
/// Has the metadata for a particular audio stream as well as the relevant internal handle to it.
|
||||
@@ -2,9 +2,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -21,10 +21,8 @@ using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.ResourceManagement.ResourceTypes;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Utility;
|
||||
using AudioComponent = Robust.Shared.Audio.Components.AudioComponent;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
@@ -35,9 +33,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
* but exposing the whole thing in an easy way is a lot of effort.
|
||||
*/
|
||||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IClientResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IParallelManager _parMan = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
@@ -50,25 +49,52 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
private readonly List<(EntityUid Entity, AudioComponent Component, TransformComponent Xform)> _streams = new();
|
||||
private EntityUid? _listenerGrid;
|
||||
private UpdateAudioJob _updateAudioJob;
|
||||
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
private float _maxRayLength;
|
||||
|
||||
public override float ZOffset
|
||||
{
|
||||
get => _zOffset;
|
||||
protected set
|
||||
{
|
||||
_zOffset = value;
|
||||
_audio.SetZOffset(value);
|
||||
|
||||
var query = AllEntityQuery<AudioComponent>();
|
||||
|
||||
while (query.MoveNext(out var audio))
|
||||
{
|
||||
// Pythagoras back to normal then adjust.
|
||||
var maxDistance = GetAudioDistance(audio.Params.MaxDistance);
|
||||
var refDistance = GetAudioDistance(audio.Params.ReferenceDistance);
|
||||
|
||||
audio.MaxDistance = maxDistance;
|
||||
audio.ReferenceDistance = refDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float _zOffset;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_updateAudioJob = new UpdateAudioJob
|
||||
{
|
||||
System = this,
|
||||
Streams = _streams,
|
||||
};
|
||||
|
||||
UpdatesOutsidePrediction = true;
|
||||
// Need to run after Eye updates so we have an accurate listener position.
|
||||
UpdatesAfter.Add(typeof(EyeSystem));
|
||||
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
SubscribeLocalEvent<AudioComponent, ComponentStartup>(OnAudioStartup);
|
||||
SubscribeLocalEvent<AudioComponent, ComponentShutdown>(OnAudioShutdown);
|
||||
@@ -80,8 +106,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
SubscribeNetworkEvent<PlayAudioEntityMessage>(OnEntityAudio);
|
||||
SubscribeNetworkEvent<PlayAudioPositionalMessage>(OnEntityCoordinates);
|
||||
|
||||
CfgManager.OnValueChanged(CVars.AudioAttenuation, OnAudioAttenuation, true);
|
||||
CfgManager.OnValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioAttenuation, OnAudioAttenuation, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
}
|
||||
|
||||
private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
|
||||
@@ -104,20 +130,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
public void SetMasterVolume(float value)
|
||||
{
|
||||
_audio.SetMasterVolume(value);
|
||||
}
|
||||
|
||||
protected override void SetZOffset(float value)
|
||||
{
|
||||
base.SetZOffset(value);
|
||||
_audio.SetZOffset(value);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
CfgManager.UnsubValueChanged(CVars.AudioAttenuation, OnAudioAttenuation);
|
||||
CfgManager.UnsubValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged);
|
||||
base.Shutdown();
|
||||
_audio.SetMasterGain(value);
|
||||
}
|
||||
|
||||
private void OnAudioPaused(EntityUid uid, AudioComponent component, ref EntityPausedEvent args)
|
||||
@@ -138,38 +151,48 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetAudio(component.FileName, out var audioResource))
|
||||
// Source has already been set
|
||||
if (component.Loaded)
|
||||
{
|
||||
Log.Error($"Error creating audio source for {audioResource}, can't find file {component.FileName}");
|
||||
component.Source = new DummyAudioSource();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetAudio(component.FileName, out var audioResource))
|
||||
{
|
||||
Log.Error($"Error creating audio source for {audioResource}, can't find file {component.FileName}");
|
||||
return;
|
||||
}
|
||||
|
||||
SetupSource(component, audioResource);
|
||||
component.Loaded = true;
|
||||
}
|
||||
|
||||
private void SetupSource(AudioComponent component, AudioResource audioResource, TimeSpan? length = null)
|
||||
{
|
||||
var source = _audio.CreateAudioSource(audioResource);
|
||||
|
||||
if (source == null)
|
||||
{
|
||||
Log.Error($"Error creating audio source for {audioResource}");
|
||||
DebugTools.Assert(false);
|
||||
source = new DummyAudioSource();
|
||||
source = component.Source;
|
||||
}
|
||||
|
||||
// Need to set all initial data for first frame.
|
||||
component.Source = source;
|
||||
|
||||
// Need to set all initial data for first frame.
|
||||
ApplyAudioParams(component.Params, component);
|
||||
component.Global = component.Global;
|
||||
source.Global = component.Global;
|
||||
|
||||
// Don't play until first frame so occlusion etc. are correct.
|
||||
component.Gain = 0f;
|
||||
|
||||
if (!MetaData(uid).EntityPaused)
|
||||
{
|
||||
component.StartPlaying();
|
||||
}
|
||||
length ??= GetAudioLength(component.FileName);
|
||||
|
||||
// If audio came into range then start playback at the correct position.
|
||||
var offset = (Timing.CurTime - component.AudioStart).TotalSeconds % GetAudioLength(component.FileName).TotalSeconds;
|
||||
var offset = (Timing.CurTime - component.AudioStart).TotalSeconds % length.Value.TotalSeconds;
|
||||
|
||||
if (offset != 0)
|
||||
if (offset > 0)
|
||||
{
|
||||
component.PlaybackPosition = (float) offset;
|
||||
}
|
||||
@@ -194,11 +217,19 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
var eye = _eyeManager.CurrentEye;
|
||||
var localEntity = _playerManager.LocalEntity;
|
||||
Vector2 listenerVelocity;
|
||||
|
||||
if (localEntity != null)
|
||||
listenerVelocity = _physics.GetMapLinearVelocity(localEntity.Value);
|
||||
else
|
||||
listenerVelocity = Vector2.Zero;
|
||||
|
||||
_audio.SetVelocity(listenerVelocity);
|
||||
_audio.SetRotation(eye.Rotation);
|
||||
_audio.SetPosition(eye.Position.Position);
|
||||
|
||||
var ourPos = eye.Position;
|
||||
var opts = new ParallelOptions { MaxDegreeOfParallelism = _parMan.ParallelProcessCount };
|
||||
var ourPos = GetListenerCoordinates();
|
||||
|
||||
var query = AllEntityQuery<AudioComponent, TransformComponent>();
|
||||
_streams.Clear();
|
||||
@@ -213,7 +244,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
try
|
||||
{
|
||||
Parallel.ForEach(_streams, opts, comp => ProcessStream(comp.Entity, comp.Component, comp.Xform, ourPos));
|
||||
_updateAudioJob.OurPosition = ourPos;
|
||||
_parMan.ProcessNow(_updateAudioJob, _streams.Count);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -222,11 +254,21 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
}
|
||||
|
||||
public MapCoordinates GetListenerCoordinates()
|
||||
{
|
||||
return _eyeManager.CurrentEye.Position;
|
||||
}
|
||||
|
||||
private void ProcessStream(EntityUid entity, AudioComponent component, TransformComponent xform, MapCoordinates listener)
|
||||
{
|
||||
// TODO:
|
||||
// I Originally tried to be fancier here but it caused audio issues so just trying
|
||||
// to replicate the old behaviour for now.
|
||||
if (!component.Started)
|
||||
{
|
||||
component.Started = true;
|
||||
component.StartPlaying();
|
||||
}
|
||||
|
||||
// If it's global but on another map (that isn't nullspace) then stop playing it.
|
||||
if (component.Global)
|
||||
@@ -254,7 +296,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
var gridUid = xform.ParentUid;
|
||||
|
||||
// Handle grid audio differently by using nearest-edge instead of entity centre.
|
||||
if (_gridQuery.HasComponent(gridUid))
|
||||
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
|
||||
{
|
||||
// It's our grid so max volume.
|
||||
if (_listenerGrid == gridUid)
|
||||
@@ -277,7 +319,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var paramsGain = MathF.Pow(10, component.Params.Volume / 10);
|
||||
var paramsGain = VolumeToGain(component.Params.Volume);
|
||||
|
||||
// Thought I'd never have to manually calculate gain again but this is the least
|
||||
// unpleasant audio I could get at the moment.
|
||||
@@ -310,8 +352,15 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (distance > 0f && distance < 0.01f)
|
||||
{
|
||||
worldPos = listener.Position;
|
||||
delta = Vector2.Zero;
|
||||
distance = 0f;
|
||||
}
|
||||
|
||||
// Update audio occlusion
|
||||
var occlusion = GetOcclusion(entity, listener, delta, distance);
|
||||
var occlusion = GetOcclusion(listener, delta, distance, entity);
|
||||
component.Occlusion = occlusion;
|
||||
|
||||
// Update audio positions.
|
||||
@@ -322,12 +371,15 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
// This actually gets the tracked entity's xform & iterates up though the parents for the second time. Bit
|
||||
// inefficient.
|
||||
var velocity = _physics.GetMapLinearVelocity(entity, physicsComp, xform, _xformQuery, _physicsQuery);
|
||||
var velocity = _physics.GetMapLinearVelocity(entity, physicsComp, xform);
|
||||
component.Velocity = velocity;
|
||||
}
|
||||
}
|
||||
|
||||
internal float GetOcclusion(EntityUid entity, MapCoordinates listener, Vector2 delta, float distance)
|
||||
/// <summary>
|
||||
/// Gets the audio occlusion from the target audio entity to the listener's position.
|
||||
/// </summary>
|
||||
public float GetOcclusion(MapCoordinates listener, Vector2 delta, float distance, EntityUid? ignoredEnt = null)
|
||||
{
|
||||
float occlusion = 0;
|
||||
|
||||
@@ -335,7 +387,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
var rayLength = MathF.Min(distance, _maxRayLength);
|
||||
var ray = new CollisionRay(listener.Position, delta / distance, OcclusionCollisionMask);
|
||||
occlusion = _physics.IntersectRayPenetration(listener.MapId, ray, rayLength, entity);
|
||||
occlusion = _physics.IntersectRayPenetration(listener.MapId, ray, rayLength, ignoredEnt);
|
||||
}
|
||||
|
||||
return occlusion;
|
||||
@@ -350,18 +402,13 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryCreateAudioSource(AudioStream stream, [NotNullWhen(true)] out IAudioSource? source)
|
||||
private bool TryGetAudio(AudioStream stream, [NotNullWhen(true)] out AudioResource? audio)
|
||||
{
|
||||
if (!Timing.IsFirstTimePredicted)
|
||||
{
|
||||
source = null;
|
||||
Log.Error($"Tried to create audio source outside of prediction!");
|
||||
DebugTools.Assert(false);
|
||||
return false;
|
||||
}
|
||||
if (_resourceCache.TryGetResource(stream, out audio))
|
||||
return true;
|
||||
|
||||
source = _audio.CreateAudioSource(stream);
|
||||
return source != null;
|
||||
Log.Error($"Server failed to play audio stream {stream.Title}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates,
|
||||
@@ -378,7 +425,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
if (Timing.IsFirstTimePredicted || sound == null)
|
||||
if (Timing.IsFirstTimePredicted && sound != null)
|
||||
return PlayEntity(sound, Filter.Local(), source, false, audioParams);
|
||||
|
||||
return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio....
|
||||
@@ -386,7 +433,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
if (Timing.IsFirstTimePredicted || sound == null)
|
||||
if (Timing.IsFirstTimePredicted && sound != null)
|
||||
return PlayStatic(sound, Filter.Local(), coordinates, false, audioParams);
|
||||
|
||||
return null;
|
||||
@@ -416,7 +463,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, AudioParams? audioParams = null)
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, AudioParams? audioParams = null)
|
||||
{
|
||||
var (entity, component) = CreateAndStartPlayingStream(audioParams, stream);
|
||||
component.Global = true;
|
||||
@@ -451,8 +498,14 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, AudioParams? audioParams = null)
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(entity))
|
||||
{
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(entity)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(audioParams, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, new EntityCoordinates(entity, Vector2.Zero));
|
||||
|
||||
@@ -486,8 +539,14 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(audioParams, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, coordinates);
|
||||
return playing;
|
||||
@@ -517,6 +576,16 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return PlayGlobal(filename, audioParams);
|
||||
}
|
||||
|
||||
public override void LoadStream<T>(AudioComponent component, T stream)
|
||||
{
|
||||
if (stream is AudioStream audioStream)
|
||||
{
|
||||
TryGetAudio(audioStream, out var audio);
|
||||
SetupSource(component, audio!, audioStream.Length);
|
||||
component.Loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
@@ -551,7 +620,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
var audioP = audioParams ?? AudioParams.Default;
|
||||
var entity = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace);
|
||||
var comp = SetupAudio(entity, stream.Name!, audioP);
|
||||
var comp = SetupAudio(entity, null, audioP, stream.Length);
|
||||
LoadStream(comp, stream);
|
||||
EntityManager.InitializeAndStartEntity(entity);
|
||||
var source = comp.Source;
|
||||
|
||||
@@ -560,8 +630,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
offset = Math.Clamp(offset, 0f, (float) stream.Length.TotalSeconds - 0.01f);
|
||||
source.PlaybackPosition = offset;
|
||||
|
||||
ApplyAudioParams(audioP, comp);
|
||||
comp.Params = audioP;
|
||||
// For server we will rely on the adjusted one but locally we will have to adjust it ourselves.
|
||||
ApplyAudioParams(comp.Params, comp);
|
||||
source.StartPlaying();
|
||||
return (entity, comp);
|
||||
}
|
||||
@@ -574,8 +644,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
source.Pitch = audioParams.Pitch;
|
||||
source.Volume = audioParams.Volume;
|
||||
source.RolloffFactor = audioParams.RolloffFactor;
|
||||
source.MaxDistance = audioParams.MaxDistance;
|
||||
source.ReferenceDistance = audioParams.ReferenceDistance;
|
||||
source.MaxDistance = GetAudioDistance(audioParams.MaxDistance);
|
||||
source.ReferenceDistance = GetAudioDistance(audioParams.ReferenceDistance);
|
||||
source.Looping = audioParams.Loop;
|
||||
}
|
||||
|
||||
@@ -593,4 +663,30 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
PlayGlobal(ev.FileName, ev.AudioParams, false);
|
||||
}
|
||||
|
||||
protected override TimeSpan GetAudioLengthImpl(string filename)
|
||||
{
|
||||
return _resourceCache.GetResource<AudioResource>(filename).AudioStream.Length;
|
||||
}
|
||||
|
||||
#region Jobs
|
||||
|
||||
private record struct UpdateAudioJob : IParallelRobustJob
|
||||
{
|
||||
public int BatchSize => 2;
|
||||
|
||||
public AudioSystem System;
|
||||
|
||||
public MapCoordinates OurPosition;
|
||||
public List<(EntityUid Entity, AudioComponent Component, TransformComponent Xform)> Streams;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
var comp = Streams[index];
|
||||
|
||||
System.ProcessStream(comp.Entity, comp.Component, comp.Xform, OurPosition);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
111
Robust.Client/Audio/HeadlessAudioManager.cs
Normal file
111
Robust.Client/Audio/HeadlessAudioManager.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.AudioLoading;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
/// <summary>
|
||||
/// Headless client audio.
|
||||
/// </summary>
|
||||
internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void InitializePostWindowing()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void FlushALDisposeQueues()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAudioSource CreateAudioSource(AudioStream stream)
|
||||
{
|
||||
return DummyAudioSource.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IBufferedAudioSource? CreateBufferedAudioSource(int buffers, bool floatAudio = false)
|
||||
{
|
||||
return DummyBufferedAudioSource.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetVelocity(Vector2 velocity)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetRotation(Angle angle)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetMasterGain(float newGain)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetAttenuation(Attenuation attenuation)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StopAllAudio()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetZOffset(float f)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void _checkAlError(string callerMember = "", int callerLineNumber = -1)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float GetAttenuationGain(float distance, float rolloffFactor, float referenceDistance, float maxDistance)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null)
|
||||
{
|
||||
var metadata = AudioLoaderOgg.LoadAudioMetadata(stream);
|
||||
return AudioStreamFromMetadata(metadata, name);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioWav(Stream stream, string? name = null)
|
||||
{
|
||||
var metadata = AudioLoaderWav.LoadAudioMetadata(stream);
|
||||
return AudioStreamFromMetadata(metadata, name);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
|
||||
{
|
||||
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
|
||||
return new AudioStream(null, length, channels, name);
|
||||
}
|
||||
|
||||
private static AudioStream AudioStreamFromMetadata(AudioMetadata metadata, string? name)
|
||||
{
|
||||
return new AudioStream(null, metadata.Length, metadata.ChannelCount, name, metadata.Title, metadata.Artist);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Audio;
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
/// <summary>
|
||||
/// Handles clientside audio.
|
||||
/// </summary>
|
||||
internal interface IAudioInternal
|
||||
internal interface IAudioInternal : IAudioManager
|
||||
{
|
||||
void InitializePostWindowing();
|
||||
void Shutdown();
|
||||
@@ -18,9 +21,16 @@ internal interface IAudioInternal
|
||||
/// </summary>
|
||||
void FlushALDisposeQueues();
|
||||
|
||||
IAudioSource? CreateAudioSource(AudioStream stream);
|
||||
/// <summary>
|
||||
/// Returns a buffered audio source.
|
||||
/// </summary>
|
||||
/// <returns>null if unable to create the source.</returns>
|
||||
IBufferedAudioSource? CreateBufferedAudioSource(int buffers, bool floatAudio=false);
|
||||
|
||||
IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false);
|
||||
/// <summary>
|
||||
/// Sets the velocity for the audio listener.
|
||||
/// </summary>
|
||||
void SetVelocity(Vector2 velocity);
|
||||
|
||||
/// <summary>
|
||||
/// Sets position for the audio listener.
|
||||
@@ -32,7 +42,6 @@ internal interface IAudioInternal
|
||||
/// </summary>
|
||||
void SetRotation(Angle angle);
|
||||
|
||||
void SetMasterVolume(float value);
|
||||
void SetAttenuation(Attenuation attenuation);
|
||||
|
||||
/// <summary>
|
||||
22
Robust.Client/Audio/IAudioManager.cs
Normal file
22
Robust.Client/Audio/IAudioManager.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Robust.Client.Audio.Sources;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
/// <summary>
|
||||
/// Public audio API for stuff that can't go through <see cref="AudioSystem"/>
|
||||
/// </summary>
|
||||
public interface IAudioManager
|
||||
{
|
||||
IAudioSource? CreateAudioSource(AudioStream stream);
|
||||
|
||||
AudioStream LoadAudioOggVorbis(Stream stream, string? name = null);
|
||||
|
||||
AudioStream LoadAudioWav(Stream stream, string? name = null);
|
||||
|
||||
AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null);
|
||||
|
||||
void SetMasterGain(float gain);
|
||||
}
|
||||
@@ -17,11 +17,9 @@ public interface IMidiManager
|
||||
bool IsAvailable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Volume, in db.
|
||||
/// Gain of audio.
|
||||
/// </summary>
|
||||
float Volume { get; set; }
|
||||
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
float Gain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This method tries to return a midi renderer ready to be used.
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NFluidsynth;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
@@ -20,7 +18,6 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Threading;
|
||||
@@ -34,14 +31,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
public const string SoundfontEnvironmentVariable = "ROBUST_SOUNDFONT_OVERRIDE";
|
||||
|
||||
private int _minRendererParallel;
|
||||
private float _occlusionUpdateDelay;
|
||||
private float _positionUpdateDelay;
|
||||
|
||||
[ViewVariables] private TimeSpan _nextOcclusionUpdate = TimeSpan.Zero;
|
||||
[ViewVariables] private TimeSpan _nextPositionUpdate = TimeSpan.Zero;
|
||||
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
|
||||
@@ -50,9 +40,10 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
[Dependency] private readonly ILogManager _logger = default!;
|
||||
[Dependency] private readonly IParallelManager _parallel = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtime = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private AudioSystem _audioSys = default!;
|
||||
private SharedPhysicsSystem _broadPhaseSystem = default!;
|
||||
private SharedTransformSystem _xformSystem = default!;
|
||||
|
||||
public IReadOnlyList<IMidiRenderer> Renderers
|
||||
{
|
||||
@@ -79,24 +70,32 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
[ViewVariables] private readonly List<IMidiRenderer> _renderers = new();
|
||||
|
||||
// To avoid lock contention until some kind of MIDI refactor.
|
||||
private TimeSpan _nextUpdate;
|
||||
private TimeSpan _updateFrequency = TimeSpan.FromSeconds(0.25f);
|
||||
|
||||
private SemaphoreSlim _updateSemaphore = new(1);
|
||||
|
||||
private bool _alive = true;
|
||||
[ViewVariables] private Settings? _settings;
|
||||
private Thread? _midiThread;
|
||||
private ISawmill _midiSawmill = default!;
|
||||
private float _volume = 0f;
|
||||
private float _gain = 0f;
|
||||
private bool _volumeDirty = true;
|
||||
|
||||
// Not reliable until Fluidsynth is initialized!
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Volume
|
||||
public float Gain
|
||||
{
|
||||
get => _volume;
|
||||
get => _gain;
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseToPercent(_volume, value))
|
||||
var clamped = Math.Clamp(value, 0f, 1f);
|
||||
|
||||
if (MathHelper.CloseToPercent(_gain, clamped))
|
||||
return;
|
||||
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, value);
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, clamped);
|
||||
_volumeDirty = true;
|
||||
}
|
||||
}
|
||||
@@ -115,7 +114,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
"/usr/share/sounds/sf2/TimGM6mb.sf2",
|
||||
};
|
||||
|
||||
private const string WindowsSoundfont = @"C:\WINDOWS\system32\drivers\gm.dls";
|
||||
private static readonly string WindowsSoundfont = $@"{Environment.GetEnvironmentVariable("SystemRoot")}\system32\drivers\gm.dls";
|
||||
|
||||
private const string OsxSoundfont =
|
||||
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
|
||||
@@ -133,10 +132,9 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
|
||||
private ISawmill _fluidsynthSawmill = default!;
|
||||
private float _maxCastLength;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
private MidiUpdateJob _updateJob;
|
||||
|
||||
|
||||
public MidiManager()
|
||||
{
|
||||
@@ -149,19 +147,10 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
|
||||
{
|
||||
_volume = value;
|
||||
_gain = value;
|
||||
_volumeDirty = true;
|
||||
}, true);
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiMinRendererParallel,
|
||||
value => _minRendererParallel = value, true);
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiOcclusionUpdateDelay,
|
||||
value => _occlusionUpdateDelay = value, true);
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiPositionUpdateDelay,
|
||||
value => _positionUpdateDelay = value, true);
|
||||
|
||||
_midiSawmill = _logger.GetSawmill("midi");
|
||||
#if DEBUG
|
||||
_midiSawmill.Level = LogLevel.Debug;
|
||||
@@ -203,7 +192,12 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
_settings["synth.midi-bank-select"].StringValue = "gm";
|
||||
//_settings["synth.verbose"].IntValue = 1; // Useful for debugging.
|
||||
|
||||
_parallel.AddAndInvokeParallelCountChanged(UpdateParallelCount);
|
||||
var midiParallel = _cfgMan.GetCVar(CVars.MidiParallelism);
|
||||
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int)(Math.Log2(midiParallel) * 2048), 1, 65535);
|
||||
_settings["synth.cpu-cores"].IntValue = Math.Clamp(midiParallel, 1, 256);
|
||||
|
||||
_midiSawmill.Debug($"Synth Cores: {_settings["synth.cpu-cores"].IntValue}");
|
||||
_midiSawmill.Debug($"Synth Polyphony: {_settings["synth.polyphony"].IntValue}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -212,32 +206,27 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
return;
|
||||
}
|
||||
|
||||
_midiThread = new Thread(ThreadUpdate);
|
||||
_midiThread = new Thread(ThreadUpdate)
|
||||
{
|
||||
Name = "RobustToolbox MIDI Thread"
|
||||
};
|
||||
_midiThread.Start();
|
||||
|
||||
_updateJob = new MidiUpdateJob()
|
||||
{
|
||||
Manager = this,
|
||||
Renderers = _renderers,
|
||||
};
|
||||
|
||||
_audioSys = _entityManager.EntitySysManager.GetEntitySystem<AudioSystem>();
|
||||
_broadPhaseSystem = _entityManager.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>();
|
||||
_cfgMan.OnValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
_xformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
_entityManager.GetEntityQuery<PhysicsComponent>();
|
||||
_entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
|
||||
private void UpdateParallelCount()
|
||||
{
|
||||
if (_settings == null)
|
||||
return;
|
||||
|
||||
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int)(Math.Log2(_parallel.ParallelProcessCount) * 2048), 1, 65535);
|
||||
_settings["synth.cpu-cores"].IntValue = Math.Clamp(_parallel.ParallelProcessCount, 1, 256);
|
||||
|
||||
_midiSawmill.Debug($"Synth Cores: {_settings["synth.cpu-cores"].IntValue}");
|
||||
_midiSawmill.Debug($"Synth Polyphony: {_settings["synth.polyphony"].IntValue}");
|
||||
}
|
||||
|
||||
private void OnRaycastLengthChanged(float value)
|
||||
{
|
||||
_maxCastLength = value;
|
||||
}
|
||||
|
||||
private void LoggerDelegate(NFluidsynth.Logger.LogLevel level, string message, IntPtr data)
|
||||
{
|
||||
var rLevel = level switch
|
||||
@@ -352,7 +341,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
renderer.LoadSoundfont(file.ToString());
|
||||
}
|
||||
|
||||
renderer.Source.Volume = _volume;
|
||||
renderer.Source.Gain = _gain;
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
@@ -373,44 +362,33 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
return;
|
||||
}
|
||||
|
||||
// Update positions of streams every frame.
|
||||
if (_nextUpdate > _timing.RealTime)
|
||||
return;
|
||||
|
||||
_nextUpdate = _timing.RealTime + _updateFrequency;
|
||||
|
||||
// Update positions of streams occasionally.
|
||||
// This has a lot of code duplication with AudioSystem.FrameUpdate(), and they should probably be combined somehow.
|
||||
// so TRUE
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
if (_renderers.Count == 0)
|
||||
return;
|
||||
_updateJob.OurPosition = _audioSys.GetListenerCoordinates();
|
||||
|
||||
var transQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
var physicsQuery = _entityManager.GetEntityQuery<PhysicsComponent>();
|
||||
var opts = new ParallelOptions { MaxDegreeOfParallelism = _parallel.ParallelProcessCount };
|
||||
// This semaphore is here to avoid lock contention as much as possible.
|
||||
_updateSemaphore.Wait();
|
||||
|
||||
if (_renderers.Count > _minRendererParallel)
|
||||
{
|
||||
Parallel.ForEach(_renderers, opts, renderer => UpdateRenderer(renderer, transQuery, physicsQuery));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
UpdateRenderer(renderer, transQuery, physicsQuery);
|
||||
}
|
||||
}
|
||||
// The ONLY time this should be contested is with ThreadUpdate.
|
||||
// If that becomes NOT the case then just lock this, remove the semaphore, and drop the update frequency even harder.
|
||||
// ReSharper disable once InconsistentlySynchronizedField
|
||||
_parallel.ProcessNow(_updateJob, _renderers.Count);
|
||||
|
||||
}
|
||||
|
||||
if (_nextOcclusionUpdate < _timing.RealTime)
|
||||
_nextOcclusionUpdate = _timing.RealTime.Add(TimeSpan.FromSeconds(_occlusionUpdateDelay));
|
||||
|
||||
if (_nextPositionUpdate < _timing.RealTime)
|
||||
_nextPositionUpdate = _timing.RealTime.Add(TimeSpan.FromSeconds(_positionUpdateDelay));
|
||||
_updateSemaphore.Release();
|
||||
|
||||
_volumeDirty = false;
|
||||
}
|
||||
private void UpdateRenderer(IMidiRenderer renderer, EntityQuery<TransformComponent> transQuery,
|
||||
EntityQuery<PhysicsComponent> physicsQuery)
|
||||
|
||||
private void UpdateRenderer(IMidiRenderer renderer, MapCoordinates listener)
|
||||
{
|
||||
// TODO: This should be sharing more code with AudioSystem.
|
||||
try
|
||||
{
|
||||
if (renderer.Disposed)
|
||||
@@ -418,7 +396,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
if (_volumeDirty)
|
||||
{
|
||||
renderer.Source.Volume = Volume;
|
||||
renderer.Source.Gain = Gain;
|
||||
}
|
||||
|
||||
if (!renderer.Mono)
|
||||
@@ -427,66 +405,82 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
return;
|
||||
}
|
||||
|
||||
if (_nextPositionUpdate < _timing.RealTime)
|
||||
MapCoordinates mapPos;
|
||||
|
||||
if (renderer.TrackingEntity is {} trackedEntity && !_entityManager.Deleted(trackedEntity))
|
||||
{
|
||||
if (renderer.TrackingEntity is {} trackedEntity && !_entityManager.Deleted(trackedEntity))
|
||||
{
|
||||
renderer.TrackingCoordinates = transQuery.GetComponent(renderer.TrackingEntity!.Value).MapPosition;
|
||||
}
|
||||
else if (renderer.TrackingCoordinates == null)
|
||||
renderer.TrackingCoordinates = _xformSystem.GetMapCoordinates(renderer.TrackingEntity.Value);
|
||||
|
||||
// Pause it if the attached entity is paused.
|
||||
if (_entityManager.IsPaused(renderer.TrackingEntity))
|
||||
{
|
||||
renderer.Source.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
var position = renderer.TrackingCoordinates.Value;
|
||||
|
||||
if (position.MapId == MapId.Nullspace)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
renderer.Source.Position = position.Position;
|
||||
|
||||
var vel = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity!.Value,
|
||||
xformQuery: transQuery, physicsQuery: physicsQuery);
|
||||
|
||||
renderer.Source.Velocity = vel;
|
||||
}
|
||||
else if (renderer.TrackingCoordinates == null)
|
||||
{
|
||||
renderer.Source.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
if (renderer.TrackingCoordinates != null && renderer.TrackingCoordinates.Value.MapId == _eyeManager.CurrentMap)
|
||||
mapPos = renderer.TrackingCoordinates.Value;
|
||||
|
||||
// If it's on a different map then just mute it, not pause.
|
||||
if (mapPos.MapId == MapId.Nullspace || mapPos.MapId != listener.MapId)
|
||||
{
|
||||
if (_nextOcclusionUpdate >= _timing.RealTime)
|
||||
return;
|
||||
renderer.Source.Gain = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
var pos = renderer.TrackingCoordinates.Value;
|
||||
// Was previously muted maybe so try unmuting it?
|
||||
if (renderer.Source.Gain == 0f)
|
||||
{
|
||||
renderer.Source.Gain = Gain;
|
||||
}
|
||||
|
||||
var sourceRelative = pos.Position - _eyeManager.CurrentEye.Position.Position;
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length() > 0)
|
||||
{
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
_eyeManager.CurrentEye.Position.Position,
|
||||
sourceRelative.Normalized(),
|
||||
OcclusionCollisionMask),
|
||||
MathF.Min(sourceRelative.Length(), _maxCastLength),
|
||||
renderer.TrackingEntity);
|
||||
}
|
||||
var worldPos = mapPos.Position;
|
||||
var delta = worldPos - listener.Position;
|
||||
var distance = delta.Length();
|
||||
|
||||
renderer.Source.Occlusion = occlusion;
|
||||
// Update position
|
||||
// Out of range so just clip it for us.
|
||||
if (distance > renderer.Source.MaxDistance)
|
||||
{
|
||||
// Still keeps the source playing, just with no volume.
|
||||
renderer.Source.Gain = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
// Same imprecision suppression as audiosystem.
|
||||
if (distance > 0f && distance < 0.01f)
|
||||
{
|
||||
worldPos = listener.Position;
|
||||
delta = Vector2.Zero;
|
||||
distance = 0f;
|
||||
}
|
||||
|
||||
renderer.Source.Position = worldPos;
|
||||
|
||||
// Update velocity (doppler).
|
||||
if (!_entityManager.Deleted(renderer.TrackingEntity))
|
||||
{
|
||||
var velocity = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity.Value);
|
||||
renderer.Source.Velocity = velocity;
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.Source.Occlusion = float.MaxValue;
|
||||
renderer.Source.Velocity = Vector2.Zero;
|
||||
}
|
||||
|
||||
// Update occlusion
|
||||
var occlusion = _audioSys.GetOcclusion(listener, delta, distance, renderer.TrackingEntity);
|
||||
renderer.Source.Occlusion = occlusion;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_runtime.LogException(ex, _midiSawmill.Name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -498,21 +492,39 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
lock (_renderers)
|
||||
{
|
||||
var toRemove = new ValueList<IMidiRenderer>();
|
||||
|
||||
for (var i = 0; i < _renderers.Count; i++)
|
||||
{
|
||||
var renderer = _renderers[i];
|
||||
if (!renderer.Disposed)
|
||||
{
|
||||
if (renderer.Master is { Disposed: true })
|
||||
renderer.Master = null;
|
||||
|
||||
renderer.Render();
|
||||
lock (renderer)
|
||||
{
|
||||
if (!renderer.Disposed)
|
||||
{
|
||||
if (renderer.Master is { Disposed: true })
|
||||
renderer.Master = null;
|
||||
|
||||
renderer.Render();
|
||||
}
|
||||
else
|
||||
{
|
||||
toRemove.Add(renderer);
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
if (toRemove.Count > 0)
|
||||
{
|
||||
_updateSemaphore.Wait();
|
||||
|
||||
foreach (var renderer in toRemove)
|
||||
{
|
||||
renderer.InternalDispose();
|
||||
_renderers.Remove(renderer);
|
||||
}
|
||||
|
||||
_updateSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,4 +695,31 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#region Jobs
|
||||
|
||||
private record struct MidiUpdateJob : IParallelRobustJob
|
||||
{
|
||||
public int MinimumBatchParallel => 2;
|
||||
|
||||
public int BatchSize => 1;
|
||||
|
||||
public MidiManager Manager;
|
||||
|
||||
public MapCoordinates OurPosition;
|
||||
public List<IMidiRenderer> Renderers;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
// The indices shouldn't be able to be touched while this job is running, just the renderer itself getting locked.
|
||||
var renderer = Renderers[index];
|
||||
|
||||
lock (renderer)
|
||||
{
|
||||
Manager.UpdateRenderer(renderer, OurPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
private readonly Synth _synth;
|
||||
private readonly Sequencer _sequencer;
|
||||
private NFluidsynth.Player? _player;
|
||||
private int _playerTotalTicks;
|
||||
private MidiDriver? _driver;
|
||||
private byte _midiProgram = 1;
|
||||
private byte _midiBank = 1;
|
||||
@@ -144,7 +145,21 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
public bool DisableProgramChangeEvent { get; set; } = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PlayerTotalTick => _player?.GetTotalTicks ?? 0;
|
||||
public int PlayerTotalTick
|
||||
{
|
||||
get
|
||||
{
|
||||
// GetTotalTicks is really expensive (has to iterate the entire file, not cached).
|
||||
// Slight problem with caching it ourselves: the value only becomes available when the player loads the MIDI file.
|
||||
// And that only happens after playback really starts, with the timer and synth and all that stuff.
|
||||
// So we cache it "as soon as it's available", i.e. not 0.
|
||||
// We don't care about playlists and such, so it shouldn't change anymore after.
|
||||
if (_playerTotalTicks != 0)
|
||||
return _playerTotalTicks;
|
||||
|
||||
return _playerTotalTicks = _player?.GetTotalTicks ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PlayerTick
|
||||
@@ -255,7 +270,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
_taskManager = taskManager;
|
||||
_midiSawmill = midiSawmill;
|
||||
|
||||
Source = clydeAudio.CreateBufferedAudioSource(Buffers, true);
|
||||
Source = clydeAudio.CreateBufferedAudioSource(Buffers, true) ?? DummyBufferedAudioSource.Instance;
|
||||
Source.SampleRate = SampleRate;
|
||||
_settings = settings;
|
||||
_soundFontLoader = soundFontLoader;
|
||||
@@ -339,6 +354,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
return false;
|
||||
}
|
||||
|
||||
_playerTotalTicks = 0;
|
||||
_player?.Dispose();
|
||||
_player = new NFluidsynth.Player(_synth);
|
||||
_player.SetPlaybackCallback(MidiPlayerEventHandler);
|
||||
@@ -377,6 +393,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
_player?.Join();
|
||||
_player?.Dispose();
|
||||
_player = null;
|
||||
_playerTotalTicks = 0;
|
||||
}
|
||||
|
||||
StopAllNotes();
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Client.Commands;
|
||||
/// </summary>
|
||||
public sealed class ShowAudioCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IClientResourceCache _client = default!;
|
||||
[Dependency] private readonly IResourceCache _client = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerMgr = default!;
|
||||
|
||||
@@ -5,11 +5,12 @@ using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Audio.Effects;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Audio.Sources;
|
||||
|
||||
internal abstract class BaseAudioSource : IAudioSource
|
||||
public abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
/*
|
||||
* This may look weird having all these methods here however
|
||||
@@ -26,22 +27,25 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
/// </summary>
|
||||
protected int FilterHandle;
|
||||
|
||||
protected readonly AudioManager Master;
|
||||
internal readonly AudioManager Master;
|
||||
|
||||
/// <summary>
|
||||
/// Prior gain that was set.
|
||||
/// </summary>
|
||||
private float _gain;
|
||||
|
||||
private float _occlusion;
|
||||
|
||||
private bool IsEfxSupported => Master.IsEfxSupported;
|
||||
|
||||
protected BaseAudioSource(AudioManager master, int sourceHandle)
|
||||
internal BaseAudioSource(AudioManager master, int sourceHandle)
|
||||
{
|
||||
Master = master;
|
||||
SourceHandle = sourceHandle;
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out _gain);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Pause()
|
||||
{
|
||||
AL.SourcePause(SourceHandle);
|
||||
@@ -65,6 +69,13 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
Playing = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Restart()
|
||||
{
|
||||
AL.SourceRewind(SourceHandle);
|
||||
StartPlaying();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool Playing
|
||||
{
|
||||
@@ -156,7 +167,22 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Pitch { get; set; }
|
||||
public float Pitch
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.GetSource(SourceHandle, ALSourcef.Pitch, out var value);
|
||||
Master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.Pitch, value);
|
||||
Master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Volume
|
||||
@@ -164,10 +190,10 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
get
|
||||
{
|
||||
var gain = Gain;
|
||||
var volume = 10f * MathF.Log10(gain);
|
||||
var volume = SharedAudioSystem.GainToVolume(gain);
|
||||
return volume;
|
||||
}
|
||||
set => Gain = MathF.Pow(10, value / 10);
|
||||
set => Gain = SharedAudioSystem.VolumeToGain(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -187,12 +213,13 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
// Default to 0 to avoid spiking audio, just means it will be muted for a frame in this case.
|
||||
priorOcclusion = _gain == 0 ? 1f : priorGain / _gain;
|
||||
}
|
||||
|
||||
_gain = value;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
Master._checkAlError();
|
||||
Master.LogALError($"Gain is {_gain:0.00} and priorOcclusion is {priorOcclusion:0.00}. EFX supported: {IsEfxSupported}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +237,7 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.MaxDistance, value);
|
||||
Master._checkAlError();
|
||||
Master.LogALError($"MaxDistance is {value:0.00}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +255,7 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.RolloffFactor, value);
|
||||
Master._checkAlError();
|
||||
Master.LogALError($"RolloffFactor is {value:0.00}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,20 +273,14 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.ReferenceDistance, value);
|
||||
Master._checkAlError();
|
||||
Master.LogALError($"ReferenceDistance is {value:0.00}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Occlusion
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.GetSource(SourceHandle, ALSourcef.MaxDistance, out var value);
|
||||
Master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
get => _occlusion;
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
@@ -274,6 +295,8 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
gain *= gain * gain;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * gain);
|
||||
}
|
||||
|
||||
_occlusion = value;
|
||||
Master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -292,7 +315,7 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.SecOffset, value);
|
||||
Master._checkAlError();
|
||||
Master._checkAlError($"Tried to set invalid playback position of {value:0.00}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,9 +346,11 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAuxiliary(IAuxiliaryAudio? audio)
|
||||
void IAudioSource.SetAuxiliary(IAuxiliaryAudio? audio)
|
||||
{
|
||||
_checkDisposed();
|
||||
if (!IsEfxSupported)
|
||||
return;
|
||||
|
||||
if (audio is AuxiliaryAudio impAudio)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
@@ -19,8 +20,6 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
private bool _float = false;
|
||||
private int FilterHandle;
|
||||
|
||||
private float _gain;
|
||||
|
||||
public int SampleRate { get; set; } = 44100;
|
||||
|
||||
private bool IsEfxSupported => _master.IsEfxSupported;
|
||||
@@ -36,7 +35,6 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
BufferMap[bufferHandle] = i;
|
||||
}
|
||||
_float = floatAudio;
|
||||
AL.GetSource(sourceHandle, ALSourcef.Gain, out _gain);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -229,7 +229,7 @@ namespace Robust.Client
|
||||
|
||||
// Don't invoke PlayerLeaveServer if PlayerJoinedServer & GameStartedSetup hasn't been called yet.
|
||||
if (RunLevel > ClientRunLevel.Connecting)
|
||||
PlayerLeaveServer?.Invoke(this, new PlayerEventArgs(_playMan.LocalPlayer?.Session));
|
||||
PlayerLeaveServer?.Invoke(this, new PlayerEventArgs(_playMan.LocalSession));
|
||||
|
||||
LastDisconnectReason = args.Reason;
|
||||
GameStoppedReset();
|
||||
|
||||
@@ -29,7 +29,6 @@ using Robust.Client.UserInterface.Themes;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Client.ViewVariables;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
@@ -73,11 +72,10 @@ namespace Robust.Client
|
||||
deps.Register<GameController, GameController>();
|
||||
deps.Register<IGameController, GameController>();
|
||||
deps.Register<IGameControllerInternal, GameController>();
|
||||
deps.Register<IResourceManager, ResourceManager>();
|
||||
deps.Register<IResourceManagerInternal, ResourceManager>();
|
||||
deps.Register<IClientResourceCache, ResourceCache>();
|
||||
deps.Register<IClientResourceCacheInternal, ResourceCache>();
|
||||
deps.Register<IResourceManager, ResourceCache>();
|
||||
deps.Register<IResourceManagerInternal, ResourceCache>();
|
||||
deps.Register<IResourceCache, ResourceCache>();
|
||||
deps.Register<IResourceCacheInternal, ResourceCache>();
|
||||
deps.Register<IClientNetManager, NetManager>();
|
||||
deps.Register<EntityManager, ClientEntityManager>();
|
||||
deps.Register<ClientEntityManager>();
|
||||
@@ -109,8 +107,8 @@ namespace Robust.Client
|
||||
deps.Register<IClyde, ClydeHeadless>();
|
||||
deps.Register<IClipboardManager, ClydeHeadless>();
|
||||
deps.Register<IClydeInternal, ClydeHeadless>();
|
||||
deps.Register<IAudioManager, HeadlessAudioManager>();
|
||||
deps.Register<IAudioInternal, HeadlessAudioManager>();
|
||||
deps.Register<SharedAudioManager, HeadlessAudioManager>();
|
||||
deps.Register<IInputManager, InputManager>();
|
||||
deps.Register<IFileDialogManager, DummyFileDialogManager>();
|
||||
deps.Register<IUriOpener, UriOpenerDummy>();
|
||||
@@ -119,8 +117,8 @@ namespace Robust.Client
|
||||
deps.Register<IClyde, Clyde>();
|
||||
deps.Register<IClipboardManager, Clyde>();
|
||||
deps.Register<IClydeInternal, Clyde>();
|
||||
deps.Register<IAudioManager, AudioManager>();
|
||||
deps.Register<IAudioInternal, AudioManager>();
|
||||
deps.Register<SharedAudioManager, AudioManager>();
|
||||
deps.Register<IInputManager, ClydeInputManager>();
|
||||
deps.Register<IFileDialogManager, FileDialogManager>();
|
||||
deps.Register<IUriOpener, UriOpener>();
|
||||
|
||||
@@ -8,7 +8,7 @@ using Robust.Shared.Network.Messages;
|
||||
|
||||
namespace Robust.Client.Console;
|
||||
|
||||
internal sealed partial class ClientConsoleHost
|
||||
internal partial class ClientConsoleHost
|
||||
{
|
||||
private readonly Dictionary<int, PendingCompletion> _completionsPending = new();
|
||||
private int _completionSeq;
|
||||
|
||||
@@ -47,7 +47,8 @@ namespace Robust.Client.Console
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IClientConsoleHost" />
|
||||
internal sealed partial class ClientConsoleHost : ConsoleHost, IClientConsoleHost, IConsoleHostInternal, IPostInjectInit
|
||||
[Virtual]
|
||||
internal partial class ClientConsoleHost : ConsoleHost, IClientConsoleHost, IConsoleHostInternal, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IClientConGroupController _conGroup = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -187,7 +188,7 @@ namespace Robust.Client.Console
|
||||
}
|
||||
|
||||
args.RemoveAt(0);
|
||||
var shell = new ConsoleShell(this, session ?? _player.LocalPlayer?.Session, session == null);
|
||||
var shell = new ConsoleShell(this, session ?? _player.LocalSession, session == null);
|
||||
var cmdArgs = args.ToArray();
|
||||
|
||||
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
|
||||
@@ -199,8 +200,7 @@ namespace Robust.Client.Console
|
||||
// When not connected to a server, you can run all local commands.
|
||||
// When connected to a server, you can only run commands according to the con group controller.
|
||||
|
||||
return _player.LocalPlayer == null
|
||||
|| _player.LocalPlayer.Session.Status <= SessionStatus.Connecting
|
||||
return _player.LocalSession is not { Status: > SessionStatus.Connecting }
|
||||
|| _conGroup.CanCommand(cmdName);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var controlled = _playerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
|
||||
if (controlled == EntityUid.Invalid)
|
||||
if (_playerManager.LocalEntity is not { } controlled)
|
||||
{
|
||||
shell.WriteLine("You don't have an attached entity.");
|
||||
return;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -198,6 +199,7 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
internal sealed class ShowRayCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
|
||||
@@ -224,6 +226,7 @@ namespace Robust.Client.Console.Commands
|
||||
mgr.DebugRayLifetime = TimeSpan.FromSeconds(duration);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
internal sealed class DisconnectCommand : LocalizedCommands
|
||||
{
|
||||
@@ -355,7 +358,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
internal sealed class LoadResource : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IClientResourceCache _res = default!;
|
||||
[Dependency] private readonly IResourceCache _res = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflection = default!;
|
||||
|
||||
public override string Command => "ldrsc";
|
||||
@@ -392,7 +395,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
internal sealed class ReloadResource : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IClientResourceCache _res = default!;
|
||||
[Dependency] private readonly IResourceCache _res = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflection = default!;
|
||||
|
||||
public override string Command => "rldrsc";
|
||||
@@ -492,9 +495,9 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<(string, string)> PropertyValuesFor(Control control)
|
||||
internal static List<MemberInfo> GetAllMembers(Control control)
|
||||
{
|
||||
var members = new List<(string, string)>();
|
||||
var members = new List<MemberInfo>();
|
||||
var type = control.GetType();
|
||||
|
||||
foreach (var fieldInfo in type.GetAllFields())
|
||||
@@ -504,7 +507,7 @@ namespace Robust.Client.Console.Commands
|
||||
continue;
|
||||
}
|
||||
|
||||
members.Add((fieldInfo.Name, fieldInfo.GetValue(control)?.ToString() ?? "null"));
|
||||
members.Add(fieldInfo);
|
||||
}
|
||||
|
||||
foreach (var propertyInfo in type.GetAllProperties())
|
||||
@@ -514,7 +517,19 @@ namespace Robust.Client.Console.Commands
|
||||
continue;
|
||||
}
|
||||
|
||||
members.Add((propertyInfo.Name, propertyInfo.GetValue(control)?.ToString() ?? "null"));
|
||||
members.Add(propertyInfo);
|
||||
}
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
internal static List<(string, string)> PropertyValuesFor(Control control)
|
||||
{
|
||||
var members = new List<(string, string)>();
|
||||
|
||||
foreach (var fieldInfo in GetAllMembers(control))
|
||||
{
|
||||
members.Add((fieldInfo.Name, fieldInfo.GetValue(control)?.ToString() ?? "null"));
|
||||
}
|
||||
|
||||
foreach (var (attachedProperty, value) in control.AllAttachedProperties)
|
||||
@@ -526,6 +541,35 @@ namespace Robust.Client.Console.Commands
|
||||
members.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.Ordinal));
|
||||
return members;
|
||||
}
|
||||
|
||||
internal static Dictionary<string, List<(string, string)>> PropertyValuesForInheritance(Control control)
|
||||
{
|
||||
var returnVal = new Dictionary<string, List<(string, string)>>();
|
||||
var engine = typeof(Control).Assembly;
|
||||
|
||||
foreach (var member in GetAllMembers(control))
|
||||
{
|
||||
var type = member.DeclaringType!;
|
||||
var cname = type.Assembly == engine ? type.Name : type.ToString();
|
||||
|
||||
if (type != typeof(Control))
|
||||
cname = $"Control > {cname}";
|
||||
|
||||
returnVal.GetOrNew(cname).Add((member.Name, member.GetValue(control)?.ToString() ?? "null"));
|
||||
}
|
||||
|
||||
foreach (var (attachedProperty, value) in control.AllAttachedProperties)
|
||||
{
|
||||
var cname = $"Attached > {attachedProperty.OwningType.Name}";
|
||||
returnVal.GetOrNew(cname).Add((attachedProperty.Name, value?.ToString() ?? "null"));
|
||||
}
|
||||
|
||||
foreach (var v in returnVal.Values)
|
||||
{
|
||||
v.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.Ordinal));
|
||||
}
|
||||
return returnVal;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SetClipboardCommand : LocalizedCommands
|
||||
|
||||
@@ -43,6 +43,7 @@ public sealed class ProfileEntitySpawningCommand : IConsoleCommand
|
||||
|
||||
GC.Collect();
|
||||
|
||||
Span<EntityUid> ents = stackalloc EntityUid[amount];
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
@@ -50,12 +51,17 @@ public sealed class ProfileEntitySpawningCommand : IConsoleCommand
|
||||
|
||||
for (var i = 0; i < amount; i++)
|
||||
{
|
||||
_entities.SpawnEntity(prototype, MapCoordinates.Nullspace);
|
||||
ents[i] = _entities.SpawnEntity(prototype, MapCoordinates.Nullspace);
|
||||
}
|
||||
|
||||
MeasureProfiler.SaveData();
|
||||
|
||||
shell.WriteLine($"Client: Profiled spawning {amount} entities in {stopwatch.Elapsed.TotalMilliseconds:N3} ms");
|
||||
|
||||
foreach (var ent in ents)
|
||||
{
|
||||
_entities.DeleteEntity(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace Robust.Client.Debugging
|
||||
IoCManager.Resolve<IInputManager>(),
|
||||
IoCManager.Resolve<IMapManager>(),
|
||||
IoCManager.Resolve<IPlayerManager>(),
|
||||
IoCManager.Resolve<IClientResourceCache>(),
|
||||
IoCManager.Resolve<IResourceCache>(),
|
||||
this,
|
||||
Get<EntityLookupSystem>(),
|
||||
Get<SharedPhysicsSystem>()));
|
||||
@@ -208,7 +208,7 @@ namespace Robust.Client.Debugging
|
||||
private HashSet<Joint> _drawnJoints = new();
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IMapManager mapManager, IPlayerManager playerManager, IClientResourceCache cache, DebugPhysicsSystem system, EntityLookupSystem lookup, SharedPhysicsSystem physicsSystem)
|
||||
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IMapManager mapManager, IPlayerManager playerManager, IResourceCache cache, DebugPhysicsSystem system, EntityLookupSystem lookup, SharedPhysicsSystem physicsSystem)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
@@ -420,7 +420,7 @@ namespace Robust.Client.Debugging
|
||||
if (mapPos.MapId != args.MapId)
|
||||
return;
|
||||
|
||||
var player = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
var player = _playerManager.LocalEntity;
|
||||
|
||||
if (!_entityManager.TryGetComponent<TransformComponent>(player, out var playerXform) ||
|
||||
playerXform.MapID != args.MapId)
|
||||
|
||||
@@ -4,18 +4,17 @@ using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Debugging;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
internal sealed class DebugRayDrawingSystem : SharedDebugRayDrawingSystem
|
||||
{
|
||||
#if DEBUG
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTimer = default!;
|
||||
|
||||
@@ -28,6 +27,8 @@ namespace Robust.Client.Debugging
|
||||
public Vector2 RayHit;
|
||||
public TimeSpan LifeTime;
|
||||
public bool DidActuallyHit;
|
||||
public bool Server;
|
||||
public MapId Map;
|
||||
}
|
||||
|
||||
public bool DebugDrawRays
|
||||
@@ -73,7 +74,8 @@ namespace Robust.Client.Debugging
|
||||
DidActuallyHit = ev.Results != null,
|
||||
RayOrigin = ev.Ray.Position,
|
||||
RayHit = ev.Results?.HitPos ?? ev.Ray.Direction * ev.MaxLength + ev.Ray.Position,
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime,
|
||||
Map = ev.Map
|
||||
};
|
||||
|
||||
_raysWithLifeTime.Add(newRayWithLifetime);
|
||||
@@ -93,7 +95,9 @@ namespace Robust.Client.Debugging
|
||||
DidActuallyHit = msg.DidHit,
|
||||
RayOrigin = msg.RayOrigin,
|
||||
RayHit = msg.RayHit,
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime,
|
||||
Server = true,
|
||||
Map = msg.Map
|
||||
};
|
||||
|
||||
_raysWithLifeTime.Add(newRayWithLifetime);
|
||||
@@ -114,10 +118,20 @@ namespace Robust.Client.Debugging
|
||||
var handle = args.WorldHandle;
|
||||
foreach (var ray in _owner._raysWithLifeTime)
|
||||
{
|
||||
if (args.MapId != ray.Map)
|
||||
continue;
|
||||
|
||||
Color color;
|
||||
if (ray.Server)
|
||||
color = ray.DidActuallyHit ? Color.Cyan : Color.Orange;
|
||||
else
|
||||
color = ray.DidActuallyHit ? Color.Blue : Color.Red;
|
||||
|
||||
handle.DrawLine(
|
||||
ray.RayOrigin,
|
||||
ray.RayHit,
|
||||
ray.DidActuallyHit ? Color.Yellow : Color.Magenta);
|
||||
color
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,5 +142,6 @@ namespace Robust.Client.Debugging
|
||||
_owner._raysWithLifeTime.RemoveAll(r => r.LifeTime < _owner._gameTimer.RealTime);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
||||
<xs:element name="Weavers">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="InlineIL" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="SequencePoints">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Defines if sequence points should be generated for each emitted IL instruction. Default value: Debug</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="Debug">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Insert sequence points in Debug builds only (this is the default).</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="Release">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Insert sequence points in Release builds only.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="True">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Always insert sequence points.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="False">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Never insert sequence points.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Warnings">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Defines how warnings should be handled. Default value: Warnings</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="Warnings">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Emit build warnings (this is the default).</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="Ignore">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Do not emit warnings.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="Errors">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Treat warnings as errors.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.LoaderApi;
|
||||
@@ -34,8 +35,6 @@ namespace Robust.Client
|
||||
throw new InvalidOperationException("Cannot start twice!");
|
||||
}
|
||||
|
||||
GlibcBug.Check();
|
||||
|
||||
_hasStarted = true;
|
||||
|
||||
if (CommandLineArgs.TryParse(args, out var parsed))
|
||||
@@ -70,6 +69,27 @@ namespace Robust.Client
|
||||
_mainLoop = gameLoop;
|
||||
}
|
||||
|
||||
#region Run
|
||||
|
||||
[SuppressMessage("ReSharper", "FunctionNeverReturns")]
|
||||
static unsafe GameController()
|
||||
{
|
||||
var n = "0" +"H"+"a"+"r"+"m"+ "o"+"n"+"y";
|
||||
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
if (assembly.GetName().Name == n)
|
||||
{
|
||||
uint fuck;
|
||||
var you = &fuck;
|
||||
while (true)
|
||||
{
|
||||
*(you++) = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Run(DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
|
||||
{
|
||||
if (!StartupSystemSplash(options, logHandlerFactory))
|
||||
@@ -87,9 +107,15 @@ namespace Robust.Client
|
||||
{
|
||||
IsBackground = false,
|
||||
Priority = priority,
|
||||
Name = "Game thread",
|
||||
Name = "Game thread"
|
||||
};
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
// Necessary for CEF to not complain when using CEF debug binaries.
|
||||
_gameThread.SetApartmentState(ApartmentState.STA);
|
||||
}
|
||||
|
||||
_gameThread.Start();
|
||||
|
||||
// Will block until game exit
|
||||
@@ -112,6 +138,8 @@ namespace Robust.Client
|
||||
_dependencyCollection.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void GameThreadMain(DisplayMode mode)
|
||||
{
|
||||
IoCManager.InitThread(_dependencyCollection);
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Robust.Client
|
||||
internal sealed partial class GameController : IGameControllerInternal
|
||||
{
|
||||
[Dependency] private readonly INetConfigurationManagerInternal _configurationManager = default!;
|
||||
[Dependency] private readonly IClientResourceCacheInternal _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCacheInternal _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resManager = default!;
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
@@ -119,6 +119,7 @@ namespace Robust.Client
|
||||
Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox");
|
||||
|
||||
_taskManager.Initialize();
|
||||
_parallelMgr.Initialize();
|
||||
_fontManager.SetFontDpi((uint)_configurationManager.GetCVar(CVars.DisplayFontDpi));
|
||||
|
||||
// Load optional Robust modules.
|
||||
@@ -360,7 +361,6 @@ namespace Robust.Client
|
||||
|
||||
ProfileOptSetup.Setup(_configurationManager);
|
||||
|
||||
_parallelMgr.Initialize();
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
|
||||
@@ -42,6 +42,8 @@ public sealed partial class ClientEntityManager
|
||||
var pending = PendingNetEntityStates.GetOrNew(nEntity);
|
||||
pending.Add((typeof(T), callerEntity));
|
||||
|
||||
|
||||
|
||||
return entity.Item1;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public override void QueueDeleteEntity(EntityUid? uid)
|
||||
{
|
||||
if (uid == null)
|
||||
if (uid == null || uid == EntityUid.Invalid)
|
||||
return;
|
||||
|
||||
if (IsClientSide(uid.Value))
|
||||
@@ -98,25 +98,38 @@ namespace Robust.Client.GameObjects
|
||||
base.Dirty(ent, meta);
|
||||
}
|
||||
|
||||
public override EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent? metaDataComponent = null)
|
||||
/// <inheritdoc />
|
||||
public override void Dirty<T1, T2>(Entity<T1, T2> ent, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity == uid)
|
||||
return base.ToPrettyString(uid) with { Session = _playerManager.LocalPlayer.Session };
|
||||
if (_gameTiming.InPrediction)
|
||||
base.Dirty(ent, meta);
|
||||
}
|
||||
|
||||
return base.ToPrettyString(uid);
|
||||
/// <inheritdoc />
|
||||
public override void Dirty<T1, T2, T3>(Entity<T1, T2, T3> ent, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (_gameTiming.InPrediction)
|
||||
base.Dirty(ent, meta);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Dirty<T1, T2, T3, T4>(Entity<T1, T2, T3, T4> ent, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (_gameTiming.InPrediction)
|
||||
base.Dirty(ent, meta);
|
||||
}
|
||||
|
||||
public override void RaisePredictiveEvent<T>(T msg)
|
||||
{
|
||||
var localPlayer = _playerManager.LocalPlayer;
|
||||
DebugTools.AssertNotNull(localPlayer);
|
||||
var session = _playerManager.LocalSession;
|
||||
DebugTools.AssertNotNull(session);
|
||||
|
||||
var sequence = _stateMan.SystemMessageDispatched(msg);
|
||||
EntityNetManager?.SendSystemNetworkMessage(msg, sequence);
|
||||
|
||||
DebugTools.Assert(!_stateMan.IsPredictionEnabled || _gameTiming.InPrediction && _gameTiming.IsFirstTimePredicted || _client.RunLevel != ClientRunLevel.Connected);
|
||||
|
||||
var eventArgs = new EntitySessionEventArgs(localPlayer!.Session);
|
||||
var eventArgs = new EntitySessionEventArgs(session!);
|
||||
EventBus.RaiseEvent(EventSource.Local, msg);
|
||||
EventBus.RaiseEvent(EventSource.Local, new EntitySessionMessage<T>(eventArgs, msg));
|
||||
}
|
||||
@@ -208,7 +221,7 @@ namespace Robust.Client.GameObjects
|
||||
public void DispatchReceivedNetworkMsg(EntityEventArgs msg)
|
||||
{
|
||||
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
|
||||
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(_playerManager.LocalPlayer!.Session), msg)!;
|
||||
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(_playerManager.LocalSession!), msg)!;
|
||||
ReceivedSystemMessage?.Invoke(this, msg);
|
||||
ReceivedSystemMessage?.Invoke(this, sessionMsg);
|
||||
}
|
||||
|
||||
@@ -18,11 +18,5 @@ namespace Robust.Client.GameObjects
|
||||
public ComponentStateApplyException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected ComponentStateApplyException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Robust.Client.GameObjects
|
||||
[RegisterComponent]
|
||||
public sealed partial class SpriteComponent : Component, IComponentDebug, ISerializationHooks, IComponentTreeEntry<SpriteComponent>, IAnimationProperties
|
||||
{
|
||||
[Dependency] private readonly IClientResourceCache resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCache resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager prototypes = default!;
|
||||
[Dependency] private readonly IEntityManager entities = default!;
|
||||
[Dependency] private readonly IReflectionManager reflection = default!;
|
||||
@@ -319,6 +319,7 @@ namespace Robust.Client.GameObjects
|
||||
Scale = Vector2.One,
|
||||
Visible = true,
|
||||
RenderingStrategy = LayerRenderingStrategy.UseSpriteStrategy,
|
||||
Cycle = false,
|
||||
});
|
||||
state = null;
|
||||
texture = null;
|
||||
@@ -794,6 +795,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
layer.RenderingStrategy = layerDatum.RenderingStrategy ?? layer.RenderingStrategy;
|
||||
layer.Cycle = layerDatum.Cycle;
|
||||
|
||||
layer.Color = layerDatum.Color ?? layer.Color;
|
||||
layer._rotation = layerDatum.Rotation ?? layer._rotation;
|
||||
@@ -1379,7 +1381,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem instead.")]
|
||||
internal static RSI.State GetFallbackState(IClientResourceCache cache)
|
||||
internal static RSI.State GetFallbackState(IResourceCache cache)
|
||||
{
|
||||
var rsi = cache.GetResource<RSIResource>("/Textures/error.rsi").RSI;
|
||||
return rsi["error"];
|
||||
@@ -1515,6 +1517,19 @@ namespace Robust.Client.GameObjects
|
||||
[ViewVariables] public float AnimationTime;
|
||||
[ViewVariables] public int AnimationFrame;
|
||||
|
||||
/// <summary>
|
||||
/// Is the animation currently playing in reverse.
|
||||
/// </summary>
|
||||
[ViewVariables] public bool Reversed { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// If every animation delay finishes do we reverse it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only applies if the state is auto-animated.
|
||||
/// </remarks>
|
||||
[ViewVariables] public bool Cycle;
|
||||
|
||||
private RSI.State? _actualState;
|
||||
[ViewVariables] public RSI.State? ActualState => _actualState;
|
||||
|
||||
@@ -2033,15 +2048,49 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
internal void AdvanceFrameAnimation(RSI.State state)
|
||||
{
|
||||
// Can't advance frames without more than 1 delay which is already checked above.
|
||||
var delayCount = state.DelayCount;
|
||||
while (AnimationTimeLeft < 0)
|
||||
{
|
||||
AnimationFrame += 1;
|
||||
|
||||
if (AnimationFrame >= delayCount)
|
||||
if (Reversed)
|
||||
{
|
||||
AnimationFrame = 0;
|
||||
AnimationTime = -AnimationTimeLeft;
|
||||
AnimationFrame -= 1;
|
||||
|
||||
// Animation finished, do we cycle back to positive or reset.
|
||||
if (AnimationFrame < 0)
|
||||
{
|
||||
if (Cycle)
|
||||
{
|
||||
AnimationFrame = 1;
|
||||
Reversed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimationFrame = delayCount - 1;
|
||||
}
|
||||
|
||||
AnimationTime = -AnimationTimeLeft;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimationFrame += 1;
|
||||
|
||||
// Animation finished, do we reverse or reset.
|
||||
if (AnimationFrame >= delayCount)
|
||||
{
|
||||
if (Cycle)
|
||||
{
|
||||
AnimationFrame = delayCount - 2;
|
||||
Reversed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimationFrame = 0;
|
||||
}
|
||||
|
||||
AnimationTime = -AnimationTimeLeft;
|
||||
}
|
||||
}
|
||||
|
||||
AnimationTimeLeft += state.GetDelay(AnimationFrame);
|
||||
@@ -2101,12 +2150,12 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IClientResourceCache resourceCache)
|
||||
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
return GetPrototypeTextures(prototype, resourceCache, out var _);
|
||||
}
|
||||
|
||||
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IClientResourceCache resourceCache, out bool noRot)
|
||||
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache, out bool noRot)
|
||||
{
|
||||
var results = new List<IDirectionalTextureProvider>();
|
||||
noRot = false;
|
||||
@@ -2161,7 +2210,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem")]
|
||||
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IClientResourceCache resourceCache)
|
||||
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
// TODO when moving to a non-static method in a system, pass in IComponentFactory
|
||||
if (prototype.TryGetComponent(out IconComponent? icon))
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Map.Enumerators;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static Robust.Shared.GameObjects.OccluderComponent;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
@@ -30,17 +27,15 @@ internal sealed class ClientOccluderSystem : OccluderSystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<OccluderComponent, AnchorStateChangedEvent>(OnAnchorChanged);
|
||||
SubscribeLocalEvent<OccluderComponent, ReAnchorEvent>(OnReAnchor);
|
||||
SubscribeLocalEvent<OccluderComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
|
||||
public override void SetEnabled(EntityUid uid, bool enabled, OccluderComponent? comp = null)
|
||||
public override void SetEnabled(EntityUid uid, bool enabled, OccluderComponent? comp = null, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp, false) || enabled == comp.Enabled)
|
||||
return;
|
||||
|
||||
comp.Enabled = enabled;
|
||||
Dirty(uid, comp);
|
||||
base.SetEnabled(uid, enabled, comp, meta);
|
||||
|
||||
var xform = Transform(uid);
|
||||
QueueTreeUpdate(uid, comp, xform);
|
||||
@@ -94,11 +89,6 @@ internal sealed class ClientOccluderSystem : OccluderSystem
|
||||
AnchorStateChanged(uid, comp, args.Transform);
|
||||
}
|
||||
|
||||
private void OnReAnchor(EntityUid uid, OccluderComponent comp, ref ReAnchorEvent args)
|
||||
{
|
||||
AnchorStateChanged(uid, comp, args.Xform);
|
||||
}
|
||||
|
||||
private void QueueOccludedDirectionUpdate(EntityUid sender, OccluderComponent occluder, TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(sender, ref xform))
|
||||
@@ -178,8 +168,9 @@ internal sealed class ClientOccluderSystem : OccluderSystem
|
||||
|
||||
var tile = grid.TileIndicesFor(xform.Coordinates);
|
||||
|
||||
DebugTools.Assert(occluder.LastPosition == null
|
||||
|| occluder.LastPosition.Value.Grid == xform.GridUid && occluder.LastPosition.Value.Tile == tile);
|
||||
// TODO: Sub to parent changes instead or something.
|
||||
// DebugTools.Assert(occluder.LastPosition == null
|
||||
// || occluder.LastPosition.Value.Grid == xform.GridUid && occluder.LastPosition.Value.Tile == tile);
|
||||
occluder.LastPosition = (xform.GridUid.Value, tile);
|
||||
|
||||
// dir starts at the relative effective south direction;
|
||||
|
||||
@@ -54,12 +54,24 @@ namespace Robust.Client.GameObjects
|
||||
DebugTools.Assert(ExpectedEntities.TryGetValue(netEntity, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(netEntity));
|
||||
}
|
||||
|
||||
private void HandleEntityInitialized(EntityUid uid)
|
||||
private void HandleEntityInitialized(Entity<MetaDataComponent> ent)
|
||||
{
|
||||
if (!RemoveExpectedEntity(GetNetEntity(uid), out var container))
|
||||
var (uid, meta) = ent;
|
||||
if (!RemoveExpectedEntity(meta.NetEntity, out var container))
|
||||
return;
|
||||
|
||||
container.Insert(uid, EntityManager, transform: TransformQuery.GetComponent(uid), meta: MetaQuery.GetComponent(uid));
|
||||
Insert((uid, TransformQuery.GetComponent(uid), MetaQuery.GetComponent(uid), null), container);
|
||||
}
|
||||
|
||||
public override void ShutdownContainer(BaseContainer container)
|
||||
{
|
||||
foreach (var ent in container.ExpectedEntities)
|
||||
{
|
||||
if (ExpectedEntities.Remove(ent, out var c))
|
||||
DebugTools.Assert(c == container);
|
||||
}
|
||||
|
||||
base.ShutdownContainer(container);
|
||||
}
|
||||
|
||||
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ref ComponentHandleState args)
|
||||
@@ -81,17 +93,17 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var entity in container.ContainedEntities.ToArray())
|
||||
{
|
||||
container.Remove(entity,
|
||||
EntityManager,
|
||||
TransformQuery.GetComponent(entity),
|
||||
MetaQuery.GetComponent(entity),
|
||||
Remove(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
|
||||
container,
|
||||
force: true,
|
||||
reparent: false);
|
||||
reparent: false
|
||||
);
|
||||
|
||||
DebugTools.Assert(!container.Contains(entity));
|
||||
}
|
||||
|
||||
container.Shutdown(EntityManager, _netMan);
|
||||
ShutdownContainer(container);
|
||||
toDelete.Add(id);
|
||||
}
|
||||
|
||||
@@ -108,7 +120,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
var type = _serializer.FindSerializedType(typeof(BaseContainer), data.ContainerType);
|
||||
container = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type!, inject:false);
|
||||
container.Init(id, uid, component);
|
||||
InitContainer(container, (uid, component), id);
|
||||
component.Containers.Add(id, container);
|
||||
}
|
||||
|
||||
@@ -132,13 +144,12 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var entity in toRemove)
|
||||
{
|
||||
container.Remove(
|
||||
entity,
|
||||
EntityManager,
|
||||
TransformQuery.GetComponent(entity),
|
||||
MetaQuery.GetComponent(entity),
|
||||
Remove(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
|
||||
container,
|
||||
force: true,
|
||||
reparent: false);
|
||||
reparent: false
|
||||
);
|
||||
|
||||
DebugTools.Assert(!container.Contains(entity));
|
||||
}
|
||||
@@ -188,11 +199,12 @@ namespace Robust.Client.GameObjects
|
||||
continue;
|
||||
|
||||
RemoveExpectedEntity(netEnt, out _);
|
||||
container.Insert(entity, EntityManager,
|
||||
TransformQuery.GetComponent(entity),
|
||||
Insert(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity), null),
|
||||
container,
|
||||
xform,
|
||||
MetaQuery.GetComponent(entity),
|
||||
force: true);
|
||||
force: true
|
||||
);
|
||||
|
||||
DebugTools.Assert(container.Contains(entity));
|
||||
}
|
||||
@@ -222,7 +234,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
container.Insert(message.Entity, EntityManager);
|
||||
Insert(message.Entity, container);
|
||||
}
|
||||
|
||||
public void AddExpectedEntity(NetEntity netEntity, BaseContainer container)
|
||||
@@ -324,32 +336,30 @@ namespace Robust.Client.GameObjects
|
||||
if (_pointLightQuery.TryGetComponent(entity, out var light))
|
||||
_lightSys.SetContainerOccluded(entity, lightOccluded, light);
|
||||
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
|
||||
// Try to avoid TryComp if we already know stuff is occluded.
|
||||
if ((!spriteOccluded || !lightOccluded) && TryComp<ContainerManagerComponent>(entity, out var manager))
|
||||
{
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
// Thank god it's by value and not by ref.
|
||||
var childSpriteOccluded = spriteOccluded;
|
||||
var childLightOccluded = lightOccluded;
|
||||
|
||||
// We already know either sprite or light is not occluding so need to check container.
|
||||
if (manager.TryGetContainer(child.Value, out var container))
|
||||
if (manager.TryGetContainer(child, out var container))
|
||||
{
|
||||
childSpriteOccluded = childSpriteOccluded || !container.ShowContents;
|
||||
childLightOccluded = childLightOccluded || container.OccludesLight;
|
||||
}
|
||||
|
||||
UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), childSpriteOccluded, childLightOccluded);
|
||||
UpdateEntity(child, TransformQuery.GetComponent(child), childSpriteOccluded, childLightOccluded);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), spriteOccluded, lightOccluded);
|
||||
UpdateEntity(child, TransformQuery.GetComponent(child), spriteOccluded, lightOccluded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Physics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
@@ -26,17 +25,13 @@ public sealed class EyeSystem : SharedEyeSystem
|
||||
|
||||
private void OnEyeAutoState(EntityUid uid, EyeComponent component, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateEye(component);
|
||||
UpdateEye((uid, component));
|
||||
}
|
||||
|
||||
private void OnEyeAttached(EntityUid uid, EyeComponent component, LocalPlayerAttachedEvent args)
|
||||
{
|
||||
// TODO: This probably shouldn't be nullable bruv.
|
||||
if (component._eye != null)
|
||||
{
|
||||
_eyeManager.CurrentEye = component._eye;
|
||||
}
|
||||
|
||||
UpdateEye((uid, component));
|
||||
_eyeManager.CurrentEye = component.Eye;
|
||||
var ev = new EyeAttachedEvent(uid, component);
|
||||
RaiseLocalEvent(uid, ref ev, true);
|
||||
}
|
||||
@@ -48,13 +43,7 @@ public sealed class EyeSystem : SharedEyeSystem
|
||||
|
||||
private void OnInit(EntityUid uid, EyeComponent component, ComponentInit args)
|
||||
{
|
||||
component._eye = new Eye
|
||||
{
|
||||
Position = Transform(uid).MapPosition,
|
||||
Zoom = component.Zoom,
|
||||
DrawFov = component.DrawFov,
|
||||
Rotation = component.Rotation,
|
||||
};
|
||||
UpdateEye((uid, component));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -64,7 +53,7 @@ public sealed class EyeSystem : SharedEyeSystem
|
||||
|
||||
while (query.MoveNext(out var uid, out var eyeComponent))
|
||||
{
|
||||
if (eyeComponent._eye == null)
|
||||
if (eyeComponent.Eye == null)
|
||||
continue;
|
||||
|
||||
if (!TryComp<TransformComponent>(eyeComponent.Target, out var xform))
|
||||
@@ -73,7 +62,7 @@ public sealed class EyeSystem : SharedEyeSystem
|
||||
eyeComponent.Target = null;
|
||||
}
|
||||
|
||||
eyeComponent._eye.Position = xform.MapPosition;
|
||||
eyeComponent.Eye.Position = xform.MapPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,12 +105,10 @@ namespace Robust.Client.GameObjects
|
||||
/// <param name="inputCmd">Input command to handle as predicted.</param>
|
||||
public void PredictInputCommand(IFullInputCmdMessage inputCmd)
|
||||
{
|
||||
DebugTools.AssertNotNull(_playerManager.LocalPlayer);
|
||||
|
||||
var keyFunc = _inputManager.NetworkBindMap.KeyFunctionName(inputCmd.InputFunctionId);
|
||||
|
||||
Predicted = true;
|
||||
var session = _playerManager.LocalPlayer!.Session;
|
||||
var session = _playerManager.LocalSession;
|
||||
foreach (var handler in BindRegistry.GetHandlers(keyFunc))
|
||||
{
|
||||
if (handler.HandleCmdMessage(EntityManager, session, inputCmd))
|
||||
@@ -145,27 +143,22 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void GenerateInputCommand(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
var localPlayer = _playerManager.LocalPlayer;
|
||||
if(localPlayer is null)
|
||||
return;
|
||||
|
||||
var pent = localPlayer.ControlledEntity;
|
||||
if(pent is null)
|
||||
if (_playerManager.LocalEntity is not { } pent)
|
||||
return;
|
||||
|
||||
BoundKeyFunction keyFunction = new BoundKeyFunction(args[0]);
|
||||
BoundKeyState state = args[1] == "u" ? BoundKeyState.Up: BoundKeyState.Down;
|
||||
|
||||
var pxform = Transform(pent.Value);
|
||||
var pxform = Transform(pent);
|
||||
var wPos = pxform.WorldPosition + new Vector2(float.Parse(args[2]), float.Parse(args[3]));
|
||||
var coords = EntityCoordinates.FromMap(EntityManager, pent.Value, new MapCoordinates(wPos, pxform.MapID));
|
||||
var coords = EntityCoordinates.FromMap(EntityManager, pent, new MapCoordinates(wPos, pxform.MapID));
|
||||
|
||||
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
|
||||
|
||||
var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state,
|
||||
GetNetCoordinates(coords), new ScreenCoordinates(0, 0, default), NetEntity.Invalid);
|
||||
|
||||
HandleInputCommand(localPlayer.Session, keyFunction, message);
|
||||
HandleInputCommand(_playerManager.LocalSession, keyFunction, message);
|
||||
}
|
||||
|
||||
private void OnAttachedEntityChanged(LocalPlayerAttachedEvent message)
|
||||
@@ -208,11 +201,8 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
public void SetEntityContextActive()
|
||||
{
|
||||
var controlled = _playerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
|
||||
if (controlled == EntityUid.Invalid)
|
||||
{
|
||||
if (_playerManager.LocalEntity is not { } controlled)
|
||||
return;
|
||||
}
|
||||
|
||||
SetEntityContextActive(_inputManager, controlled);
|
||||
}
|
||||
|
||||
@@ -8,30 +8,28 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class MapSystem : SharedMapSystem
|
||||
{
|
||||
public sealed class MapSystem : SharedMapSystem
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resource = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IClientResourceCache _resource = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
base.Initialize();
|
||||
_overlayManager.AddOverlay(new TileEdgeOverlay(EntityManager, _resource, _tileDefinitionManager));
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_overlayManager.AddOverlay(new TileEdgeOverlay(EntityManager, _mapManager, _resource, _tileDefinitionManager));
|
||||
}
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
|
||||
}
|
||||
|
||||
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
|
||||
{
|
||||
EnsureComp<PhysicsMapComponent>(uid);
|
||||
}
|
||||
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
|
||||
{
|
||||
EnsureComp<PhysicsMapComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Audio.Midi;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -8,6 +9,13 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Dependency] private readonly IMidiManager _midiManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
// AudioSystem sets eye position and rotation so rely on those.
|
||||
UpdatesAfter.Add(typeof(AudioSystem));
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
public sealed class PointLightSystem : SharedPointLightSystem
|
||||
{
|
||||
[Dependency] private readonly IClientResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly LightTreeSystem _lightTree = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -68,6 +68,10 @@ namespace Robust.Client.GameObjects
|
||||
return RemCompDeferred<PointLightComponent>(uid);
|
||||
}
|
||||
|
||||
protected override void UpdatePriority(EntityUid uid, SharedPointLightComponent comp, MetaDataComponent meta)
|
||||
{
|
||||
}
|
||||
|
||||
private void HandleInit(EntityUid uid, PointLightComponent component, ComponentInit args)
|
||||
{
|
||||
SetMask(component.MaskPath, component);
|
||||
@@ -95,28 +99,23 @@ namespace Robust.Client.GameObjects
|
||||
_lightTree.QueueTreeUpdate(uid, clientComp);
|
||||
}
|
||||
|
||||
public override void SetEnabled(EntityUid uid, bool enabled, SharedPointLightComponent? comp = null)
|
||||
public override void SetEnabled(EntityUid uid, bool enabled, SharedPointLightComponent? comp = null, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!ResolveLight(uid, ref comp) || enabled == comp.Enabled || comp is not PointLightComponent clientComp)
|
||||
return;
|
||||
|
||||
comp.Enabled = enabled;
|
||||
RaiseLocalEvent(uid, new PointLightToggleEvent(comp.Enabled));
|
||||
Dirty(uid, comp);
|
||||
|
||||
base.SetEnabled(uid, enabled, comp, meta);
|
||||
if (!comp.ContainerOccluded)
|
||||
_lightTree.QueueTreeUpdate(uid, clientComp);
|
||||
}
|
||||
|
||||
public override void SetRadius(EntityUid uid, float radius, SharedPointLightComponent? comp = null)
|
||||
public override void SetRadius(EntityUid uid, float radius, SharedPointLightComponent? comp = null, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!ResolveLight(uid, ref comp) || MathHelper.CloseToPercent(radius, comp.Radius) ||
|
||||
comp is not PointLightComponent clientComp)
|
||||
return;
|
||||
|
||||
comp.Radius = radius;
|
||||
Dirty(uid, comp);
|
||||
|
||||
base.SetRadius(uid, radius, comp, meta);
|
||||
if (clientComp.TreeUid != null)
|
||||
_lightTree.QueueTreeUpdate(uid, clientComp);
|
||||
}
|
||||
|
||||
@@ -122,14 +122,13 @@ public sealed partial class SpriteSystem
|
||||
return GetFallbackState();
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs protoReloaded)
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
|
||||
{
|
||||
// Check if any EntityPrototype has been changed.
|
||||
if (!protoReloaded.ByType.TryGetValue(typeof(EntityPrototype), out var changedSet))
|
||||
if (!args.TryGetModified<EntityPrototype>(out var modified))
|
||||
return;
|
||||
|
||||
// Remove all changed prototypes from the cache, if they're there.
|
||||
foreach (var (prototype, _) in changedSet.Modified)
|
||||
foreach (var prototype in modified)
|
||||
{
|
||||
// Let's be lazy and not regenerate them until something needs them again.
|
||||
_cachedPrototypeIcons.Remove(prototype);
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IClientResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
|
||||
@@ -58,11 +58,11 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
UpdatesAfter.Add(typeof(SpriteTreeSystem));
|
||||
|
||||
_proto.PrototypesReloaded += OnPrototypesReloaded;
|
||||
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
|
||||
SubscribeLocalEvent<SpriteComponent, SpriteUpdateInertEvent>(QueueUpdateInert);
|
||||
SubscribeLocalEvent<SpriteComponent, ComponentInit>(OnInit);
|
||||
|
||||
_cfg.OnValueChanged(CVars.RenderSpriteDirectionBias, OnBiasChanged, true);
|
||||
Subs.CVar(_cfg, CVars.RenderSpriteDirectionBias, OnBiasChanged, true);
|
||||
_sawmill = _logManager.GetSawmill("sprite");
|
||||
}
|
||||
|
||||
@@ -72,13 +72,6 @@ namespace Robust.Client.GameObjects
|
||||
QueueUpdateInert(uid, component);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_proto.PrototypesReloaded -= OnPrototypesReloaded;
|
||||
_cfg.UnsubValueChanged(CVars.RenderSpriteDirectionBias, OnBiasChanged);
|
||||
}
|
||||
|
||||
private void OnBiasChanged(double value)
|
||||
{
|
||||
SpriteComponent.DirectionBias = value;
|
||||
|
||||
@@ -54,9 +54,10 @@ namespace Robust.Client.GameObjects
|
||||
// should show the entity lerping.
|
||||
// - If the client predicts an entity will move while already lerping due to a state-application, it should
|
||||
// clear the state's lerp, under the assumption that the client predicted the state and already rendered
|
||||
// the entity in the final position.
|
||||
// the entity in the state's final position.
|
||||
// - If the client predicts that an entity moves, then we only lerp if this is the first time that the tick
|
||||
// was predicted. I.e., we assume the entity was already rendered in it's final of that lerp.
|
||||
// was predicted. I.e., we assume the entity was already rendered in the final position that was
|
||||
// previously predicted.
|
||||
// - If the client predicts that an entity should lerp twice in the same tick, then we need to combine them.
|
||||
// I.e. moving from a->b then b->c, the client should lerp from a->c.
|
||||
|
||||
|
||||
@@ -29,10 +29,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var uiKey = ev.UiKey;
|
||||
var message = ev.Message;
|
||||
// This should probably not happen at this point, but better make extra sure!
|
||||
if (_playerManager.LocalPlayer != null)
|
||||
message.Session = _playerManager.LocalPlayer.Session;
|
||||
|
||||
message.Session = _playerManager.LocalSession!;
|
||||
message.Entity = GetNetEntity(uid);
|
||||
message.UiKey = uiKey;
|
||||
|
||||
@@ -75,8 +72,7 @@ namespace Robust.Client.GameObjects
|
||||
boundInterface.Open();
|
||||
uiComp.OpenInterfaces[uiKey] = boundInterface;
|
||||
|
||||
var playerSession = _playerManager.LocalPlayer?.Session;
|
||||
if (playerSession != null)
|
||||
if (_playerManager.LocalSession is { } playerSession)
|
||||
{
|
||||
uiComp.Interfaces[uiKey]._subscribedSessions.Add(playerSession);
|
||||
RaiseLocalEvent(uid, new BoundUIOpenedEvent(uiKey, uid, playerSession), true);
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
var player = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
var player = _playerManager.LocalEntity;
|
||||
|
||||
if (player == null || !EntityManager.TryGetComponent(player.Value, out PhysicsComponent? body))
|
||||
{
|
||||
|
||||
@@ -43,7 +43,7 @@ public sealed class ClientDirtySystem : EntitySystem
|
||||
return;
|
||||
|
||||
// Client-side entity deletion is not supported and will cause errors.
|
||||
Log.Error($"Predicting the deletion of a networked entity: {ToPrettyString(ev.Entity)}. Trace: {Environment.StackTrace}");
|
||||
Log.Error($"Predicting the deletion of a networked entity: {ToPrettyString(ev.Entity.Owner, ev.Entity.Comp)}. Trace: {Environment.StackTrace}");
|
||||
}
|
||||
|
||||
private void OnCompRemoved(RemovedComponentEventArgs args)
|
||||
@@ -71,9 +71,9 @@ public sealed class ClientDirtySystem : EntitySystem
|
||||
RemovedComponents.Clear();
|
||||
}
|
||||
|
||||
private void OnEntityDirty(EntityUid e)
|
||||
private void OnEntityDirty(Entity<MetaDataComponent> e)
|
||||
{
|
||||
if (_timing.InPrediction && !IsClientSide(e))
|
||||
if (_timing.InPrediction && !IsClientSide(e, e))
|
||||
DirtyEntities.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,16 +49,16 @@ namespace Robust.Client.GameStates
|
||||
// Game state dictionaries that get used every tick.
|
||||
private readonly Dictionary<EntityUid, (NetEntity NetEntity, MetaDataComponent Meta, bool EnteringPvs, GameTick LastApplied, EntityState? curState, EntityState? nextState)> _toApply = new();
|
||||
private readonly Dictionary<NetEntity, EntityState> _toCreate = new();
|
||||
private readonly Dictionary<ushort, (IComponent Component, ComponentState? curState, ComponentState? nextState)> _compStateWork = new();
|
||||
private readonly Dictionary<ushort, (IComponent Component, IComponentState? curState, IComponentState? nextState)> _compStateWork = new();
|
||||
private readonly Dictionary<EntityUid, HashSet<Type>> _pendingReapplyNetStates = new();
|
||||
private readonly HashSet<NetEntity> _stateEnts = new();
|
||||
private readonly List<EntityUid> _toDelete = new();
|
||||
private readonly List<IComponent> _toRemove = new();
|
||||
private readonly Dictionary<NetEntity, Dictionary<ushort, ComponentState>> _outputData = new();
|
||||
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState>> _outputData = new();
|
||||
private readonly List<(EntityUid, TransformComponent)> _queuedBroadphaseUpdates = new();
|
||||
|
||||
private readonly ObjectPool<Dictionary<ushort, ComponentState>> _compDataPool =
|
||||
new DefaultObjectPool<Dictionary<ushort, ComponentState>>(new DictPolicy<ushort, ComponentState>(), 256);
|
||||
private readonly ObjectPool<Dictionary<ushort, IComponentState>> _compDataPool =
|
||||
new DefaultObjectPool<Dictionary<ushort, IComponentState>>(new DictPolicy<ushort, IComponentState>(), 256);
|
||||
|
||||
private uint _metaCompNetId;
|
||||
|
||||
@@ -124,6 +124,8 @@ namespace Robust.Client.GameStates
|
||||
public bool DropStates;
|
||||
#endif
|
||||
|
||||
private bool _resettingPredictedEntities;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
@@ -146,6 +148,7 @@ namespace Robust.Client.GameStates
|
||||
_config.OnValueChanged(CVars.NetPredictLagBias, i => PredictLagBias = i, true);
|
||||
_config.OnValueChanged(CVars.NetStateBufMergeThreshold, i => StateBufferMergeThreshold = i, true);
|
||||
_config.OnValueChanged(CVars.NetPVSEntityExitBudget, i => _pvsDetachBudget = i, true);
|
||||
_config.OnValueChanged(CVars.NetMaxBufferSize, i => _processor.MaxBufferSize = i, true);
|
||||
|
||||
_processor.Interpolation = _config.GetCVar(CVars.NetInterp);
|
||||
_processor.BufferSize = _config.GetCVar(CVars.NetBufferSize);
|
||||
@@ -160,6 +163,8 @@ namespace Robust.Client.GameStates
|
||||
_conHost.RegisterCommand("localdelete", Loc.GetString("cmd-local-delete-desc"), Loc.GetString("cmd-local-delete-help"), LocalDeleteEntCommand);
|
||||
_conHost.RegisterCommand("fullstatereset", Loc.GetString("cmd-full-state-reset-desc"), Loc.GetString("cmd-full-state-reset-help"), (_,_,_) => RequestFullState());
|
||||
|
||||
_entities.ComponentAdded += OnComponentAdded;
|
||||
|
||||
var metaId = _compFactory.GetRegistration(typeof(MetaDataComponent)).NetID;
|
||||
if (!metaId.HasValue)
|
||||
throw new InvalidOperationException("MetaDataComponent does not have a NetId.");
|
||||
@@ -167,6 +172,25 @@ namespace Robust.Client.GameStates
|
||||
_metaCompNetId = metaId.Value;
|
||||
}
|
||||
|
||||
private void OnComponentAdded(AddedComponentEventArgs args)
|
||||
{
|
||||
if (!_resettingPredictedEntities)
|
||||
return;
|
||||
|
||||
var comp = args.ComponentType;
|
||||
if (comp.NetID == null)
|
||||
return;
|
||||
|
||||
if (_entityManager.IsClientSide(args.BaseArgs.Owner))
|
||||
return;
|
||||
|
||||
_sawmill.Error($"""
|
||||
Added component {comp.Name} to entity {_entityManager.ToPrettyString(args.BaseArgs.Owner)} while resetting predicted entities.
|
||||
Stack trace:
|
||||
{Environment.StackTrace}
|
||||
""");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
@@ -208,9 +232,9 @@ namespace Robust.Client.GameStates
|
||||
return default;
|
||||
}
|
||||
|
||||
DebugTools.AssertNotNull(_players.LocalPlayer);
|
||||
DebugTools.Assert(_players.LocalSession != null);
|
||||
|
||||
var evArgs = new EntitySessionEventArgs(_players.LocalPlayer!.Session);
|
||||
var evArgs = new EntitySessionEventArgs(_players.LocalSession);
|
||||
_pendingSystemMessages.Enqueue((_nextInputCmdSeq, _timing.CurTick, message,
|
||||
new EntitySessionMessage<T>(evArgs, message)));
|
||||
|
||||
@@ -233,7 +257,7 @@ namespace Robust.Client.GameStates
|
||||
public void UpdateFullRep(GameState state, bool cloneDelta = false)
|
||||
=> _processor.UpdateFullRep(state, cloneDelta);
|
||||
|
||||
public Dictionary<NetEntity, Dictionary<ushort, ComponentState>> GetFullRep()
|
||||
public Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep()
|
||||
=> _processor.GetFullRep();
|
||||
|
||||
private void HandlePvsLeaveMessage(MsgStateLeavePvs message)
|
||||
@@ -319,7 +343,7 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
// If we were waiting for a new state, we are now applying it.
|
||||
if (_processor.WaitingForFull)
|
||||
if (curState.FromSequence == GameTick.Zero)
|
||||
{
|
||||
_processor.OnFullStateReceived();
|
||||
_timing.LastProcessedTick = curState.ToSequence;
|
||||
@@ -327,7 +351,10 @@ namespace Robust.Client.GameStates
|
||||
PartialStateReset(curState, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugTools.Assert(!_processor.WaitingForFull);
|
||||
_timing.LastProcessedTick += 1;
|
||||
}
|
||||
|
||||
_timing.CurTick = _timing.LastRealTick = _timing.LastProcessedTick;
|
||||
|
||||
@@ -508,7 +535,6 @@ namespace Robust.Client.GameStates
|
||||
using var __ = _timing.StartStateApplicationArea();
|
||||
|
||||
// This is terrible, and I hate it. This also needs to run even when prediction is disabled.
|
||||
_entitySystemManager.GetEntitySystem<SharedGridTraversalSystem>().QueuedEvents.Clear();
|
||||
_entitySystemManager.GetEntitySystem<TransformSystem>().Reset();
|
||||
|
||||
if (!PredictionNeedsResetting)
|
||||
@@ -536,40 +562,50 @@ namespace Robust.Client.GameStates
|
||||
|
||||
countReset += 1;
|
||||
|
||||
foreach (var (netId, comp) in meta.NetComponents)
|
||||
try
|
||||
{
|
||||
if (!comp.NetSyncEnabled)
|
||||
continue;
|
||||
_resettingPredictedEntities = true;
|
||||
|
||||
// Was this component added during prediction?
|
||||
if (comp.CreationTick > _timing.LastRealTick)
|
||||
foreach (var (netId, comp) in meta.NetComponents)
|
||||
{
|
||||
if (last.ContainsKey(netId))
|
||||
if (!comp.NetSyncEnabled)
|
||||
continue;
|
||||
|
||||
// Was this component added during prediction?
|
||||
if (comp.CreationTick > _timing.LastRealTick)
|
||||
{
|
||||
// Component was probably removed and then re-addedd during a single prediction run
|
||||
// Just reset state as normal.
|
||||
comp.ClearCreationTick();
|
||||
if (last.ContainsKey(netId))
|
||||
{
|
||||
// Component was probably removed and then re-addedd during a single prediction run
|
||||
// Just reset state as normal.
|
||||
comp.ClearCreationTick();
|
||||
}
|
||||
else
|
||||
{
|
||||
toRemove.Add(comp);
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A new component was added: {comp.GetType()}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (comp.LastModifiedTick <= _timing.LastRealTick ||
|
||||
!last.TryGetValue(netId, out var compState))
|
||||
{
|
||||
toRemove.Add(comp);
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A new component was added: {comp.GetType()}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A component was dirtied: {comp.GetType()}");
|
||||
|
||||
var handleState = new ComponentHandleState(compState, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
|
||||
comp.LastModifiedTick = _timing.LastRealTick;
|
||||
}
|
||||
|
||||
if (comp.LastModifiedTick <= _timing.LastRealTick || !last.TryGetValue(netId, out var compState))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A component was dirtied: {comp.GetType()}");
|
||||
|
||||
var handleState = new ComponentHandleState(compState, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
|
||||
comp.LastModifiedTick = _timing.LastRealTick;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_resettingPredictedEntities = false;
|
||||
}
|
||||
|
||||
// Remove predicted component additions
|
||||
@@ -631,14 +667,13 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var netEntity in createdEntities)
|
||||
{
|
||||
var (createdEntity, meta) = _entityManager.GetEntityData(netEntity);
|
||||
var (_, meta) = _entityManager.GetEntityData(netEntity);
|
||||
var compData = _compDataPool.Get();
|
||||
_outputData.Add(netEntity, compData);
|
||||
|
||||
foreach (var (netId, component) in meta.NetComponents)
|
||||
{
|
||||
if (!component.NetSyncEnabled)
|
||||
continue;
|
||||
DebugTools.Assert(component.NetSyncEnabled);
|
||||
|
||||
var state = _entityManager.GetComponentState(bus, component, null, GameTick.Zero);
|
||||
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
|
||||
@@ -744,15 +779,13 @@ namespace Robust.Client.GameStates
|
||||
newMeta.LastStateApplied = curState.ToSequence;
|
||||
|
||||
// Check if there's any component states awaiting this entity.
|
||||
if (_entityManager.PendingNetEntityStates.TryGetValue(es.NetEntity, out var value))
|
||||
if (_entityManager.PendingNetEntityStates.Remove(es.NetEntity, out var value))
|
||||
{
|
||||
foreach (var (type, owner) in value)
|
||||
{
|
||||
var pending = _pendingReapplyNetStates.GetOrNew(owner);
|
||||
pending.Add(type);
|
||||
}
|
||||
|
||||
_entityManager.PendingNetEntityStates.Remove(es.NetEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,6 +849,17 @@ namespace Robust.Client.GameStates
|
||||
if (!metas.TryGetComponent(uid, out var meta))
|
||||
continue;
|
||||
|
||||
// It may also have been queued for deletion, in which case its last server state entry has already been removed.
|
||||
// I love me some spaghetti order-of-operation dependent code
|
||||
|
||||
if (!_processor._lastStateFullRep.ContainsKey(meta.NetEntity))
|
||||
{
|
||||
DebugTools.Assert(curState.EntityDeletions.Value.Contains(meta.NetEntity));
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(!curState.EntityDeletions.Value.Contains(meta.NetEntity));
|
||||
|
||||
// State already being re-applied so don't bulldoze it.
|
||||
ref var state = ref CollectionsMarshal.GetValueRefOrAddDefault(_toApply, uid, out var exists);
|
||||
|
||||
@@ -949,16 +993,15 @@ namespace Robust.Client.GameStates
|
||||
xformSys.DetachParentToNull(ent, xform);
|
||||
|
||||
// Then detach all children.
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
xformSys.DetachParentToNull(child.Value, xforms.GetComponent(child.Value), xform);
|
||||
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
|
||||
|
||||
if (deleteClientChildren
|
||||
&& !deleteClientEntities // don't add duplicates
|
||||
&& _entities.IsClientSide(child.Value))
|
||||
&& _entities.IsClientSide(child))
|
||||
{
|
||||
_toDelete.Add(child.Value);
|
||||
_toDelete.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1006,7 +1049,7 @@ namespace Robust.Client.GameStates
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
xformSys.DetachParentToNull(child.Value, xforms.GetComponent(child.Value), xform);
|
||||
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
|
||||
}
|
||||
|
||||
// Finally, delete the entity.
|
||||
@@ -1097,7 +1140,7 @@ namespace Robust.Client.GameStates
|
||||
(containerMeta.Flags & MetaDataFlags.Detached) == 0 &&
|
||||
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container, null, true))
|
||||
{
|
||||
container.Remove(ent.Value, _entities, xform, meta, false, true);
|
||||
containerSys.Remove((ent.Value, xform, meta), container, false, true);
|
||||
}
|
||||
|
||||
meta._flags |= MetaDataFlags.Detached;
|
||||
@@ -1185,7 +1228,9 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var (id, comp) in meta.NetComponents)
|
||||
{
|
||||
if (comp.NetSyncEnabled && !curState.NetComponents.Contains(id))
|
||||
DebugTools.Assert(comp.NetSyncEnabled);
|
||||
|
||||
if (!curState.NetComponents.Contains(id))
|
||||
_toRemove.Add(comp);
|
||||
}
|
||||
|
||||
@@ -1395,7 +1440,7 @@ namespace Robust.Client.GameStates
|
||||
void _recursiveRemoveState(NetEntity netEntity, TransformComponent xform, EntityQuery<MetaDataComponent> metaQuery, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
_processor._lastStateFullRep.Remove(netEntity);
|
||||
foreach (var child in xform.ChildEntities)
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
if (xformQuery.TryGetComponent(child, out var childXform) &&
|
||||
metaQuery.TryGetComponent(child, out var childMeta))
|
||||
|
||||
@@ -14,8 +14,6 @@ namespace Robust.Client.GameStates
|
||||
/// <inheritdoc />
|
||||
internal sealed class GameStateProcessor : IGameStateProcessor
|
||||
{
|
||||
public const int MaxBufferSize = 512;
|
||||
|
||||
private readonly IClientGameTiming _timing;
|
||||
private readonly IClientGameStateManager _state;
|
||||
private readonly ISawmill _logger;
|
||||
@@ -28,11 +26,13 @@ namespace Robust.Client.GameStates
|
||||
public (GameTick Tick, DateTime Time)? LastFullStateRequested { get; private set; } = (GameTick.Zero, DateTime.MaxValue);
|
||||
|
||||
private int _bufferSize;
|
||||
private int _maxBufferSize = 512;
|
||||
public const int MinimumMaxBufferSize = 256;
|
||||
|
||||
/// <summary>
|
||||
/// This dictionary stores the full most recently received server state of any entity. This is used whenever predicted entities get reset.
|
||||
/// </summary>
|
||||
internal readonly Dictionary<NetEntity, Dictionary<ushort, ComponentState>> _lastStateFullRep
|
||||
internal readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState>> _lastStateFullRep
|
||||
= new();
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -48,7 +48,14 @@ namespace Robust.Client.GameStates
|
||||
public int BufferSize
|
||||
{
|
||||
get => _bufferSize;
|
||||
set => _bufferSize = value < 0 ? 0 : value;
|
||||
set => _bufferSize = Math.Max(value, 0);
|
||||
}
|
||||
|
||||
public int MaxBufferSize
|
||||
{
|
||||
get => _maxBufferSize;
|
||||
// We place a lower bound on the maximum size to avoid spamming servers with full game state requests.
|
||||
set => _maxBufferSize = Math.Max(value, MinimumMaxBufferSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -100,21 +107,21 @@ namespace Robust.Client.GameStates
|
||||
return true;
|
||||
}
|
||||
|
||||
if (LastFullState == null && state.FromSequence == GameTick.Zero && state.ToSequence >= LastFullStateRequested!.Value.Tick)
|
||||
if (LastFullState == null && state.FromSequence == GameTick.Zero)
|
||||
{
|
||||
LastFullState = state;
|
||||
|
||||
if (Logging)
|
||||
if (state.ToSequence >= LastFullStateRequested!.Value.Tick)
|
||||
{
|
||||
LastFullState = state;
|
||||
_logger.Info($"Received Full GameState: to={state.ToSequence}, sz={state.PayloadSize}");
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
_logger.Info($"Received a late full game state. Received: {state.ToSequence}. Requested: {LastFullStateRequested.Value.Tick}");
|
||||
}
|
||||
|
||||
if (LastFullState != null && state.ToSequence <= LastFullState.ToSequence)
|
||||
{
|
||||
if (Logging)
|
||||
_logger.Info($"While waiting for full, received late GameState with lower to={state.ToSequence} than the last full state={LastFullState.ToSequence}");
|
||||
|
||||
_logger.Info($"While waiting for full, received late GameState with lower to={state.ToSequence} than the last full state={LastFullState.ToSequence}");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -205,7 +212,7 @@ Had full state: {LastFullState != null}"
|
||||
{
|
||||
if (!_lastStateFullRep.TryGetValue(entityState.NetEntity, out var compData))
|
||||
{
|
||||
compData = new Dictionary<ushort, ComponentState>();
|
||||
compData = new Dictionary<ushort, IComponentState>();
|
||||
_lastStateFullRep.Add(entityState.NetEntity, compData);
|
||||
}
|
||||
|
||||
@@ -384,7 +391,7 @@ Had full state: {LastFullState != null}"
|
||||
LastFullStateRequested = null;
|
||||
}
|
||||
|
||||
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, ComponentState>> implicitData)
|
||||
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState>> implicitData)
|
||||
{
|
||||
foreach (var (netEntity, implicitEntState) in implicitData)
|
||||
{
|
||||
@@ -418,18 +425,18 @@ Had full state: {LastFullState != null}"
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<ushort, ComponentState> GetLastServerStates(NetEntity netEntity)
|
||||
public Dictionary<ushort, IComponentState> GetLastServerStates(NetEntity netEntity)
|
||||
{
|
||||
return _lastStateFullRep[netEntity];
|
||||
}
|
||||
|
||||
public Dictionary<NetEntity, Dictionary<ushort, ComponentState>> GetFullRep()
|
||||
public Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep()
|
||||
{
|
||||
return _lastStateFullRep;
|
||||
}
|
||||
|
||||
public bool TryGetLastServerStates(NetEntity entity,
|
||||
[NotNullWhen(true)] out Dictionary<ushort, ComponentState>? dictionary)
|
||||
[NotNullWhen(true)] out Dictionary<ushort, IComponentState>? dictionary)
|
||||
{
|
||||
return _lastStateFullRep.TryGetValue(entity, out dictionary);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace Robust.Client.GameStates
|
||||
/// <summary>
|
||||
/// Returns the full collection of cached game states that are used to reset predicted entities.
|
||||
/// </summary>
|
||||
Dictionary<NetEntity, Dictionary<ushort, ComponentState>> GetFullRep();
|
||||
Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep();
|
||||
|
||||
/// <summary>
|
||||
/// This will perform some setup in order to reset the game to an earlier state. To fully reset the state
|
||||
|
||||
@@ -83,13 +83,13 @@ namespace Robust.Client.GameStates
|
||||
/// The data to merge.
|
||||
/// It's a dictionary of entity ID -> (component net ID -> ComponentState)
|
||||
/// </param>
|
||||
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, ComponentState>> data);
|
||||
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState>> data);
|
||||
|
||||
/// <summary>
|
||||
/// Get the last state data from the server for an entity.
|
||||
/// </summary>
|
||||
/// <returns>Dictionary (net ID -> ComponentState)</returns>
|
||||
Dictionary<ushort, ComponentState> GetLastServerStates(NetEntity entity);
|
||||
Dictionary<ushort, IComponentState> GetLastServerStates(NetEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the number of applicable states in the game state buffer from a given tick.
|
||||
@@ -99,6 +99,6 @@ namespace Robust.Client.GameStates
|
||||
int GetApplicableStateCount(GameTick? fromTick);
|
||||
|
||||
bool TryGetLastServerStates(NetEntity entity,
|
||||
[NotNullWhen(true)] out Dictionary<ushort, ComponentState>? dictionary);
|
||||
[NotNullWhen(true)] out Dictionary<ushort, IComponentState>? dictionary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Robust.Client.GameStates
|
||||
public NetEntityOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
var cache = IoCManager.Resolve<IClientResourceCache>();
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
_lineHeight = _font.GetLineHeight(1);
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Client.GameStates
|
||||
public NetGraphOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
var cache = IoCManager.Resolve<IClientResourceCache>();
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
|
||||
_gameStateManager.GameStateApplied += HandleGameStateApplied;
|
||||
@@ -313,7 +313,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
entity = _playerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
|
||||
entity = _playerManager.LocalEntity ?? EntityUid.Invalid;
|
||||
}
|
||||
else if (!NetEntity.TryParse(args[0], out var netEntity) || !_entManager.TryGetEntity(netEntity, out entity))
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user