mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
261 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18c32a0258 | ||
|
|
72314a102d | ||
|
|
719ea26a31 | ||
|
|
5cb8fe1897 | ||
|
|
f35a52fc24 | ||
|
|
6bdb0cef47 | ||
|
|
fe3c9fe28f | ||
|
|
6085671f22 | ||
|
|
a2398da324 | ||
|
|
b27304cc58 | ||
|
|
3bf851a6cf | ||
|
|
cef92efd0f | ||
|
|
c5961a5ab1 | ||
|
|
8ddd92993d | ||
|
|
da253a5f34 | ||
|
|
ca9400a1ff | ||
|
|
f232195ceb | ||
|
|
b54a803519 | ||
|
|
a0d3d2108f | ||
|
|
977e4a017b | ||
|
|
2d8b159016 | ||
|
|
9caa0dde4b | ||
|
|
7a5a8c5eb1 | ||
|
|
95ba58f0a4 | ||
|
|
f780f04784 | ||
|
|
85782bda92 | ||
|
|
14a01df5b1 | ||
|
|
644da60bfc | ||
|
|
8c83999ad2 | ||
|
|
24b9fc9eec | ||
|
|
ba40185179 | ||
|
|
8b013cb424 | ||
|
|
b67d24efee | ||
|
|
d992e47f30 | ||
|
|
dadd7b4cc3 | ||
|
|
baef2bc7f8 | ||
|
|
e0b1a7d64a | ||
|
|
aea5f83002 | ||
|
|
7df2d1f430 | ||
|
|
d216c3a1f6 | ||
|
|
986ec3ef06 | ||
|
|
60cec9cb84 | ||
|
|
c06707d519 | ||
|
|
63128324ab | ||
|
|
abea3024b4 | ||
|
|
07dafeb6cd | ||
|
|
a726d42ae3 | ||
|
|
d02d186a2f | ||
|
|
a6be66949d | ||
|
|
0dfd3b7443 | ||
|
|
6f90e9a76e | ||
|
|
9246b88560 | ||
|
|
61af9db362 | ||
|
|
5aa950e7f7 | ||
|
|
b8903af52f | ||
|
|
5b35c4e82b | ||
|
|
06db80780c | ||
|
|
e5fd4f5700 | ||
|
|
e1a199e060 | ||
|
|
22ac34c7a1 | ||
|
|
04fa6901b1 | ||
|
|
8f7d4211e3 | ||
|
|
1147d6fd9a | ||
|
|
e4449c4901 | ||
|
|
dc8963faa5 | ||
|
|
f9cf9a8fd4 | ||
|
|
393bdc04bc | ||
|
|
db4d787b49 | ||
|
|
cd802d6a66 | ||
|
|
be2281a5b3 | ||
|
|
e717396b33 | ||
|
|
db0e49f5dd | ||
|
|
d3b94aa6af | ||
|
|
713f702064 | ||
|
|
d0b6a9b28c | ||
|
|
4003781a1b | ||
|
|
40586a8f0e | ||
|
|
f4d427f5c5 | ||
|
|
ce2a4282f3 | ||
|
|
0cc78c1402 | ||
|
|
fa0d4da6d1 | ||
|
|
33334d6f5c | ||
|
|
55aba93faf | ||
|
|
4f49296a94 | ||
|
|
65357ee8da | ||
|
|
bfcad4001b | ||
|
|
5a3000febb | ||
|
|
2d236670ab | ||
|
|
796f1480a8 | ||
|
|
a8ee7be844 | ||
|
|
60b506cb2a | ||
|
|
66117e35ba | ||
|
|
22cfee4f01 | ||
|
|
d9355e576f | ||
|
|
e54e33edb0 | ||
|
|
25881ce343 | ||
|
|
f64197c189 | ||
|
|
a7ec907f1d | ||
|
|
81272b0bc8 | ||
|
|
25c1c6ef91 | ||
|
|
d1a83134e3 | ||
|
|
71c2993d20 | ||
|
|
14fa616723 | ||
|
|
d46ea9c9ba | ||
|
|
ea00ccb34f | ||
|
|
8e416bb3cb | ||
|
|
8e0a26073d | ||
|
|
9fa24948ea | ||
|
|
b9a9cc4b0f | ||
|
|
a67345f9bf | ||
|
|
2a022f39bd | ||
|
|
a3ba969589 | ||
|
|
21e1b75f5d | ||
|
|
1f6ddd96a6 | ||
|
|
b4d2cd26aa | ||
|
|
ee6aee57bd | ||
|
|
ac1705e41f | ||
|
|
196a6047f1 | ||
|
|
b98b36a461 | ||
|
|
9980a98a94 | ||
|
|
5a1a1d0420 | ||
|
|
1c99678fe9 | ||
|
|
4049f10faa | ||
|
|
331c0bd43f | ||
|
|
604cd2f93d | ||
|
|
a87db2e57c | ||
|
|
4c7f0a8d6b | ||
|
|
9eaf52886a | ||
|
|
fce6f6c714 | ||
|
|
c04d51d489 | ||
|
|
d310871aa6 | ||
|
|
5fa422865f | ||
|
|
c137823355 | ||
|
|
0a0026b9ae | ||
|
|
97a2a5cfae | ||
|
|
a266e21d9e | ||
|
|
ad8bbe6401 | ||
|
|
71ce4749fe | ||
|
|
1e33c3c843 | ||
|
|
b3f3ca9725 | ||
|
|
33c37393ba | ||
|
|
ae36529744 | ||
|
|
ed2be48864 | ||
|
|
7ad5ce302e | ||
|
|
647f1a6808 | ||
|
|
6c44dd9665 | ||
|
|
c81413b0b4 | ||
|
|
88b3a557da | ||
|
|
572eb01290 | ||
|
|
9dab74c9d5 | ||
|
|
e1cb1e1b9c | ||
|
|
a23da702b1 | ||
|
|
ae9c2423ff | ||
|
|
a6dae8e30a | ||
|
|
96c0a4ae1f | ||
|
|
c26ebcbc78 | ||
|
|
8334050411 | ||
|
|
cc67edfc2c | ||
|
|
943ea9e6c8 | ||
|
|
3aa5cefe03 | ||
|
|
c5b34bab69 | ||
|
|
e4f24ec125 | ||
|
|
250971ade7 | ||
|
|
718adf9740 | ||
|
|
5d63aa8c95 | ||
|
|
17af3612a5 | ||
|
|
d2ecf6b9b1 | ||
|
|
2a1eda0d38 | ||
|
|
f0180abeb0 | ||
|
|
720f1b1d05 | ||
|
|
ae45a96753 | ||
|
|
74257c72ee | ||
|
|
cea088f4b4 | ||
|
|
88678e7d58 | ||
|
|
d222e25d22 | ||
|
|
f0d7fbb6f2 | ||
|
|
adec0e71ec | ||
|
|
86d9067d62 | ||
|
|
b989c9dbee | ||
|
|
3101ec1985 | ||
|
|
9ec2da1777 | ||
|
|
0acd28e8f4 | ||
|
|
c340f50ee5 | ||
|
|
2b589215aa | ||
|
|
490a567ad4 | ||
|
|
32f0c49484 | ||
|
|
61113d2434 | ||
|
|
6ba1baa88c | ||
|
|
07867acb9a | ||
|
|
3e28b083b9 | ||
|
|
68d9e13edf | ||
|
|
a0c0a722c9 | ||
|
|
bf4aead1e8 | ||
|
|
39f0d2e974 | ||
|
|
b20ae98f21 | ||
|
|
7899780543 | ||
|
|
0c9e322b3e | ||
|
|
6005208285 | ||
|
|
2b15831349 | ||
|
|
1b2450d1cb | ||
|
|
5f31036ab2 | ||
|
|
8efffc471d | ||
|
|
eb9e842027 | ||
|
|
f9cd9ac12a | ||
|
|
8fd98c75a9 | ||
|
|
7c008e857d | ||
|
|
4de2e35e66 | ||
|
|
d4467acf93 | ||
|
|
f3babcc39f | ||
|
|
f491bb5571 | ||
|
|
b201f10c76 | ||
|
|
a9208c0d29 | ||
|
|
b8cc01d872 | ||
|
|
2d827890e9 | ||
|
|
f86d6ccd3c | ||
|
|
967b76483a | ||
|
|
ef2c0ad8cf | ||
|
|
9ae1352030 | ||
|
|
d8d9b271cc | ||
|
|
122acc5fd5 | ||
|
|
32dea84196 | ||
|
|
91d58dbca4 | ||
|
|
c54b1572f5 | ||
|
|
1fa979c0f6 | ||
|
|
760599171d | ||
|
|
10d295d535 | ||
|
|
0047c5000f | ||
|
|
810a6d190f | ||
|
|
197227dcf6 | ||
|
|
fa23ec8fc6 | ||
|
|
6506171ea0 | ||
|
|
8bd1e72e9f | ||
|
|
4ce6629ace | ||
|
|
f9ef605903 | ||
|
|
c6b74e998f | ||
|
|
c4946b8466 | ||
|
|
ffa908bf27 | ||
|
|
0d37ff3f20 | ||
|
|
7aecdcf70a | ||
|
|
70f82d6db8 | ||
|
|
20b7870739 | ||
|
|
172639baea | ||
|
|
6038483b1e | ||
|
|
39d98d591c | ||
|
|
01c2fc0730 | ||
|
|
1884bb0067 | ||
|
|
1c368bbaa8 | ||
|
|
d16078a35f | ||
|
|
4dd04207ac | ||
|
|
02af42da30 | ||
|
|
2c75c8b36d | ||
|
|
013e6f7ce4 | ||
|
|
cbd7b62ad7 | ||
|
|
c1396f1c50 | ||
|
|
3ec9e7a734 | ||
|
|
3a1e6e84b1 | ||
|
|
7224419f77 | ||
|
|
056e4de0c1 | ||
|
|
aa90f22e23 | ||
|
|
071234095d | ||
|
|
5b06391159 |
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
||||
2
.github/workflows/test-content.yml
vendored
2
.github/workflows/test-content.yml
vendored
@@ -38,4 +38,4 @@ jobs:
|
||||
- name: Content.Tests
|
||||
run: dotnet test --no-build Content.Tests/Content.Tests.csproj -v n
|
||||
- name: Content.IntegrationTests
|
||||
run: dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
|
||||
run: COMPlus_gcServer=1 dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -13,3 +13,6 @@
|
||||
[submodule "ManagedHttpListener"]
|
||||
path = ManagedHttpListener
|
||||
url = https://github.com/space-wizards/ManagedHttpListener.git
|
||||
[submodule "Linguini"]
|
||||
path = Linguini
|
||||
url = https://github.com/space-wizards/Linguini
|
||||
|
||||
7
Avalonia.Base/Avalonia.Base.csproj
Normal file
7
Avalonia.Base/Avalonia.Base.csproj
Normal file
@@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
2
Avalonia.Base/README.md
Normal file
2
Avalonia.Base/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
See `Robust.Client/UserInterface/XAML/RiderNotes.md` for why this project exists.
|
||||
We are not actually using Avalonia (yet).
|
||||
1
Linguini
Submodule
1
Linguini
Submodule
Submodule Linguini added at 62b0e75b91
@@ -4,5 +4,6 @@
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>9</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -23,12 +23,12 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return NativeLibrary.Load("libglfw.so.3", assembly, path);
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
return NativeLibrary.Load("libglfw.3.dylib", assembly, path);
|
||||
}
|
||||
|
||||
@@ -14,12 +14,6 @@
|
||||
<PackageReference Condition="'$(TargetFramework)' == 'net472'" Include="System.Memory" Version="4.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="OpenToolkit.GraphicsLibraryFramework.dll.config">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<configuration>
|
||||
<!-- I actually have no idea whether this works on FreeBSD but it can't hurt to set it as such. -->
|
||||
<dllmap os="linux,freebsd" dll="glfw3.dll" target="glfw.so.3" />
|
||||
<dllmap os="osx" dll="glfw3.dll" target="glfw.3.dylib" />
|
||||
</configuration>
|
||||
25
README.md
25
README.md
@@ -1,25 +1,16 @@
|
||||

|
||||
|
||||
[](https://ci.appveyor.com/project/Silvertorch5/space-station-14/branch/master)
|
||||
[](https://sonarcloud.io/dashboard?id=ss14)
|
||||
Robust Toolbox is an engine primarily being developed for [Space Station 14](https://github.com/space-wizards/space-station-14), although we're working on making it usable for both [singleplayer](https://github.com/space-wizards/RobustToolboxTemplateSingleplayer) and [multiplayer](https://github.com/space-wizards/RobustToolboxTemplate) projects.
|
||||
|
||||
Robust Toolbox is a client/server backend for [Space Station 14](https://github.com/space-wizards/space-station-14).
|
||||
Use the [content repo](https://github.com/space-wizards/space-station-14) for actual development, even if you're modifying the engine itself.
|
||||
|
||||
### *This repository is the *engine* section of SS14. This is the base engine all SS14 servers will be built on. As such, it does not start on its own: it needs the [content repo](https://github.com/space-wizards/space-station-14). Use said repo for actual development, even if you're modifying the engine itself. Think of Robust Toolbox as BYOND in the context of Spacestation 13.*
|
||||
## Project Links
|
||||
|
||||
## Getting in Touch
|
||||
[Website](https://spacestation14.io/) | [Discord](https://discord.gg/t2jac3p) | [Forum](https://forum.spacestation14.io/) | [Steam](https://store.steampowered.com/app/1255460/Space_Station_14/) | [Standalone Download](https://spacestation14.io/about/nightlies/)
|
||||
|
||||
* Website: [spacestation14.io](https://spacestation14.io/)
|
||||
* Discord: [Invite Link](https://discord.gg/t2jac3p)
|
||||
* IRC: `irc.rizon.net#spacebus`
|
||||
* Code Analysis: [Sonar Cloud](https://sonarcloud.io/dashboard?id=ss14)
|
||||
* Automatic Content Builds: [builds.spacestation14.io](https://builds.spacestation14.io/jenkins/)
|
||||
## Documentation/Wiki
|
||||
|
||||
The IRC is setup to relay back and forth to the Discord server so [IRC nerds](https://xkcd.com/1782/) will not be left out.
|
||||
|
||||
## Documentation
|
||||
|
||||
We have various technical documentation articles on the [HackMD Wiki](https://hackmd.io/@ss14/docs/%2F%40ss14%2Ftechnical-documentation-overview).
|
||||
The [HackMD Wiki](https://hackmd.io/@ss14/docs/wiki) has documentation on SS14s content, engine, game design and more. We also have lots of resources for new contributors to the project.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -27,8 +18,8 @@ We are happy to accept contributions from anybody. Get in Discord or IRC if you
|
||||
|
||||
## Building
|
||||
|
||||
**In practice, you usually don't build this repository directly.**
|
||||
This repository is the **engine** part of SS14. It's the base engine all SS14 servers will be built on. As such, it does not start on its own: it needs the [content repo](https://github.com/space-wizards/space-station-14). Think of Robust Toolbox as BYOND in the context of Spacestation 13.
|
||||
|
||||
## Legal Info
|
||||
|
||||
See `legal.md` for licenses and copyright.
|
||||
See [legal.md](https://github.com/space-wizards/RobustToolbox/blob/master/legal.md) for licenses and copyright.
|
||||
|
||||
@@ -23,6 +23,48 @@
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
- name: Box2D
|
||||
license:
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Erin Catto
|
||||
|
||||
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.
|
||||
|
||||
- name: Bullet Physics SDK
|
||||
license:
|
||||
The files in this repository are licensed under the zlib license, except for the files under 'Extras' and examples/ThirdPartyLibs.
|
||||
|
||||
Bullet Continuous Collision Detection and Physics Library
|
||||
http://bulletphysics.org
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it freely,
|
||||
subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
|
||||
If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
- name: Castle Core
|
||||
license: |
|
||||
Copyright 2004-2016 Castle Project - http://www.castleproject.org/
|
||||
@@ -317,6 +359,43 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
- name: Farseer Physics Engine
|
||||
license:
|
||||
Microsoft Permissive License (Ms-PL)
|
||||
|
||||
This license governs use of the accompanying software.
|
||||
If you use the software, you accept this license.
|
||||
If you do not accept the license, do not use the software.
|
||||
|
||||
1. Definitions
|
||||
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
|
||||
A "contribution" is the original software, or any additions or changes to the software.
|
||||
A "contributor" is any person that distributes its contribution under this license.
|
||||
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
|
||||
|
||||
2. Grant of Rights
|
||||
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||
each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution,
|
||||
prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
||||
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||
each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to
|
||||
make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or
|
||||
derivative works of the contribution in the software.
|
||||
|
||||
3. Conditions and Limitations
|
||||
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
|
||||
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
|
||||
your patent license from such contributor to the software ends automatically.
|
||||
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark,
|
||||
and attribution notices that are present in the software.
|
||||
(D) If you distribute any portion of the software in source code form, you may do so only under this license by
|
||||
including a complete copy of this license with your distribution. If you distribute any portion of the software in
|
||||
compiled or object code form, you may only do so under a license that complies with this license.
|
||||
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties,
|
||||
guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change.
|
||||
To the extent permitted under your local laws, the contributors exclude the implied warranties of
|
||||
merchantability, fitness for a particular purpose and non-infringement.
|
||||
|
||||
- name: Mono.Cecil
|
||||
license: |
|
||||
Copyright (c) 2008 - 2015 Jb Evain
|
||||
|
||||
11
Resources/Locale/en-US/custom-controls.ftl
Normal file
11
Resources/Locale/en-US/custom-controls.ftl
Normal file
@@ -0,0 +1,11 @@
|
||||
## EntitySpawnWindow
|
||||
|
||||
entity-spawn-window-title = Entity Spawn Panel
|
||||
entity-spawn-window-search-bar-placeholder = search
|
||||
entity-spawn-window-clear-button = Clear
|
||||
entity-spawn-window-erase-button-text = Erase Mode
|
||||
entity-spawn-window-override-menu-tooltip = Override placement
|
||||
|
||||
## Console
|
||||
|
||||
console-line-edit-placeholder = Command Here
|
||||
54
Resources/Locale/en-US/input.ftl
Normal file
54
Resources/Locale/en-US/input.ftl
Normal file
@@ -0,0 +1,54 @@
|
||||
input-key-Escape = Escape
|
||||
input-key-Control = Control
|
||||
input-key-Shift = Shift
|
||||
input-key-Alt = Alt
|
||||
input-key-Menu = Menu
|
||||
input-key-F1 = F1
|
||||
input-key-F2 = F2
|
||||
input-key-F3 = F3
|
||||
input-key-F4 = F4
|
||||
input-key-F5 = F5
|
||||
input-key-F6 = F6
|
||||
input-key-F7 = F7
|
||||
input-key-F8 = F8
|
||||
input-key-F9 = F9
|
||||
input-key-F10 = F10
|
||||
input-key-F11 = F11
|
||||
input-key-F12 = F12
|
||||
input-key-F13 = F13
|
||||
input-key-F14 = F14
|
||||
input-key-F15 = F15
|
||||
input-key-Pause = Pause
|
||||
input-key-Left = Left
|
||||
input-key-Up = Up
|
||||
input-key-Down = Down
|
||||
input-key-Right = Right
|
||||
input-key-Space = Space
|
||||
input-key-Return = Return
|
||||
input-key-NumpadEnter = Num Enter
|
||||
input-key-BackSpace = Backspace
|
||||
input-key-Tab = Tab
|
||||
input-key-PageUp = Page Up
|
||||
input-key-PageDown = Page Down
|
||||
input-key-End = End
|
||||
input-key-Home = Home
|
||||
input-key-Insert = Insert
|
||||
input-key-Delete = Delete
|
||||
input-key-MouseLeft = Mouse Left
|
||||
input-key-MouseRight = Mouse Right
|
||||
input-key-MouseMiddle = Mouse Middle
|
||||
input-key-MouseButton4 = Mouse 4
|
||||
input-key-MouseButton5 = Mouse 5
|
||||
input-key-MouseButton6 = Mouse 6
|
||||
input-key-MouseButton7 = Mouse 7
|
||||
input-key-MouseButton8 = Mouse 8
|
||||
input-key-MouseButton9 = Mouse 9
|
||||
|
||||
input-key-LSystem-win = Left Win
|
||||
input-key-RSystem-win = Right Win
|
||||
input-key-LSystem-mac = Left Cmd
|
||||
input-key-RSystem-mac = Right Cmd
|
||||
input-key-LSystem-linux = Left Meta
|
||||
input-key-RSystem-linux = Right Meta
|
||||
|
||||
input-key-unknown = <unknown key>
|
||||
1
Resources/Locale/en-US/tab-container.ftl
Normal file
1
Resources/Locale/en-US/tab-container.ftl
Normal file
@@ -0,0 +1 @@
|
||||
tab-container-not-tab-title-provided = No title
|
||||
11
Resources/Locale/en-US/view-variables.ftl
Normal file
11
Resources/Locale/en-US/view-variables.ftl
Normal file
@@ -0,0 +1,11 @@
|
||||
## ViewVariablesInstanceEntity
|
||||
|
||||
view-variable-instance-entity-server-components-add-component-button-placeholder = Add Component
|
||||
view-variable-instance-entity-client-variables-tab-title = Client Variables
|
||||
view-variable-instance-entity-client-components-tab-title = Client Components
|
||||
view-variable-instance-entity-server-variables-tab-title = Server Variables
|
||||
view-variable-instance-entity-server-components-tab-title = Server Components
|
||||
view-variable-instance-entity-client-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-server-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-add-window-server-components = Add Component [S]
|
||||
view-variable-instance-entity-add-window-client-components = Add Component [C]
|
||||
@@ -1,40 +0,0 @@
|
||||
- type: entity
|
||||
id: debugRotation1
|
||||
name: dbg_rotation1
|
||||
components:
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
visible: true
|
||||
sprite: debugRotation.rsi
|
||||
state: direction1
|
||||
placement:
|
||||
mode: AlignTileAny
|
||||
|
||||
- type: entity
|
||||
id: debugRotation4
|
||||
name: dbg_rotation4
|
||||
components:
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
visible: true
|
||||
sprite: debugRotation.rsi
|
||||
state: direction4
|
||||
placement:
|
||||
mode: AlignTileAny
|
||||
|
||||
- type: entity
|
||||
id: debugRotationTex
|
||||
name: dbg_rotationTex
|
||||
components:
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
visible: true
|
||||
texture: debugRotation.rsi/direction1.png
|
||||
placement:
|
||||
mode: AlignTileAny
|
||||
@@ -1,4 +0,0 @@
|
||||
- type: entity
|
||||
name: blank entity
|
||||
id: BlankEntity
|
||||
abstract: true
|
||||
@@ -1,5 +1,11 @@
|
||||
#include "/Shaders/Internal/light_shared.swsl"
|
||||
|
||||
highp vec4 calcGaussianWeights(highp float sigma, highp vec4 offset)
|
||||
{
|
||||
highp vec4 eExp = offset * offset / (2.0 * sigma * sigma);
|
||||
return exp(-eExp) / (sigma * sqrt(2.0 * PI));
|
||||
}
|
||||
|
||||
highp float createOcclusion(highp vec2 diff)
|
||||
{
|
||||
// Calculate vector perpendicular to light vector.
|
||||
@@ -8,23 +14,57 @@ highp float createOcclusion(highp vec2 diff)
|
||||
|
||||
highp float ourDist = length(diff);
|
||||
|
||||
highp vec2 occlDist = occludeDepth(diff, shadowMap, lightIndex);
|
||||
// Sample 7 points on a line perpendicular to the light source.
|
||||
// Depending on the closest point, we change the gaussian weights down below
|
||||
// to change the "narrowness" of the samples.
|
||||
perpendicular *= lightSoftness * 1.5;
|
||||
|
||||
// Get all the samples we need.
|
||||
highp vec2 sample1 = occludeDepth(diff, shadowMap, lightIndex);
|
||||
highp vec2 sample2 = occludeDepth(diff + perpendicular, shadowMap, lightIndex);
|
||||
highp vec2 sample3 = occludeDepth(diff - perpendicular, shadowMap, lightIndex);
|
||||
highp vec2 sample4 = occludeDepth(diff + perpendicular * 2.0, shadowMap, lightIndex);
|
||||
highp vec2 sample5 = occludeDepth(diff - perpendicular * 2.0, shadowMap, lightIndex);
|
||||
highp vec2 sample6 = occludeDepth(diff + perpendicular * 3.0, shadowMap, lightIndex);
|
||||
highp vec2 sample7 = occludeDepth(diff - perpendicular * 3.0, shadowMap, lightIndex);
|
||||
|
||||
highp float mindist =
|
||||
min(sample1.x,
|
||||
min(sample2.x,
|
||||
min(sample3.x,
|
||||
min(sample4.x,
|
||||
min(sample5.x,
|
||||
min(sample6.x,
|
||||
sample7.x))))));
|
||||
|
||||
mindist = max(0.001, mindist);
|
||||
|
||||
// Change soft shadow size based on distance from primary occluder.
|
||||
highp float distRatio = (ourDist - occlDist.x) / occlDist.x / 2.0;
|
||||
highp float distRatio = (ourDist - mindist);
|
||||
|
||||
perpendicular *= distRatio * lightSoftness;
|
||||
// Sigma can never be zero so make sure to clamp.
|
||||
// TODO: Scaling the dist ratio here in a more sane way might make shadows look better buuuut I'm lazy.
|
||||
// Shadows look pretty nice already.
|
||||
highp float sigma = max(0.001, distRatio * 0.75);
|
||||
highp vec4 weights = calcGaussianWeights(sigma, vec4(0.0, 1.0, 2.0, 3.0));
|
||||
|
||||
// Totally not hacky PCF on top of VSM.
|
||||
highp float occlusion = smoothstep(0.1, 1.0, ChebyshevUpperBound(occlDist, ourDist));
|
||||
// Calculation of gaussian weights here is broken because it doesn't add up to 1.
|
||||
// Fixing this is hard and if I had to guess too expensive for GPU shaders.
|
||||
// So instead we add up the total weights and scale the result with that,
|
||||
// so that we still end up with 0-1.
|
||||
highp float totalWeigths = weights.x + weights.y * 2.0 + weights.z * 2.0 + weights.w * 2.0;
|
||||
|
||||
occlusion += shadowContrib(diff + perpendicular);
|
||||
occlusion += shadowContrib(diff - perpendicular);
|
||||
occlusion += shadowContrib(diff + perpendicular * 2.0);
|
||||
occlusion += shadowContrib(diff - perpendicular * 2.0);
|
||||
occlusion += shadowContrib(diff + perpendicular * 3.0);
|
||||
occlusion += shadowContrib(diff - perpendicular * 3.0);
|
||||
highp float occlusion = 0.0;
|
||||
|
||||
return occlusion / 7.0;
|
||||
// Calculate actual occlusion with new weights.
|
||||
occlusion += ChebyshevUpperBound(sample1, ourDist) * weights.x;
|
||||
occlusion += ChebyshevUpperBound(sample2, ourDist) * weights.y;
|
||||
occlusion += ChebyshevUpperBound(sample3, ourDist) * weights.y;
|
||||
occlusion += ChebyshevUpperBound(sample4, ourDist) * weights.z;
|
||||
occlusion += ChebyshevUpperBound(sample5, ourDist) * weights.z;
|
||||
occlusion += ChebyshevUpperBound(sample6, ourDist) * weights.w;
|
||||
occlusion += ChebyshevUpperBound(sample7, ourDist) * weights.w;
|
||||
|
||||
return occlusion / totalWeigths;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Copy
|
||||
{
|
||||
public class SerializationCopyBenchmark : SerializationBenchmark
|
||||
{
|
||||
public SerializationCopyBenchmark()
|
||||
{
|
||||
InitializeSerialization();
|
||||
|
||||
DataDefinitionWithString = new DataDefinitionWithString {StringField = "ABC"};
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));
|
||||
|
||||
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
|
||||
|
||||
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
|
||||
}
|
||||
|
||||
private const string String = "ABC";
|
||||
|
||||
private const int Integer = 1;
|
||||
|
||||
private DataDefinitionWithString DataDefinitionWithString { get; }
|
||||
|
||||
private SeedDataDefinition Seed { get; }
|
||||
|
||||
[Benchmark]
|
||||
public string? CreateCopyString()
|
||||
{
|
||||
return SerializationManager.CreateCopy(String);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int? CreateCopyInteger()
|
||||
{
|
||||
return SerializationManager.CreateCopy(Integer);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataDefinitionWithString? CreateCopyDataDefinitionWithString()
|
||||
{
|
||||
return SerializationManager.CreateCopy(DataDefinitionWithString);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public SeedDataDefinition? CreateCopySeedDataDefinition()
|
||||
{
|
||||
return SerializationManager.CreateCopy(Seed);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public SeedDataDefinition BaselineCreateCopySeedDataDefinition()
|
||||
{
|
||||
// ReSharper disable once UseObjectOrCollectionInitializer
|
||||
var copy = new SeedDataDefinition();
|
||||
|
||||
copy.ID = Seed.ID;
|
||||
copy.Name = Seed.Name;
|
||||
copy.SeedName = Seed.SeedName;
|
||||
copy.SeedNoun = Seed.SeedNoun;
|
||||
copy.DisplayName = Seed.DisplayName;
|
||||
copy.RoundStart = Seed.RoundStart;
|
||||
copy.Mysterious = Seed.Mysterious;
|
||||
copy.Immutable = Seed.Immutable;
|
||||
|
||||
copy.ProductPrototypes = Seed.ProductPrototypes.ToList();
|
||||
copy.Chemicals = Seed.Chemicals.ToDictionary(p => p.Key, p => p.Value);
|
||||
copy.ConsumeGasses = Seed.ConsumeGasses.ToDictionary(p => p.Key, p => p.Value);
|
||||
copy.ExudeGasses = Seed.ExudeGasses.ToDictionary(p => p.Key, p => p.Value);
|
||||
|
||||
copy.NutrientConsumption = Seed.NutrientConsumption;
|
||||
copy.WaterConsumption = Seed.WaterConsumption;
|
||||
copy.IdealHeat = Seed.IdealHeat;
|
||||
copy.HeatTolerance = Seed.HeatTolerance;
|
||||
copy.IdealLight = Seed.IdealLight;
|
||||
copy.LightTolerance = Seed.LightTolerance;
|
||||
copy.ToxinsTolerance = Seed.ToxinsTolerance;
|
||||
copy.LowPressureTolerance = Seed.LowPressureTolerance;
|
||||
copy.HighPressureTolerance = Seed.HighPressureTolerance;
|
||||
copy.PestTolerance = Seed.PestTolerance;
|
||||
copy.WeedTolerance = Seed.WeedTolerance;
|
||||
|
||||
copy.Endurance = Seed.Endurance;
|
||||
copy.Yield = Seed.Yield;
|
||||
copy.Lifespan = Seed.Lifespan;
|
||||
copy.Maturation = Seed.Maturation;
|
||||
copy.Production = Seed.Production;
|
||||
copy.GrowthStages = Seed.GrowthStages;
|
||||
copy.HarvestRepeat = Seed.HarvestRepeat;
|
||||
copy.Potency = Seed.Potency;
|
||||
copy.Ligneous = Seed.Ligneous;
|
||||
|
||||
copy.PlantRsi = Seed.PlantRsi == null
|
||||
? null!
|
||||
: new ResourcePath(Seed.PlantRsi.ToString(), Seed.PlantRsi.Separator);
|
||||
copy.PlantIconState = Seed.PlantIconState;
|
||||
copy.Bioluminescent = Seed.Bioluminescent;
|
||||
copy.BioluminescentColor = Seed.BioluminescentColor;
|
||||
copy.SplatPrototype = Seed.SplatPrototype;
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization
|
||||
namespace Robust.Benchmarks.Serialization.Definitions
|
||||
{
|
||||
[DataDefinition]
|
||||
public class DataDefinitionWithString
|
||||
{
|
||||
[field: DataField("string")]
|
||||
private string StringField { get; } = default!;
|
||||
public string StringField { get; init; } = default!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Definitions
|
||||
{
|
||||
/// <summary>
|
||||
/// Arbitrarily large data definition for benchmarks.
|
||||
/// Taken from content.
|
||||
/// </summary>
|
||||
[Prototype("seed")]
|
||||
public class SeedDataDefinition : IPrototype
|
||||
{
|
||||
public const string Prototype = @"
|
||||
- type: seed
|
||||
id: tobacco
|
||||
name: tobacco
|
||||
seedName: tobacco
|
||||
displayName: tobacco plant
|
||||
productPrototypes:
|
||||
- LeavesTobacco
|
||||
harvestRepeat: Repeat
|
||||
lifespan: 75
|
||||
maturation: 5
|
||||
production: 5
|
||||
yield: 2
|
||||
potency: 20
|
||||
growthStages: 3
|
||||
idealLight: 9
|
||||
idealHeat: 298
|
||||
chemicals:
|
||||
chem.Nicotine:
|
||||
Min: 1
|
||||
Max: 10
|
||||
PotencyDivisor: 10";
|
||||
|
||||
[DataField("id", required: true)] public string ID { get; set; } = default!;
|
||||
|
||||
#region Tracking
|
||||
[DataField("name")] public string Name { get; set; } = string.Empty;
|
||||
[DataField("seedName")] public string SeedName { get; set; } = string.Empty;
|
||||
[DataField("seedNoun")] public string SeedNoun { get; set; } = "seeds";
|
||||
[DataField("displayName")] public string DisplayName { get; set; } = string.Empty;
|
||||
[DataField("roundStart")] public bool RoundStart { get; set; } = true;
|
||||
[DataField("mysterious")] public bool Mysterious { get; set; }
|
||||
[DataField("immutable")] public bool Immutable { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Output
|
||||
[DataField("productPrototypes")]
|
||||
public List<string> ProductPrototypes { get; set; } = new();
|
||||
[DataField("chemicals")]
|
||||
public Dictionary<string, SeedChemQuantity> Chemicals { get; set; } = new();
|
||||
[DataField("consumeGasses")]
|
||||
public Dictionary<Gas, float> ConsumeGasses { get; set; } = new();
|
||||
[DataField("exudeGasses")]
|
||||
public Dictionary<Gas, float> ExudeGasses { get; set; } = new();
|
||||
#endregion
|
||||
|
||||
#region Tolerances
|
||||
[DataField("nutrientConsumption")] public float NutrientConsumption { get; set; } = 0.25f;
|
||||
[DataField("waterConsumption")] public float WaterConsumption { get; set; } = 3f;
|
||||
[DataField("idealHeat")] public float IdealHeat { get; set; } = 293f;
|
||||
[DataField("heatTolerance")] public float HeatTolerance { get; set; } = 20f;
|
||||
[DataField("idealLight")] public float IdealLight { get; set; } = 7f;
|
||||
[DataField("lightTolerance")] public float LightTolerance { get; set; } = 5f;
|
||||
[DataField("toxinsTolerance")] public float ToxinsTolerance { get; set; } = 4f;
|
||||
[DataField("lowPressureTolerance")] public float LowPressureTolerance { get; set; } = 25f;
|
||||
[DataField("highPressureTolerance")] public float HighPressureTolerance { get; set; } = 200f;
|
||||
[DataField("pestTolerance")] public float PestTolerance { get; set; } = 5f;
|
||||
[DataField("weedTolerance")] public float WeedTolerance { get; set; } = 5f;
|
||||
#endregion
|
||||
|
||||
#region General traits
|
||||
[DataField("endurance")] public float Endurance { get; set; } = 100f;
|
||||
[DataField("yield")] public int Yield { get; set; }
|
||||
[DataField("lifespan")] public float Lifespan { get; set; }
|
||||
[DataField("maturation")] public float Maturation { get; set; }
|
||||
[DataField("production")] public float Production { get; set; }
|
||||
[DataField("growthStages")] public int GrowthStages { get; set; } = 6;
|
||||
[DataField("harvestRepeat")] public HarvestType HarvestRepeat { get; set; } = HarvestType.NoRepeat;
|
||||
[DataField("potency")] public float Potency { get; set; } = 1f;
|
||||
[DataField("ligneous")] public bool Ligneous { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Cosmetics
|
||||
[DataField("plantRsi", required: true)] public ResourcePath PlantRsi { get; set; } = default!;
|
||||
[DataField("plantIconState")] public string PlantIconState { get; set; } = "produce";
|
||||
[DataField("bioluminescent")] public bool Bioluminescent { get; set; }
|
||||
[DataField("bioluminescentColor")] public Color BioluminescentColor { get; set; } = Color.White;
|
||||
[DataField("splatPrototype")] public string? SplatPrototype { get; set; }
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum HarvestType
|
||||
{
|
||||
NoRepeat,
|
||||
Repeat
|
||||
}
|
||||
|
||||
public enum Gas
|
||||
{
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public struct SeedChemQuantity
|
||||
{
|
||||
[DataField("Min")]
|
||||
public int Min;
|
||||
|
||||
[DataField("Max")]
|
||||
public int Max;
|
||||
|
||||
[DataField("PotencyDivisor")]
|
||||
public int PotencyDivisor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Initialize
|
||||
{
|
||||
public class SerializationInitializeBenchmark : SerializationBenchmark
|
||||
{
|
||||
[IterationCleanup]
|
||||
public void IterationCleanup()
|
||||
{
|
||||
SerializationManager.Shutdown();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ISerializationManager Initialize()
|
||||
{
|
||||
InitializeSerialization();
|
||||
return SerializationManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,22 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Server;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Read
|
||||
{
|
||||
public class SerializationReadBenchmark
|
||||
public class SerializationReadBenchmark : SerializationBenchmark
|
||||
{
|
||||
public SerializationReadBenchmark()
|
||||
{
|
||||
IoCManager.InitThread();
|
||||
ServerIoC.RegisterIoC();
|
||||
IoCManager.BuildGraph();
|
||||
|
||||
var assemblies = new[]
|
||||
{
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Shared"),
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Server"),
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Benchmarks")
|
||||
};
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
IoCManager.Resolve<IConfigurationManagerInternal>().LoadCVarsFromAssembly(assembly);
|
||||
}
|
||||
|
||||
IoCManager.Resolve<IReflectionManager>().LoadAssemblies(assemblies);
|
||||
|
||||
SerializationManager = IoCManager.Resolve<ISerializationManager>();
|
||||
SerializationManager.Initialize();
|
||||
InitializeSerialization();
|
||||
|
||||
StringDataDefNode = new MappingDataNode();
|
||||
StringDataDefNode.AddNode(new ValueDataNode("string"), new ValueDataNode("ABC"));
|
||||
StringDataDefNode.Add(new ValueDataNode("string"), new ValueDataNode("ABC"));
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));
|
||||
@@ -46,8 +24,6 @@ namespace Robust.Benchmarks.Serialization.Read
|
||||
SeedNode = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
|
||||
}
|
||||
|
||||
private ISerializationManager SerializationManager { get; }
|
||||
|
||||
private ValueDataNode StringNode { get; } = new("ABC");
|
||||
|
||||
private ValueDataNode IntNode { get; } = new("1");
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization
|
||||
{
|
||||
[Prototype("seed")]
|
||||
public class SeedDataDefinition : IPrototype
|
||||
{
|
||||
public const string Prototype = @"
|
||||
- type: seed
|
||||
id: tobacco
|
||||
name: tobacco
|
||||
seedName: tobacco
|
||||
displayName: tobacco plant
|
||||
productPrototypes:
|
||||
- LeavesTobacco
|
||||
harvestRepeat: Repeat
|
||||
lifespan: 75
|
||||
maturation: 5
|
||||
production: 5
|
||||
yield: 2
|
||||
potency: 20
|
||||
growthStages: 3
|
||||
idealLight: 9
|
||||
idealHeat: 298
|
||||
chemicals:
|
||||
chem.Nicotine:
|
||||
Min: 1
|
||||
Max: 10
|
||||
PotencyDivisor: 10";
|
||||
|
||||
private const string SeedPrototype = "SeedBase";
|
||||
|
||||
[ViewVariables]
|
||||
[field: DataField("id", required: true)]
|
||||
public string ID { get; private init; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier of this seed. Do NOT set this.
|
||||
/// </summary>
|
||||
public int Uid { get; internal set; } = -1;
|
||||
|
||||
#region Tracking
|
||||
|
||||
[ViewVariables] [DataField("name")] public string Name { get; set; } = string.Empty;
|
||||
[ViewVariables] [DataField("seedName")] public string SeedName { get; set; } = string.Empty;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("seedNoun")]
|
||||
public string SeedNoun { get; set; } = "seeds";
|
||||
[ViewVariables] [DataField("displayName")] public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("roundStart")]
|
||||
public bool RoundStart { get; private set; } = true;
|
||||
[ViewVariables] [DataField("mysterious")] public bool Mysterious { get; set; }
|
||||
[ViewVariables] [DataField("immutable")] public bool Immutable { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Output
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("productPrototypes")]
|
||||
public List<string> ProductPrototypes { get; set; } = new();
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("chemicals")]
|
||||
public Dictionary<string, SeedChemQuantity> Chemicals { get; set; } = new();
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("consumeGasses")]
|
||||
public Dictionary<Gas, float> ConsumeGasses { get; set; } = new();
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("exudeGasses")]
|
||||
public Dictionary<Gas, float> ExudeGasses { get; set; } = new();
|
||||
#endregion
|
||||
|
||||
#region Tolerances
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("nutrientConsumption")]
|
||||
public float NutrientConsumption { get; set; } = 0.25f;
|
||||
|
||||
[ViewVariables] [DataField("waterConsumption")] public float WaterConsumption { get; set; } = 3f;
|
||||
[ViewVariables] [DataField("idealHeat")] public float IdealHeat { get; set; } = 293f;
|
||||
[ViewVariables] [DataField("heatTolerance")] public float HeatTolerance { get; set; } = 20f;
|
||||
[ViewVariables] [DataField("idealLight")] public float IdealLight { get; set; } = 7f;
|
||||
[ViewVariables] [DataField("lightTolerance")] public float LightTolerance { get; set; } = 5f;
|
||||
[ViewVariables] [DataField("toxinsTolerance")] public float ToxinsTolerance { get; set; } = 4f;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("lowPressureTolerance")]
|
||||
public float LowPressureTolerance { get; set; } = 25f;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("highPressureTolerance")]
|
||||
public float HighPressureTolerance { get; set; } = 200f;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("pestTolerance")]
|
||||
public float PestTolerance { get; set; } = 5f;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("weedTolerance")]
|
||||
public float WeedTolerance { get; set; } = 5f;
|
||||
#endregion
|
||||
|
||||
#region General traits
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("endurance")]
|
||||
public float Endurance { get; set; } = 100f;
|
||||
[ViewVariables] [DataField("yield")] public int Yield { get; set; }
|
||||
[ViewVariables] [DataField("lifespan")] public float Lifespan { get; set; }
|
||||
[ViewVariables] [DataField("maturation")] public float Maturation { get; set; }
|
||||
[ViewVariables] [DataField("production")] public float Production { get; set; }
|
||||
[ViewVariables] [DataField("growthStages")] public int GrowthStages { get; set; } = 6;
|
||||
[ViewVariables] [DataField("harvestRepeat")] public HarvestType HarvestRepeat { get; set; } = HarvestType.NoRepeat;
|
||||
|
||||
[ViewVariables] [DataField("potency")] public float Potency { get; set; } = 1f;
|
||||
// No, I'm not removing these.
|
||||
//public PlantSpread Spread { get; set; }
|
||||
//public PlantMutation Mutation { get; set; }
|
||||
//public float AlterTemperature { get; set; }
|
||||
//public PlantCarnivorous Carnivorous { get; set; }
|
||||
//public bool Parasite { get; set; }
|
||||
//public bool Hematophage { get; set; }
|
||||
//public bool Thorny { get; set; }
|
||||
//public bool Stinging { get; set; }
|
||||
[DataField("ligneous")]
|
||||
public bool Ligneous { get; set; }
|
||||
// public bool Teleporting { get; set; }
|
||||
// public PlantJuicy Juicy { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Cosmetics
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("plantRsi", required: true)]
|
||||
public ResourcePath PlantRsi { get; set; } = default!;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("plantIconState")]
|
||||
public string PlantIconState { get; set; } = "produce";
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("bioluminescent")]
|
||||
public bool Bioluminescent { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("bioluminescentColor")]
|
||||
public Color BioluminescentColor { get; set; } = Color.White;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("splatPrototype")]
|
||||
public string? SplatPrototype { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum HarvestType
|
||||
{
|
||||
NoRepeat,
|
||||
Repeat
|
||||
}
|
||||
|
||||
public enum Gas {}
|
||||
|
||||
[DataDefinition]
|
||||
public struct SeedChemQuantity
|
||||
{
|
||||
[DataField("Min")]
|
||||
public int Min;
|
||||
[DataField("Max")]
|
||||
public int Max;
|
||||
[DataField("PotencyDivisor")]
|
||||
public int PotencyDivisor;
|
||||
}
|
||||
}
|
||||
43
Robust.Benchmarks/Serialization/SerializationBenchmark.cs
Normal file
43
Robust.Benchmarks/Serialization/SerializationBenchmark.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Robust.Server;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization
|
||||
{
|
||||
public abstract class SerializationBenchmark
|
||||
{
|
||||
public SerializationBenchmark()
|
||||
{
|
||||
IoCManager.InitThread();
|
||||
ServerIoC.RegisterIoC();
|
||||
IoCManager.BuildGraph();
|
||||
|
||||
var assemblies = new[]
|
||||
{
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Shared"),
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Server"),
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Benchmarks")
|
||||
};
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
IoCManager.Resolve<IConfigurationManagerInternal>().LoadCVarsFromAssembly(assembly);
|
||||
}
|
||||
|
||||
IoCManager.Resolve<IReflectionManager>().LoadAssemblies(assemblies);
|
||||
|
||||
SerializationManager = IoCManager.Resolve<ISerializationManager>();
|
||||
}
|
||||
|
||||
protected ISerializationManager SerializationManager { get; }
|
||||
|
||||
public void InitializeSerialization()
|
||||
{
|
||||
SerializationManager.Initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Write
|
||||
{
|
||||
public class SerializationWriteBenchmark : SerializationBenchmark
|
||||
{
|
||||
public SerializationWriteBenchmark()
|
||||
{
|
||||
InitializeSerialization();
|
||||
|
||||
DataDefinitionWithString = new DataDefinitionWithString {StringField = "ABC"};
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));
|
||||
|
||||
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
|
||||
|
||||
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
|
||||
}
|
||||
|
||||
private const string String = "ABC";
|
||||
|
||||
private const int Integer = 1;
|
||||
|
||||
private DataDefinitionWithString DataDefinitionWithString { get; }
|
||||
|
||||
private SeedDataDefinition Seed { get; }
|
||||
|
||||
[Benchmark]
|
||||
public DataNode WriteString()
|
||||
{
|
||||
return SerializationManager.WriteValue(String);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataNode WriteInteger()
|
||||
{
|
||||
return SerializationManager.WriteValue(Integer);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataNode WriteDataDefinitionWithString()
|
||||
{
|
||||
return SerializationManager.WriteValue(DataDefinitionWithString);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataNode WriteSeedDataDefinition()
|
||||
{
|
||||
return SerializationManager.WriteValue(Seed);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataNode BaselineWriteSeedDataDefinition()
|
||||
{
|
||||
var mapping = new MappingDataNode();
|
||||
|
||||
mapping.Add("id", Seed.ID);
|
||||
mapping.Add("name", Seed.Name);
|
||||
mapping.Add("seedName", Seed.SeedName);
|
||||
mapping.Add("displayName", Seed.DisplayName);
|
||||
mapping.Add("productPrototypes", Seed.ProductPrototypes);
|
||||
mapping.Add("harvestRepeat", Seed.HarvestRepeat.ToString());
|
||||
mapping.Add("lifespan", Seed.Lifespan.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("maturation", Seed.Maturation.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("production", Seed.Production.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("yield", Seed.Yield.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("potency", Seed.Potency.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("growthStages", Seed.GrowthStages.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("idealLight", Seed.IdealLight.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("idealHeat", Seed.IdealHeat.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
var chemicals = new MappingDataNode();
|
||||
foreach (var (name, quantity) in Seed.Chemicals)
|
||||
{
|
||||
chemicals.Add(name, new MappingDataNode
|
||||
{
|
||||
["Min"] = new ValueDataNode(quantity.Min.ToString(CultureInfo.InvariantCulture)),
|
||||
["Max"] = new ValueDataNode(quantity.Max.ToString(CultureInfo.InvariantCulture)),
|
||||
["PotencyDivisor"] = new ValueDataNode(quantity.PotencyDivisor.ToString(CultureInfo.InvariantCulture))
|
||||
});
|
||||
}
|
||||
|
||||
mapping.Add("chemicals", chemicals);
|
||||
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
@@ -147,9 +148,10 @@ namespace {nameSpace}
|
||||
foreach (var typeSymbol in symbols)
|
||||
{
|
||||
var xamlFileName = $"{typeSymbol.Name}.xaml";
|
||||
var relevantXamlFile = context.AdditionalFiles.FirstOrDefault(t => t.Path.EndsWith(xamlFileName));
|
||||
var xamlFileNameSep = $"{Path.DirectorySeparatorChar}{xamlFileName}";
|
||||
var relevantXamlFiles = context.AdditionalFiles.Where(t => t.Path.EndsWith(xamlFileNameSep)).ToArray();
|
||||
|
||||
if (relevantXamlFile == null)
|
||||
if (relevantXamlFiles.Length == 0)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
@@ -165,13 +167,28 @@ namespace {nameSpace}
|
||||
continue;
|
||||
}
|
||||
|
||||
var txt = relevantXamlFile.GetText()?.ToString();
|
||||
if (txt == null)
|
||||
if (relevantXamlFiles.Length > 1)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0002",
|
||||
$"Found multiple candidate XAML files for {typeSymbol}",
|
||||
$"Multiple files exist with name {xamlFileName}",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
typeSymbol.Locations[0]));
|
||||
continue;
|
||||
}
|
||||
|
||||
var txt = relevantXamlFiles[0].GetText()?.ToString();
|
||||
if (txt == null)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0004",
|
||||
$"Unexpected empty Xaml-File was found at {xamlFileName}",
|
||||
"Expected Content due to a Class with the same name being annotated with [GenerateTypedNameReferences].",
|
||||
"Usage",
|
||||
@@ -192,7 +209,7 @@ namespace {nameSpace}
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"AXN0003",
|
||||
"RXN0003",
|
||||
"Unhandled exception occured while generating typed Name references.",
|
||||
$"Unhandled exception occured while generating typed Name references: {e}",
|
||||
"Usage",
|
||||
@@ -247,7 +264,6 @@ namespace {nameSpace}
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
Location.None));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,14 +6,18 @@ using System.Threading;
|
||||
using NFluidsynth;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Logger = Robust.Shared.Log.Logger;
|
||||
|
||||
namespace Robust.Client.Audio.Midi
|
||||
@@ -32,24 +36,6 @@ namespace Robust.Client.Audio.Midi
|
||||
/// </returns>
|
||||
IMidiRenderer? GetNewRenderer();
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Checks whether the file at the given path is a valid midi file or not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We add this here so content doesn't need to reference NFluidsynth.
|
||||
/// </remarks>
|
||||
bool IsMidiFile(string filename);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the file at the given path is a valid midi file or not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We add this here so content doesn't need to reference NFluidsynth.
|
||||
/// </remarks>
|
||||
bool IsSoundfontFile(string filename);
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Method called every frame.
|
||||
/// Should be used to update positional audio.
|
||||
@@ -57,6 +43,11 @@ namespace Robust.Client.Audio.Midi
|
||||
/// <param name="frameTime"></param>
|
||||
void FrameUpdate(float frameTime);
|
||||
|
||||
/// <summary>
|
||||
/// Volume, in db.
|
||||
/// </summary>
|
||||
float Volume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, MIDI support is available.
|
||||
/// </summary>
|
||||
@@ -72,9 +63,11 @@ namespace Robust.Client.Audio.Midi
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
|
||||
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public bool IsAvailable
|
||||
{
|
||||
get
|
||||
@@ -85,12 +78,29 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<MidiRenderer> _renderers = new();
|
||||
[ViewVariables]
|
||||
private readonly List<IMidiRenderer> _renderers = new();
|
||||
|
||||
private bool _alive = true;
|
||||
private Settings? _settings;
|
||||
private Thread? _midiThread;
|
||||
private ISawmill _midiSawmill = default!;
|
||||
private float _volume = 0f;
|
||||
private bool _volumeDirty = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Volume
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseTo(_volume, value))
|
||||
return;
|
||||
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, value);
|
||||
_volumeDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string[] LinuxSoundfonts =
|
||||
{
|
||||
@@ -117,12 +127,19 @@ namespace Robust.Client.Audio.Midi
|
||||
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
|
||||
private void InitializeFluidsynth()
|
||||
{
|
||||
if (FluidsynthInitialized || _failedInitialize) return;
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
|
||||
{
|
||||
_volume = value;
|
||||
_volumeDirty = true;
|
||||
}, true);
|
||||
|
||||
_midiSawmill = Logger.GetSawmill("midi");
|
||||
_sawmill = Logger.GetSawmill("midi.fluidsynth");
|
||||
_loggerDelegate = LoggerDelegate;
|
||||
@@ -175,18 +192,6 @@ namespace Robust.Client.Audio.Midi
|
||||
_sawmill.Log(rLevel, message);
|
||||
}
|
||||
|
||||
/*
|
||||
public bool IsMidiFile(string filename)
|
||||
{
|
||||
return SoundFont.IsMidiFile(filename);
|
||||
}
|
||||
|
||||
public bool IsSoundfontFile(string filename)
|
||||
{
|
||||
return SoundFont.IsSoundFont(filename);
|
||||
}
|
||||
*/
|
||||
|
||||
public IMidiRenderer? GetNewRenderer()
|
||||
{
|
||||
if (!FluidsynthInitialized)
|
||||
@@ -220,7 +225,7 @@ namespace Robust.Client.Audio.Midi
|
||||
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
|
||||
renderer.LoadSoundfont(FallbackSoundfont);
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
foreach (var filepath in LinuxSoundfonts)
|
||||
{
|
||||
@@ -238,19 +243,21 @@ namespace Robust.Client.Audio.Midi
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
|
||||
renderer.LoadSoundfont(OsxSoundfont, true);
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
|
||||
renderer.LoadSoundfont(WindowsSoundfont, true);
|
||||
}
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
_renderers.Add(renderer);
|
||||
}
|
||||
|
||||
return renderer;
|
||||
}
|
||||
@@ -268,74 +275,79 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
|
||||
// Update positions of streams every frame.
|
||||
lock (_renderers)
|
||||
foreach (var renderer in _renderers)
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
if (renderer.Disposed)
|
||||
continue;
|
||||
|
||||
if(_volumeDirty)
|
||||
renderer.Source.SetVolume(Volume);
|
||||
|
||||
if (!renderer.Mono)
|
||||
{
|
||||
if (renderer.Disposed)
|
||||
continue;
|
||||
|
||||
if (!renderer.Mono)
|
||||
{
|
||||
renderer.Source.SetGlobal();
|
||||
continue;
|
||||
}
|
||||
|
||||
MapCoordinates? mapPos = null;
|
||||
if (renderer.TrackingCoordinates != null)
|
||||
{
|
||||
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
|
||||
}
|
||||
else if (renderer.TrackingEntity != null)
|
||||
{
|
||||
mapPos = renderer.TrackingEntity.Transform.MapPosition;
|
||||
}
|
||||
|
||||
if (mapPos != null)
|
||||
{
|
||||
var pos = mapPos.Value;
|
||||
if (pos.MapId != _eyeManager.CurrentMap)
|
||||
{
|
||||
renderer.Source.SetVolume(-10000000);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length > 0)
|
||||
{
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
pos.Position,
|
||||
sourceRelative.Normalized,
|
||||
OcclusionCollisionMask),
|
||||
sourceRelative.Length,
|
||||
renderer.TrackingEntity);
|
||||
}
|
||||
renderer.Source.SetOcclusion(occlusion);
|
||||
}
|
||||
|
||||
if (renderer.Source.SetPosition(pos.Position))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (renderer.TrackingEntity != null)
|
||||
{
|
||||
renderer.Source.SetVelocity(renderer.TrackingEntity.GlobalLinearVelocity());
|
||||
}
|
||||
|
||||
if (float.IsNaN(pos.Position.X) || float.IsNaN(pos.Position.Y))
|
||||
{
|
||||
// just duck out instead of move to NaN
|
||||
renderer.Source.SetOcclusion(float.MaxValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
_midiSawmill?.Warning("Interrupting positional audio, can't set position.");
|
||||
renderer.Source.StopPlaying();
|
||||
}
|
||||
renderer.Source.SetGlobal();
|
||||
continue;
|
||||
}
|
||||
|
||||
MapCoordinates? mapPos = null;
|
||||
if (renderer.TrackingCoordinates != null)
|
||||
{
|
||||
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
|
||||
}
|
||||
else if (renderer.TrackingEntity != null)
|
||||
{
|
||||
mapPos = renderer.TrackingEntity.Transform.MapPosition;
|
||||
}
|
||||
|
||||
if (mapPos != null)
|
||||
{
|
||||
var pos = mapPos.Value;
|
||||
if (pos.MapId != _eyeManager.CurrentMap)
|
||||
{
|
||||
renderer.Source.SetVolume(-10000000);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length > 0)
|
||||
{
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
pos.Position,
|
||||
sourceRelative.Normalized,
|
||||
OcclusionCollisionMask),
|
||||
sourceRelative.Length,
|
||||
renderer.TrackingEntity);
|
||||
}
|
||||
|
||||
renderer.Source.SetOcclusion(occlusion);
|
||||
}
|
||||
|
||||
if (renderer.Source.SetPosition(pos.Position))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (renderer.TrackingEntity != null)
|
||||
{
|
||||
renderer.Source.SetVelocity(renderer.TrackingEntity.GlobalLinearVelocity());
|
||||
}
|
||||
|
||||
if (float.IsNaN(pos.Position.X) || float.IsNaN(pos.Position.Y))
|
||||
{
|
||||
// just duck out instead of move to NaN
|
||||
renderer.Source.SetOcclusion(float.MaxValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
_midiSawmill?.Warning("Interrupting positional audio, can't set position.");
|
||||
renderer.Source.StopPlaying();
|
||||
}
|
||||
}
|
||||
|
||||
_volumeDirty = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -346,6 +358,7 @@ namespace Robust.Client.Audio.Midi
|
||||
while (_alive)
|
||||
{
|
||||
lock (_renderers)
|
||||
{
|
||||
for (var i = 0; i < _renderers.Count; i++)
|
||||
{
|
||||
var renderer = _renderers[i];
|
||||
@@ -353,10 +366,11 @@ namespace Robust.Client.Audio.Midi
|
||||
renderer.Render();
|
||||
else
|
||||
{
|
||||
((IMidiRenderer)renderer).InternalDispose();
|
||||
renderer.InternalDispose();
|
||||
_renderers.Remove(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
@@ -367,9 +381,13 @@ namespace Robust.Client.Audio.Midi
|
||||
_alive = false;
|
||||
_midiThread?.Join();
|
||||
_settings?.Dispose();
|
||||
foreach (var renderer in _renderers)
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
renderer?.Dispose();
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
renderer?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (FluidsynthInitialized && !_failedInitialize)
|
||||
@@ -424,6 +442,7 @@ namespace Robust.Client.Audio.Midi
|
||||
var span = new Span<byte>(buf.ToPointer(), length);
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
|
||||
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
|
||||
try
|
||||
{
|
||||
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
|
||||
@@ -447,6 +466,7 @@ namespace Robust.Client.Audio.Midi
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -468,10 +488,12 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
public override int Close(IntPtr sfHandle)
|
||||
{
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
if (!_openStreams.Remove((int) sfHandle, out var stream))
|
||||
return -1;
|
||||
|
||||
stream.Dispose();
|
||||
_openStreams.Remove((int) sfHandle);
|
||||
return 0;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using MidiEvent = NFluidsynth.MidiEvent;
|
||||
|
||||
namespace Robust.Client.Audio.Midi
|
||||
@@ -21,6 +23,17 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
public interface IMidiRenderer : IDisposable
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The buffered audio source of this renderer.
|
||||
/// </summary>
|
||||
internal IClydeBufferedAudioSource Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this renderer has been disposed or not.
|
||||
/// </summary>
|
||||
bool Disposed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This controls whether the midi file being played will loop or not.
|
||||
/// </summary>
|
||||
@@ -110,6 +123,11 @@ namespace Robust.Client.Audio.Midi
|
||||
/// </summary>
|
||||
void StopAllNotes();
|
||||
|
||||
/// <summary>
|
||||
/// Render and play MIDI to the audio source.
|
||||
/// </summary>
|
||||
internal void Render();
|
||||
|
||||
/// <summary>
|
||||
/// Loads a new soundfont into the renderer.
|
||||
/// </summary>
|
||||
@@ -159,7 +177,7 @@ namespace Robust.Client.Audio.Midi
|
||||
internal void InternalDispose();
|
||||
}
|
||||
|
||||
public class MidiRenderer : IMidiRenderer
|
||||
internal class MidiRenderer : IMidiRenderer
|
||||
{
|
||||
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
@@ -186,10 +204,16 @@ namespace Robust.Client.Audio.Midi
|
||||
private const int SampleRate = 44100;
|
||||
private const int Buffers = SampleRate / 2205;
|
||||
private readonly object _playerStateLock = new();
|
||||
private bool _debugEvents = false;
|
||||
private SequencerClientId _synthRegister;
|
||||
private SequencerClientId _debugRegister;
|
||||
public IClydeBufferedAudioSource Source { get; set; }
|
||||
IClydeBufferedAudioSource IMidiRenderer.Source => Source;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Disposed { get; private set; } = false;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public byte MidiProgram
|
||||
{
|
||||
get => _midiProgram;
|
||||
@@ -203,6 +227,7 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public byte MidiBank
|
||||
{
|
||||
get => _midiBank;
|
||||
@@ -216,6 +241,7 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public uint MidiSoundfont
|
||||
{
|
||||
get => _midiSoundfont;
|
||||
@@ -229,10 +255,16 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool DisablePercussionChannel { get; set; } = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool DisableProgramChangeEvent { get; set; } = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PlayerTotalTick => _player?.GetTotalTicks ?? 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PlayerTick
|
||||
{
|
||||
get => _player?.CurrentTick ?? 0;
|
||||
@@ -243,12 +275,19 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public uint SequencerTick => _sequencer?.Tick ?? 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public double SequencerTimeScale => _sequencer?.TimeScale ?? 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Mono { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public MidiRendererStatus Status { get; private set; } = MidiRendererStatus.None;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool LoopMidi
|
||||
{
|
||||
get => _loopMidi;
|
||||
@@ -260,10 +299,11 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public IEntity? TrackingEntity { get; set; } = null;
|
||||
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
|
||||
|
||||
internal bool Free { get; set; } = false;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
|
||||
|
||||
internal MidiRenderer(Settings settings, SoundFontLoader soundFontLoader, bool mono = true)
|
||||
{
|
||||
@@ -276,6 +316,7 @@ namespace Robust.Client.Audio.Midi
|
||||
_soundFontLoader = soundFontLoader;
|
||||
_synth = new Synth(_settings);
|
||||
_sequencer = new Sequencer(false);
|
||||
_debugRegister = _sequencer.RegisterClient("honk", DumpSequencerEvent);
|
||||
_synthRegister = _sequencer.RegisterFluidsynth(_synth);
|
||||
|
||||
_synth.AddSoundFontLoader(soundFontLoader);
|
||||
@@ -285,6 +326,27 @@ namespace Robust.Client.Audio.Midi
|
||||
Source.StartPlaying();
|
||||
}
|
||||
|
||||
private void DumpSequencerEvent(uint time, SequencerEvent @event)
|
||||
{
|
||||
// ReSharper disable once UseStringInterpolation
|
||||
_midiSawmill.Debug(string.Format(
|
||||
"{0:D8}: {1} chan:{2:D2} key:{3:D5} bank:{4:D2} ctrl:{5:D5} dur:{6:D5} pitch:{7:D5} prog:{8:D3} val:{9:D5} vel:{10:D5}",
|
||||
time,
|
||||
@event.Type.ToString().PadLeft(22),
|
||||
@event.Channel,
|
||||
@event.Key,
|
||||
@event.Bank,
|
||||
@event.Control,
|
||||
@event.Duration,
|
||||
@event.Pitch,
|
||||
@event.Program,
|
||||
@event.Value,
|
||||
@event.Velocity));
|
||||
|
||||
@event.Dest = _synthRegister;
|
||||
_sequencer.SendNow(@event);
|
||||
}
|
||||
|
||||
public bool OpenInput()
|
||||
{
|
||||
if (Disposed)
|
||||
@@ -294,7 +356,11 @@ namespace Robust.Client.Audio.Midi
|
||||
Status = MidiRendererStatus.Input;
|
||||
StopAllNotes();
|
||||
|
||||
_driver = new MidiDriver(_settings, MidiDriverEventHandler);
|
||||
lock (_playerStateLock)
|
||||
{
|
||||
_driver = new MidiDriver(_settings, MidiDriverEventHandler);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -332,8 +398,13 @@ namespace Robust.Client.Audio.Midi
|
||||
{
|
||||
if (Status != MidiRendererStatus.Input) return false;
|
||||
Status = MidiRendererStatus.None;
|
||||
_driver?.Dispose();
|
||||
_driver = null;
|
||||
|
||||
lock (_playerStateLock)
|
||||
{
|
||||
_driver?.Dispose();
|
||||
_driver = null;
|
||||
}
|
||||
|
||||
StopAllNotes();
|
||||
return true;
|
||||
}
|
||||
@@ -357,7 +428,8 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
public void StopAllNotes()
|
||||
{
|
||||
_synth.AllNotesOff(-1);
|
||||
lock(_playerStateLock)
|
||||
_synth.AllNotesOff(-1);
|
||||
}
|
||||
|
||||
public void LoadSoundfont(string filename, bool resetPresets = false)
|
||||
@@ -372,13 +444,15 @@ namespace Robust.Client.Audio.Midi
|
||||
public event Action<Shared.Audio.Midi.MidiEvent>? OnMidiEvent;
|
||||
public event Action? OnMidiPlayerFinished;
|
||||
|
||||
internal void Render(int length = SampleRate / 250)
|
||||
void IMidiRenderer.Render()
|
||||
{
|
||||
Render();
|
||||
}
|
||||
|
||||
private void Render(int length = SampleRate / 250)
|
||||
{
|
||||
if (Disposed) return;
|
||||
|
||||
// SSE needs this.
|
||||
DebugTools.Assert(length % 4 == 0, "Sample length must be multiple of 4");
|
||||
|
||||
var buffersProcessed = Source.GetNumberOfBuffersProcessed();
|
||||
if(buffersProcessed == Buffers) _midiSawmill.Warning("MIDI buffer overflow!");
|
||||
if (buffersProcessed == 0) return;
|
||||
@@ -393,36 +467,16 @@ namespace Robust.Client.Audio.Midi
|
||||
Source.GetBuffersProcessed(buffers);
|
||||
|
||||
lock (_playerStateLock)
|
||||
{
|
||||
// _sequencer.Process(10);
|
||||
_synth?.WriteSampleFloat(length * buffers.Length, audio, 0, Mono ? 1 : 2,
|
||||
audio, Mono ? length * buffers.Length : 1, Mono ? 1 : 2);
|
||||
|
||||
}
|
||||
if (Mono) // Turn audio to mono
|
||||
{
|
||||
var l = length * buffers.Length;
|
||||
|
||||
if (Sse.IsSupported)
|
||||
{
|
||||
fixed (float* ptr = audio)
|
||||
{
|
||||
for (var j = 0; j < l; j += 4)
|
||||
{
|
||||
var k = j + l;
|
||||
|
||||
var jV = Sse.LoadVector128(ptr + j);
|
||||
var kV = Sse.LoadVector128(ptr + k);
|
||||
|
||||
Sse.Store(j + ptr, Sse.Add(jV, kV));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var j = 0; j < l; j++)
|
||||
{
|
||||
var k = j + l;
|
||||
audio[j] = ((audio[k] + audio[j]));
|
||||
}
|
||||
}
|
||||
NumericsHelpers.Add(audio[..l], audio[l..]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < buffers.Length; i++)
|
||||
@@ -452,6 +506,7 @@ namespace Robust.Client.Audio.Midi
|
||||
var timestamp = SequencerTick;
|
||||
var midiEv = (Shared.Audio.Midi.MidiEvent) midiEvent;
|
||||
midiEv.Tick = timestamp;
|
||||
midiEvent.Dispose();
|
||||
SendMidiEvent(midiEv);
|
||||
return 0;
|
||||
}
|
||||
@@ -462,6 +517,7 @@ namespace Robust.Client.Audio.Midi
|
||||
var timestamp = SequencerTick;
|
||||
var midiEv = (Shared.Audio.Midi.MidiEvent) midiEvent;
|
||||
midiEv.Tick = timestamp;
|
||||
midiEvent.Dispose();
|
||||
SendMidiEvent(midiEv);
|
||||
return 0;
|
||||
}
|
||||
@@ -478,16 +534,16 @@ namespace Robust.Client.Audio.Midi
|
||||
lock(_playerStateLock)
|
||||
switch (midiEvent.Type)
|
||||
{
|
||||
// Note On 0x80
|
||||
case 144:
|
||||
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
|
||||
break;
|
||||
|
||||
// Note Off - 0x90
|
||||
// Note Off - 0x80
|
||||
case 128:
|
||||
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
|
||||
break;
|
||||
|
||||
// Note On 0x90
|
||||
case 144:
|
||||
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
|
||||
break;
|
||||
|
||||
// After Touch - 0xA
|
||||
case 160:
|
||||
_synth.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);
|
||||
@@ -522,6 +578,12 @@ namespace Robust.Client.Audio.Midi
|
||||
case 81:
|
||||
// System Messages - 0xF0
|
||||
case 240:
|
||||
switch (midiEvent.Control)
|
||||
{
|
||||
case 11:
|
||||
_synth.AllNotesOff(midiEvent.Channel);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
||||
default:
|
||||
@@ -543,7 +605,7 @@ namespace Robust.Client.Audio.Midi
|
||||
if (Disposed) return;
|
||||
|
||||
var seqEv = (SequencerEvent) midiEvent;
|
||||
seqEv.Dest = _synthRegister;
|
||||
seqEv.Dest = _debugEvents ? _debugRegister : _synthRegister;
|
||||
_sequencer.SendAt(seqEv, time, absolute);
|
||||
}
|
||||
|
||||
@@ -566,10 +628,15 @@ namespace Robust.Client.Audio.Midi
|
||||
void IMidiRenderer.InternalDispose()
|
||||
{
|
||||
Source?.Dispose();
|
||||
_driver?.Dispose();
|
||||
|
||||
// Do NOT dispose of the sequencer after the synth or it'll cause a segfault for some fucking reason.
|
||||
_sequencer?.UnregisterClient(_debugRegister);
|
||||
_sequencer?.UnregisterClient(_synthRegister);
|
||||
_sequencer?.Dispose();
|
||||
|
||||
_synth?.Dispose();
|
||||
_player?.Dispose();
|
||||
_driver?.Dispose();
|
||||
_sequencer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net;
|
||||
using Robust.Client.Debugging;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -13,6 +13,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -25,6 +26,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IPlayerManager _playMan = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _entityLookup = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IDiscordRichPresence _discord = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
@@ -97,6 +99,25 @@ namespace Robust.Client
|
||||
_net.ClientDisconnect(reason);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StartSinglePlayer()
|
||||
{
|
||||
DebugTools.Assert(RunLevel < ClientRunLevel.Connecting);
|
||||
DebugTools.Assert(!_net.IsConnected);
|
||||
_playMan.Startup();
|
||||
_playMan.LocalPlayer!.Name = PlayerNameOverride ?? _configManager.GetCVar(CVars.PlayerName);
|
||||
OnRunLevelChanged(ClientRunLevel.SinglePlayerGame);
|
||||
GameStartedSetup();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StopSinglePlayer()
|
||||
{
|
||||
DebugTools.Assert(RunLevel == ClientRunLevel.SinglePlayerGame);
|
||||
DebugTools.Assert(!_net.IsConnected);
|
||||
GameStoppedReset();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<RunLevelChangedEventArgs>? RunLevelChanged;
|
||||
|
||||
@@ -133,7 +154,7 @@ namespace Robust.Client
|
||||
var userId = _net.ServerChannel.UserId;
|
||||
_discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString());
|
||||
// start up player management
|
||||
_playMan.Startup(_net.ServerChannel!);
|
||||
_playMan.Startup();
|
||||
|
||||
_playMan.LocalPlayer!.UserId = userId;
|
||||
_playMan.LocalPlayer.Name = userName;
|
||||
@@ -146,16 +167,13 @@ namespace Robust.Client
|
||||
/// receiving states when they join the lobby.
|
||||
/// </summary>
|
||||
/// <param name="session">Session of the player.</param>
|
||||
private void OnPlayerJoinedServer(IPlayerSession session)
|
||||
private void OnPlayerJoinedServer(ICommonSession session)
|
||||
{
|
||||
DebugTools.Assert(RunLevel < ClientRunLevel.Connected);
|
||||
OnRunLevelChanged(ClientRunLevel.Connected);
|
||||
|
||||
_entityManager.Startup();
|
||||
_mapManager.Startup();
|
||||
GameStartedSetup();
|
||||
|
||||
_timing.ResetSimTime();
|
||||
_timing.Paused = false;
|
||||
PlayerJoinedServer?.Invoke(this, new PlayerEventArgs(session));
|
||||
}
|
||||
|
||||
@@ -163,7 +181,7 @@ namespace Robust.Client
|
||||
/// Player is joining the game
|
||||
/// </summary>
|
||||
/// <param name="session">Session of the player.</param>
|
||||
private void OnPlayerJoinedGame(IPlayerSession session)
|
||||
private void OnPlayerJoinedGame(ICommonSession session)
|
||||
{
|
||||
DebugTools.Assert(RunLevel >= ClientRunLevel.Connected);
|
||||
OnRunLevelChanged(ClientRunLevel.InGame);
|
||||
@@ -190,11 +208,25 @@ namespace Robust.Client
|
||||
PlayerLeaveServer?.Invoke(this, new PlayerEventArgs(_playMan.LocalPlayer?.Session));
|
||||
|
||||
LastDisconnectReason = args.Reason;
|
||||
GameStoppedReset();
|
||||
}
|
||||
|
||||
private void GameStartedSetup()
|
||||
{
|
||||
_entityManager.Startup();
|
||||
_mapManager.Startup();
|
||||
_entityLookup.Startup();
|
||||
|
||||
_timing.ResetSimTime();
|
||||
_timing.Paused = false;
|
||||
}
|
||||
|
||||
private void GameStoppedReset()
|
||||
{
|
||||
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
|
||||
_gameStates.Reset();
|
||||
_playMan.Shutdown();
|
||||
IoCManager.Resolve<IEntityLookup>().Shutdown();
|
||||
_entityLookup.Shutdown();
|
||||
_entityManager.Shutdown();
|
||||
_mapManager.Shutdown();
|
||||
_discord.ClearPresence();
|
||||
@@ -251,6 +283,11 @@ namespace Robust.Client
|
||||
/// The client is now in the game, moving around.
|
||||
/// </summary>
|
||||
InGame,
|
||||
|
||||
/// <summary>
|
||||
/// The client is now in singleplayer mode, in-game.
|
||||
/// </summary>
|
||||
SinglePlayerGame,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -261,12 +298,12 @@ namespace Robust.Client
|
||||
/// <summary>
|
||||
/// The session that triggered the event.
|
||||
/// </summary>
|
||||
private IPlayerSession? Session { get; }
|
||||
private ICommonSession? Session { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the class.
|
||||
/// </summary>
|
||||
public PlayerEventArgs(IPlayerSession? session)
|
||||
public PlayerEventArgs(ICommonSession? session)
|
||||
{
|
||||
Session = session;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using Robust.Client.Prototypes;
|
||||
using Robust.Client.Reflection;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Client.ViewVariables;
|
||||
@@ -27,6 +28,7 @@ using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client
|
||||
{
|
||||
@@ -36,22 +38,31 @@ namespace Robust.Client
|
||||
{
|
||||
SharedIoC.RegisterIoC();
|
||||
|
||||
IoCManager.Register<IGameTiming, ClientGameTiming>();
|
||||
IoCManager.Register<IClientGameTiming, ClientGameTiming>();
|
||||
IoCManager.Register<IPrototypeManager, ClientPrototypeManager>();
|
||||
IoCManager.Register<IMapManager, ClientMapManager>();
|
||||
IoCManager.Register<IMapManagerInternal, ClientMapManager>();
|
||||
IoCManager.Register<IClientMapManager, ClientMapManager>();
|
||||
IoCManager.Register<IEntityManager, ClientEntityManager>();
|
||||
IoCManager.Register<IEntityLookup, SharedEntityLookup>();
|
||||
IoCManager.Register<IEntityLookup, EntityLookup>();
|
||||
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
|
||||
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
|
||||
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
IoCManager.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
IoCManager.Register<GameController, GameController>();
|
||||
IoCManager.Register<IGameController, GameController>();
|
||||
IoCManager.Register<IGameControllerInternal, GameController>();
|
||||
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
|
||||
IoCManager.Register<IResourceManager, ResourceCache>();
|
||||
IoCManager.Register<IResourceManagerInternal, ResourceCache>();
|
||||
IoCManager.Register<IResourceCache, ResourceCache>();
|
||||
IoCManager.Register<IResourceCacheInternal, ResourceCache>();
|
||||
IoCManager.Register<IClientNetManager, NetManager>();
|
||||
IoCManager.Register<IClientEntityManager, ClientEntityManager>();
|
||||
IoCManager.Register<IEntityNetworkManager, ClientEntityNetworkManager>();
|
||||
IoCManager.Register<IClientEntityManagerInternal, ClientEntityManager>();
|
||||
IoCManager.Register<IEntityNetworkManager, ClientEntityManager>();
|
||||
IoCManager.Register<IClientGameStateManager, ClientGameStateManager>();
|
||||
IoCManager.Register<IBaseClient, BaseClient>();
|
||||
IoCManager.Register<IPlayerManager, PlayerManager>();
|
||||
@@ -63,8 +74,6 @@ namespace Robust.Client
|
||||
IoCManager.Register<IDebugDrawingManager, DebugDrawingManager>();
|
||||
IoCManager.Register<ILightManager, LightManager>();
|
||||
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
|
||||
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IMidiManager, MidiManager>();
|
||||
IoCManager.Register<IAuthManager, AuthManager>();
|
||||
switch (mode)
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Robust.Client
|
||||
public bool Launcher { get; }
|
||||
public string? Username { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> CVars { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
|
||||
|
||||
// Manual parser because C# has no good command line parsing libraries. Also dependencies bad.
|
||||
// Also I don't like spending 100ms parsing command line args. Do you?
|
||||
@@ -31,6 +32,7 @@ namespace Robust.Client
|
||||
var launcher = false;
|
||||
string? username = null;
|
||||
var cvars = new List<(string, string)>();
|
||||
var logLevels = new List<(string, string)>();
|
||||
var mountOptions = new MountOptions();
|
||||
|
||||
using var enumerator = args.GetEnumerator();
|
||||
@@ -124,6 +126,26 @@ namespace Robust.Client
|
||||
|
||||
mountOptions.DirMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--loglevel")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing loglevel sawmill.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var loglevel = enumerator.Current;
|
||||
DebugTools.AssertNotNull(loglevel);
|
||||
var pos = loglevel.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in loglevel.");
|
||||
return false;
|
||||
}
|
||||
|
||||
logLevels.Add((loglevel[..pos], loglevel[(pos + 1)..]));
|
||||
}
|
||||
else if (arg == "--help")
|
||||
{
|
||||
PrintHelp();
|
||||
@@ -142,6 +164,7 @@ namespace Robust.Client
|
||||
launcher,
|
||||
username,
|
||||
cvars,
|
||||
logLevels,
|
||||
connectAddress,
|
||||
ss14Address,
|
||||
mountOptions);
|
||||
@@ -162,6 +185,7 @@ Options:
|
||||
--launcher Run in launcher mode (no main menu, auto connect).
|
||||
--username Override username.
|
||||
--cvar Specifies an additional cvar overriding the config file. Syntax is <key>=<value>
|
||||
--loglevel Specifies an additional sawmill log level overriding the default values. Syntax is <key>=<value>
|
||||
--mount-dir Resource directory to mount.
|
||||
--mount-zip Resource zip to mount.
|
||||
--help Display this help text and exit.
|
||||
@@ -175,6 +199,7 @@ Options:
|
||||
bool launcher,
|
||||
string? username,
|
||||
IReadOnlyCollection<(string key, string value)> cVars,
|
||||
IReadOnlyCollection<(string key, string value)> logLevels,
|
||||
string connectAddress, string? ss14Address,
|
||||
MountOptions mountOptions)
|
||||
{
|
||||
@@ -184,6 +209,7 @@ Options:
|
||||
Launcher = launcher;
|
||||
Username = username;
|
||||
CVars = cVars;
|
||||
LogLevels = logLevels;
|
||||
ConnectAddress = connectAddress;
|
||||
Ss14Address = ss14Address;
|
||||
MountOptions = mountOptions;
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Robust.Client.Console
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc cref="IClientConsoleHost" />
|
||||
internal class ClientConsoleHost : ConsoleHost, IClientConsoleHost
|
||||
{
|
||||
@@ -45,11 +45,15 @@ namespace Robust.Client.Console
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
NetManager.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME, HandleConCmdReg);
|
||||
NetManager.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME, HandleConCmdAck);
|
||||
NetManager.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME, ProcessCommand);
|
||||
NetManager.RegisterNetMessage<MsgConCmdReg>(HandleConCmdReg);
|
||||
NetManager.RegisterNetMessage<MsgConCmdAck>(HandleConCmdAck);
|
||||
NetManager.RegisterNetMessage<MsgConCmd>(ProcessCommand);
|
||||
|
||||
Reset();
|
||||
_requestedCommands = false;
|
||||
NetManager.Connected += OnNetworkConnected;
|
||||
|
||||
LoadConsoleCommands();
|
||||
SendServerCommandRequest();
|
||||
LogManager.RootSawmill.AddHandler(new DebugConsoleLogHandler(this));
|
||||
}
|
||||
|
||||
@@ -61,17 +65,6 @@ namespace Robust.Client.Console
|
||||
ExecuteCommand(null, text);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
AvailableCommands.Clear();
|
||||
_requestedCommands = false;
|
||||
NetManager.Connected += OnNetworkConnected;
|
||||
|
||||
LoadConsoleCommands();
|
||||
SendServerCommandRequest();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<AddStringArgs>? AddString;
|
||||
|
||||
@@ -97,7 +90,7 @@ namespace Robust.Client.Console
|
||||
return;
|
||||
|
||||
// echo the command locally
|
||||
WriteError(null, "> " + command);
|
||||
WriteLine(null, "> " + command);
|
||||
|
||||
//Commands are processed locally and then sent to the server to be processed there again.
|
||||
var args = new List<string>();
|
||||
@@ -142,6 +135,9 @@ namespace Robust.Client.Console
|
||||
private void OutputText(string text, bool local, bool error)
|
||||
{
|
||||
AddString?.Invoke(this, new AddStringArgs(text, local, error));
|
||||
|
||||
var level = error ? LogLevel.Warning : LogLevel.Info;
|
||||
Logger.LogS(level, "CON", text);
|
||||
}
|
||||
|
||||
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)
|
||||
|
||||
@@ -27,6 +27,7 @@ using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
@@ -77,7 +78,7 @@ namespace Robust.Client.Console.Commands
|
||||
message.Append($"net ID: {registration.NetID}");
|
||||
}
|
||||
|
||||
message.Append($", NSE: {registration.NetworkSynchronizeExistence}, references:");
|
||||
message.Append($", References:");
|
||||
|
||||
shell.WriteLine(message.ToString());
|
||||
|
||||
@@ -280,12 +281,12 @@ namespace Robust.Client.Console.Commands
|
||||
internal class SnapGridGetCell : IConsoleCommand
|
||||
{
|
||||
public string Command => "sggcell";
|
||||
public string Help => "sggcell <gridID> <vector2i> [offset]\nThat vector2i param is in the form x<int>,y<int>.";
|
||||
public string Help => "sggcell <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.";
|
||||
public string Description => "Lists entities on a snap grid cell.";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2 && args.Length != 3)
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
@@ -293,7 +294,6 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
string gridId = args[0];
|
||||
string indices = args[1];
|
||||
string offset = args.Length == 3 ? args[2] : "Center";
|
||||
|
||||
if (!int.TryParse(args[0], out var id))
|
||||
{
|
||||
@@ -307,29 +307,17 @@ namespace Robust.Client.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
SnapGridOffset selectedOffset;
|
||||
if (Enum.IsDefined(typeof(SnapGridOffset), offset))
|
||||
{
|
||||
selectedOffset = (SnapGridOffset)Enum.Parse(typeof(SnapGridOffset), offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteError("given offset type is not defined");
|
||||
return;
|
||||
}
|
||||
|
||||
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
if (mapMan.GridExists(new GridId(int.Parse(gridId, CultureInfo.InvariantCulture))))
|
||||
{
|
||||
foreach (var entity in
|
||||
mapMan.GetGrid(new GridId(int.Parse(gridId, CultureInfo.InvariantCulture))).GetSnapGridCell(
|
||||
mapMan.GetGrid(new GridId(int.Parse(gridId, CultureInfo.InvariantCulture))).GetAnchoredEntities(
|
||||
new Vector2i(
|
||||
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
|
||||
int.Parse(indices.Split(',')[1], CultureInfo.InvariantCulture)),
|
||||
selectedOffset))
|
||||
int.Parse(indices.Split(',')[1], CultureInfo.InvariantCulture))))
|
||||
{
|
||||
shell.WriteLine(entity.Owner.Uid.ToString());
|
||||
shell.WriteLine(entity.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -549,7 +537,10 @@ namespace Robust.Client.Console.Commands
|
||||
var scroll = new ScrollContainer();
|
||||
tabContainer.AddChild(scroll);
|
||||
//scroll.SetAnchorAndMarginPreset(Control.LayoutPreset.Wide);
|
||||
var vBox = new VBoxContainer();
|
||||
var vBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
scroll.AddChild(vBox);
|
||||
|
||||
var progressBar = new ProgressBar { MaxValue = 10, Value = 5 };
|
||||
@@ -607,7 +598,10 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
|
||||
var group = new ButtonGroup();
|
||||
var vBoxRadioButtons = new VBoxContainer();
|
||||
var vBoxRadioButtons = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
vBoxRadioButtons.AddChild(new Button
|
||||
@@ -623,8 +617,9 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
TabContainer.SetTabTitle(vBoxRadioButtons, "Radio buttons!!");
|
||||
|
||||
tabContainer.AddChild(new VBoxContainer
|
||||
tabContainer.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
Name = "Slider",
|
||||
Children =
|
||||
{
|
||||
@@ -678,10 +673,10 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Gets the system clipboard";
|
||||
public string Help => "getclipboard";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<IClipboardManager>();
|
||||
shell.WriteLine(mgr.GetText());
|
||||
shell.WriteLine(await mgr.GetText());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1097,15 +1092,8 @@ namespace Robust.Client.Console.Commands
|
||||
var key = (Keyboard.Key) parsed!;
|
||||
|
||||
var name = clyde.GetKeyName(key);
|
||||
var scanCode = clyde.GetKeyScanCode(key);
|
||||
var nameScanCode = clyde.GetKeyNameScanCode(scanCode);
|
||||
|
||||
shell.WriteLine($"name: '{name}' scan code: '{scanCode}' name via scan code: '{nameScanCode}'");
|
||||
}
|
||||
else if (int.TryParse(args[0], out var scanCode))
|
||||
{
|
||||
var nameScanCode = clyde.GetKeyNameScanCode(scanCode);
|
||||
shell.WriteLine($"name via scan code: '{nameScanCode}'");
|
||||
shell.WriteLine($"name: '{name}' ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
Robust.Client/Console/Commands/LightBBCommand.cs
Normal file
19
Robust.Client/Console/Commands/LightBBCommand.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
#if DEBUG
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
internal sealed class LightDebugCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "lightbb";
|
||||
public string Description => "Toggles whether to show light bounding boxes";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<DebugLightTreeSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -25,6 +25,34 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class MonitorInfoCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "monitorinfo";
|
||||
public string Description => "";
|
||||
public string Help => "Usage: monitorinfo <id>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 1)
|
||||
{
|
||||
shell.WriteError("Expected one argument.");
|
||||
return;
|
||||
}
|
||||
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
var monitor = clyde.EnumerateMonitors().Single(c => c.Id == int.Parse(args[0]));
|
||||
|
||||
shell.WriteLine($"{monitor.Id}: {monitor.Name}");
|
||||
shell.WriteLine($"Video modes:");
|
||||
|
||||
foreach (var mode in monitor.VideoModes)
|
||||
{
|
||||
shell.WriteLine($" {mode.Width}x{mode.Height} {mode.RefreshRate} Hz {mode.RedBits}/{mode.GreenBits}/{mode.BlueBits}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class SetMonitorCommand : IConsoleCommand
|
||||
{
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
class QuitCommand : IConsoleCommand
|
||||
class HardQuitCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "quit";
|
||||
public string Command => "hardquit";
|
||||
public string Description => "Kills the game client instantly.";
|
||||
public string Help => "Kills the game client instantly, leaving no traces. No telling the server goodbye";
|
||||
|
||||
@@ -14,4 +15,16 @@ namespace Robust.Client.Console.Commands
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
class QuitCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "quit";
|
||||
public string Description => "Shuts down the game client gracefully.";
|
||||
public string Help => "Properly shuts down the game client, notifying the connected server and such.";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
IoCManager.Resolve<IGameController>().Shutdown("quit command used");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
@@ -41,7 +40,7 @@ namespace Robust.Client.Console.Commands
|
||||
var mgr = IoCManager.Resolve<IScriptClient>();
|
||||
if (!mgr.CanScript)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("You do not have server side scripting permission."));
|
||||
shell.WriteError("You do not have server side scripting permission.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,11 +11,6 @@ namespace Robust.Client.Console
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the console to a post-initialized state.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
|
||||
event EventHandler<AddStringArgs> AddString;
|
||||
event EventHandler<AddFormattedMessageArgs> AddFormatted;
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ namespace Robust.Client.Console
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_netManager.RegisterNetMessage<MsgScriptStop>(MsgScriptStop.NAME);
|
||||
_netManager.RegisterNetMessage<MsgScriptEval>(MsgScriptEval.NAME);
|
||||
_netManager.RegisterNetMessage<MsgScriptStart>(MsgScriptStart.NAME);
|
||||
_netManager.RegisterNetMessage<MsgScriptResponse>(MsgScriptResponse.NAME, ReceiveScriptResponse);
|
||||
_netManager.RegisterNetMessage<MsgScriptStartAck>(MsgScriptStartAck.NAME, ReceiveScriptStartAckResponse);
|
||||
_netManager.RegisterNetMessage<MsgScriptStop>();
|
||||
_netManager.RegisterNetMessage<MsgScriptEval>();
|
||||
_netManager.RegisterNetMessage<MsgScriptStart>();
|
||||
_netManager.RegisterNetMessage<MsgScriptResponse>(ReceiveScriptResponse);
|
||||
_netManager.RegisterNetMessage<MsgScriptStartAck>(ReceiveScriptStartAckResponse);
|
||||
}
|
||||
|
||||
private void ReceiveScriptStartAckResponse(MsgScriptStartAck message)
|
||||
@@ -30,7 +30,8 @@ namespace Robust.Client.Console
|
||||
|
||||
var console = new ScriptConsoleServer(this, session);
|
||||
_activeConsoles.Add(session, console);
|
||||
console.Open();
|
||||
// FIXME: When this is Open(), resizing the window will cause its position to get NaN'd.
|
||||
console.OpenCentered();
|
||||
}
|
||||
|
||||
private void ReceiveScriptResponse(MsgScriptResponse message)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#if CLIENT_SCRIPTING
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
@@ -14,11 +13,11 @@ using Microsoft.CodeAnalysis.Text;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.ViewVariables;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Scripting;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
|
||||
#nullable enable
|
||||
|
||||
@@ -43,14 +42,14 @@ namespace Robust.Client.Console
|
||||
|
||||
public ScriptConsoleClient()
|
||||
{
|
||||
Title = Loc.GetString("Robust C# Interactive (CLIENT)");
|
||||
Title = "Robust C# Interactive (CLIENT)";
|
||||
ScriptInstanceShared.InitDummy();
|
||||
|
||||
_globals = new ScriptGlobalsImpl(this);
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
OutputPanel.AddText(Loc.GetString(@"Robust C# interactive console (CLIENT)."));
|
||||
OutputPanel.AddText("Robust C# interactive console (CLIENT).");
|
||||
OutputPanel.AddText(">");
|
||||
}
|
||||
|
||||
@@ -118,7 +117,7 @@ namespace Robust.Client.Console
|
||||
}
|
||||
else
|
||||
{
|
||||
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager);
|
||||
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager).AddReferences(typeof(Image).Assembly);
|
||||
newScript = CSharpScript.Create(code, options, typeof(ScriptGlobals));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,10 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Scripting;
|
||||
using Robust.Shared.Timing;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Robust.Client.Console
|
||||
{
|
||||
@@ -20,7 +19,7 @@ namespace Robust.Client.Console
|
||||
{
|
||||
private readonly IReflectionManager _reflectionManager;
|
||||
|
||||
private readonly VBoxContainer _watchesVBox;
|
||||
private readonly BoxContainer _watchesVBox;
|
||||
private readonly LineEdit _addWatchEdit;
|
||||
private readonly Button _addWatchButton;
|
||||
|
||||
@@ -31,29 +30,32 @@ namespace Robust.Client.Console
|
||||
|
||||
ScriptInstanceShared.InitDummy();
|
||||
|
||||
Title = Loc.GetString("Watch Window");
|
||||
Title = "Watch Window";
|
||||
|
||||
var mainVBox = new VBoxContainer
|
||||
var mainVBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
MinSize = (500, 300),
|
||||
Children =
|
||||
{
|
||||
(_watchesVBox = new VBoxContainer
|
||||
(_watchesVBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
VerticalExpand = true
|
||||
}),
|
||||
new HBoxContainer
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
(_addWatchEdit = new HistoryLineEdit
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
PlaceHolder = Loc.GetString("Add watch (C# interactive)")
|
||||
PlaceHolder = "Add watch (C# interactive)"
|
||||
}),
|
||||
(_addWatchButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("Add")
|
||||
Text = "Add"
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -107,8 +109,9 @@ namespace Robust.Client.Console
|
||||
Button delButton;
|
||||
_runner = runner;
|
||||
|
||||
AddChild(new HBoxContainer
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
(_outputLabel = new Label
|
||||
@@ -118,7 +121,7 @@ namespace Robust.Client.Console
|
||||
}),
|
||||
(delButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("Remove")
|
||||
Text = "Remove"
|
||||
}),
|
||||
}
|
||||
});
|
||||
@@ -168,8 +171,9 @@ namespace Robust.Client.Console
|
||||
public CompilationErrorControl(string message)
|
||||
{
|
||||
Button delButton;
|
||||
AddChild(new HBoxContainer
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
@@ -178,7 +182,7 @@ namespace Robust.Client.Console
|
||||
ClipText = true,
|
||||
HorizontalExpand = true
|
||||
},
|
||||
(delButton = new Button {Text = Loc.GetString("Remove")})
|
||||
(delButton = new Button {Text = "Remove"})
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -5,10 +5,15 @@ namespace Robust.Client
|
||||
public static void Start(string[] args)
|
||||
{
|
||||
#if FULL_RELEASE
|
||||
throw new System.InvalidOperationException("ContentStart is not available on a full release.");
|
||||
throw new System.InvalidOperationException("ContentStart.Start is not available on a full release.");
|
||||
#else
|
||||
GameController.Start(args, true);
|
||||
GameController.Start(args, new GameControllerOptions(), true);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void StartLibrary(string[] args, GameControllerOptions options)
|
||||
{
|
||||
GameController.Start(args, options, true, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
@@ -22,7 +23,7 @@ namespace Robust.Client.Debugging
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
@@ -69,7 +70,7 @@ namespace Robust.Client.Debugging
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityPositionOverlay(_entityManager, _eyeManager));
|
||||
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, _eyeManager));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -104,22 +105,23 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
switch (currentSpace)
|
||||
switch (args.Space)
|
||||
{
|
||||
case OverlaySpace.ScreenSpace:
|
||||
DrawScreen((DrawingHandleScreen) handle);
|
||||
DrawScreen(args);
|
||||
break;
|
||||
case OverlaySpace.WorldSpace:
|
||||
DrawWorld((DrawingHandleWorld) handle);
|
||||
DrawWorld(args);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void DrawScreen(DrawingHandleScreen screenHandle)
|
||||
private void DrawScreen(in OverlayDrawArgs args)
|
||||
{
|
||||
var screenHandle = args.ScreenHandle;
|
||||
var lineHeight = _font.GetLineHeight(1f);
|
||||
Vector2 drawPos = _hoverStartScreen + new Vector2(20, 0) + new Vector2(0, -(_hoverBodies.Count * 4 * lineHeight / 2f));
|
||||
int row = 0;
|
||||
@@ -128,31 +130,32 @@ namespace Robust.Client.Debugging
|
||||
{
|
||||
if (body != _hoverBodies[0])
|
||||
{
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), "------");
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), "------");
|
||||
row++;
|
||||
}
|
||||
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Entity}");
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
|
||||
row++;
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
|
||||
row++;
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
|
||||
row++;
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
|
||||
row++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void DrawWorld(DrawingHandleWorld worldHandle)
|
||||
private void DrawWorld(in OverlayDrawArgs args)
|
||||
{
|
||||
var worldHandle = args.WorldHandle;
|
||||
worldHandle.UseShader(_shader);
|
||||
var drawing = new PhysDrawingAdapter(worldHandle);
|
||||
|
||||
_hoverBodies.Clear();
|
||||
var mouseScreenPos = _inputManager.MouseScreenPosition;
|
||||
var mouseWorldPos = _eyeManager.ScreenToMap(mouseScreenPos).Position;
|
||||
_hoverStartScreen = mouseScreenPos;
|
||||
_hoverStartScreen = mouseScreenPos.Position;
|
||||
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
@@ -161,20 +164,31 @@ namespace Robust.Client.Debugging
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
|
||||
var colorEdge = Color.Red.WithAlpha(0.33f);
|
||||
var drawnJoints = new HashSet<Joint>();
|
||||
|
||||
foreach (var physBody in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(mapId, viewport))
|
||||
{
|
||||
// all entities have a TransformComponent
|
||||
var transform = physBody.Entity.Transform;
|
||||
var transform = physBody.Owner.Transform;
|
||||
|
||||
var worldBox = physBody.GetWorldAABB(_mapManager);
|
||||
var worldBox = physBody.GetWorldAABB();
|
||||
if (worldBox.IsEmpty()) continue;
|
||||
|
||||
foreach (var fixture in physBody.Fixtures)
|
||||
{
|
||||
var shape = fixture.Shape;
|
||||
var sleepPercent = physBody.Awake ? physBody.SleepTime / sleepThreshold : 1.0f;
|
||||
var sleepPercent = physBody.Awake ? 0.0f : 1.0f;
|
||||
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
|
||||
drawing.SetTransform(in Matrix3.Identity);
|
||||
}
|
||||
|
||||
foreach (var joint in physBody.Joints)
|
||||
{
|
||||
if (drawnJoints.Contains(joint)) continue;
|
||||
drawnJoints.Add(joint);
|
||||
|
||||
joint.DebugDraw(drawing, in viewport);
|
||||
drawing.SetTransform(in Matrix3.Identity);
|
||||
}
|
||||
|
||||
if (worldBox.Contains(mouseWorldPos))
|
||||
@@ -187,17 +201,6 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
|
||||
{
|
||||
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
|
||||
|
||||
foreach (var rune in str.EnumerateRunes())
|
||||
{
|
||||
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
|
||||
baseLine += new Vector2(advance, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private class PhysDrawingAdapter : DebugDrawingHandle
|
||||
{
|
||||
private readonly DrawingHandleWorld _handle;
|
||||
@@ -258,34 +261,33 @@ namespace Robust.Client.Debugging
|
||||
|
||||
private sealed class EntityPositionOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEntityLookup _lookup;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager)
|
||||
public EntityPositionOverlay(IEntityLookup lookup, IEyeManager eyeManager)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_lookup = lookup;
|
||||
_eyeManager = eyeManager;
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
const float stubLength = 0.25f;
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) handle;
|
||||
foreach (var entity in _entityManager.GetEntities())
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
foreach (var entity in _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, viewport))
|
||||
{
|
||||
var transform = entity.Transform;
|
||||
if (transform.MapID != _eyeManager.CurrentMap ||
|
||||
!_eyeManager.GetWorldViewport().Contains(transform.WorldPosition))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var center = transform.WorldPosition;
|
||||
var xLine = transform.WorldRotation.RotateVec(Vector2.UnitX);
|
||||
var yLine = transform.WorldRotation.RotateVec(Vector2.UnitY);
|
||||
var worldRotation = transform.WorldRotation;
|
||||
|
||||
var xLine = worldRotation.RotateVec(Vector2.UnitX);
|
||||
var yLine = worldRotation.RotateVec(Vector2.UnitY);
|
||||
|
||||
worldHandle.DrawLine(center, center + xLine * stubLength, Color.Red);
|
||||
worldHandle.DrawLine(center, center + yLine * stubLength, Color.Green);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
@@ -54,7 +54,7 @@ namespace Robust.Client.Debugging
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_net.RegisterNetMessage<MsgRay>(MsgRay.NAME, HandleDrawRay);
|
||||
_net.RegisterNetMessage<MsgRay>(HandleDrawRay);
|
||||
}
|
||||
|
||||
private void HandleDrawRay(MsgRay msg)
|
||||
@@ -87,8 +87,9 @@ namespace Robust.Client.Debugging
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var handle = args.WorldHandle;
|
||||
foreach (var ray in _owner.raysWithLifeTime)
|
||||
{
|
||||
handle.DrawLine(
|
||||
|
||||
@@ -125,11 +125,11 @@ namespace Robust.Client.Debugging
|
||||
_physics = system;
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (_physics.Flags == PhysicsDebugFlags.None) return;
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) handle;
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
if ((_physics.Flags & PhysicsDebugFlags.Shapes) != 0)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client
|
||||
{
|
||||
public void Main(IMainArgs args)
|
||||
{
|
||||
Start(args.Args, contentStart: false, args);
|
||||
Start(args.Args, new GameControllerOptions(), contentStart: false, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Robust.LoaderApi;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client
|
||||
{
|
||||
internal partial class GameController
|
||||
{
|
||||
private IGameLoop _mainLoop = default!;
|
||||
private IGameLoop? _mainLoop;
|
||||
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
|
||||
private static bool _hasStarted;
|
||||
|
||||
private Thread? _gameThread;
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Start(args);
|
||||
Start(args, new GameControllerOptions());
|
||||
}
|
||||
|
||||
public static void Start(string[] args, bool contentStart = false, IMainArgs? loaderArgs=null)
|
||||
public static void Start(string[] args, GameControllerOptions options, bool contentStart = false, IMainArgs? loaderArgs=null)
|
||||
{
|
||||
if (_hasStarted)
|
||||
{
|
||||
@@ -30,11 +36,11 @@ namespace Robust.Client
|
||||
|
||||
if (CommandLineArgs.TryParse(args, out var parsed))
|
||||
{
|
||||
ParsedMain(parsed, contentStart, loaderArgs);
|
||||
ParsedMain(parsed, contentStart, loaderArgs, options);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs)
|
||||
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions options)
|
||||
{
|
||||
IoCManager.InitThread();
|
||||
|
||||
@@ -42,23 +48,16 @@ namespace Robust.Client
|
||||
|
||||
InitIoC(mode);
|
||||
|
||||
var gc = (GameController) IoCManager.Resolve<IGameController>();
|
||||
var gc = IoCManager.Resolve<GameController>();
|
||||
gc.SetCommandLineArgs(args);
|
||||
gc._loaderArgs = loaderArgs;
|
||||
|
||||
// When the game is ran with the startup executable being content,
|
||||
// we have to disable the separate load context.
|
||||
// Otherwise the content assemblies will be loaded twice which causes *many* fun bugs.
|
||||
gc._disableAssemblyLoadContext = contentStart;
|
||||
if (!gc.Startup())
|
||||
{
|
||||
Logger.Fatal("Failed to start game controller!");
|
||||
return;
|
||||
}
|
||||
gc.MainLoop(mode);
|
||||
gc.ContentStart = contentStart;
|
||||
|
||||
Logger.Debug("Goodbye");
|
||||
IoCManager.Clear();
|
||||
gc.Run(mode, options);
|
||||
}
|
||||
|
||||
public void OverrideMainLoop(IGameLoop gameLoop)
|
||||
@@ -66,52 +65,68 @@ namespace Robust.Client
|
||||
_mainLoop = gameLoop;
|
||||
}
|
||||
|
||||
public void MainLoop(DisplayMode mode)
|
||||
public void Run(DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
|
||||
{
|
||||
if (_mainLoop == null)
|
||||
if (!StartupSystemSplash(options, logHandlerFactory))
|
||||
{
|
||||
_mainLoop = new GameLoop(_gameTiming)
|
||||
{
|
||||
SleepMode = mode == DisplayMode.Headless ? SleepMode.Delay : SleepMode.None
|
||||
};
|
||||
Logger.Fatal("Failed to start game controller!");
|
||||
return;
|
||||
}
|
||||
|
||||
_mainLoop.Tick += (sender, args) =>
|
||||
if (_clyde.SeparateWindowThread)
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
{
|
||||
Tick(args);
|
||||
}
|
||||
};
|
||||
var stackSize = _configurationManager.GetCVar(CVars.SysGameThreadStackSize);
|
||||
var priority = (ThreadPriority) _configurationManager.GetCVar(CVars.SysGameThreadPriority);
|
||||
|
||||
_mainLoop.Render += (sender, args) =>
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
_gameThread = new Thread(() => GameThreadMain(mode), stackSize)
|
||||
{
|
||||
_gameTiming.CurFrame++;
|
||||
_clyde.Render();
|
||||
}
|
||||
};
|
||||
_mainLoop.Input += (sender, args) =>
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
{
|
||||
Input(args);
|
||||
}
|
||||
};
|
||||
IsBackground = false,
|
||||
Priority = priority,
|
||||
Name = "Game thread",
|
||||
};
|
||||
|
||||
_mainLoop.Update += (sender, args) =>
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
{
|
||||
Update(args);
|
||||
}
|
||||
};
|
||||
_gameThread.Start();
|
||||
|
||||
// set GameLoop.Running to false to return from this function.
|
||||
_mainLoop.Run();
|
||||
// Will block until game exit
|
||||
_clyde.EnterWindowLoop();
|
||||
|
||||
if (_gameThread.IsAlive)
|
||||
{
|
||||
Logger.Debug("Window loop exited; waiting for game thread to exit");
|
||||
_gameThread.Join();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ContinueStartupAndLoop(mode);
|
||||
}
|
||||
|
||||
Cleanup();
|
||||
|
||||
Logger.Debug("Goodbye");
|
||||
IoCManager.Clear();
|
||||
}
|
||||
|
||||
private void GameThreadMain(DisplayMode mode)
|
||||
{
|
||||
IoCManager.InitThread(_dependencyCollection);
|
||||
|
||||
ContinueStartupAndLoop(mode);
|
||||
|
||||
// Game thread exited, make sure window thread unblocks to finish shutdown.
|
||||
_clyde.TerminateWindowLoop();
|
||||
}
|
||||
|
||||
private void ContinueStartupAndLoop(DisplayMode mode)
|
||||
{
|
||||
if (!StartupContinue(mode))
|
||||
{
|
||||
Logger.Fatal("Failed to start game controller!");
|
||||
return;
|
||||
}
|
||||
|
||||
DebugTools.AssertNotNull(_mainLoop);
|
||||
_mainLoop!.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Management;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.Audio.Midi;
|
||||
@@ -49,6 +49,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IClientConsoleHost _console = default!;
|
||||
[Dependency] private readonly ITimerManager _timerManager = default!;
|
||||
[Dependency] private readonly IClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
|
||||
[Dependency] private readonly IOverlayManagerInternal _overlayManager = default!;
|
||||
@@ -60,37 +61,39 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IFontManagerInternal _fontManager = default!;
|
||||
[Dependency] private readonly IModLoaderInternal _modLoader = default!;
|
||||
[Dependency] private readonly IScriptClient _scriptClient = default!;
|
||||
[Dependency] private readonly IComponentManager _componentManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IRobustMappedStringSerializer _stringSerializer = default!;
|
||||
[Dependency] private readonly IAuthManager _authManager = default!;
|
||||
[Dependency] private readonly IMidiManager _midiManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
private CommandLineArgs? _commandLineArgs;
|
||||
private bool _disableAssemblyLoadContext;
|
||||
|
||||
// Arguments for loader-load. Not used otherwise.
|
||||
private IMainArgs? _loaderArgs;
|
||||
|
||||
public bool ContentStart { get; set; } = false;
|
||||
public GameControllerOptions Options { get; private set; } = new();
|
||||
public InitialLaunchState LaunchState { get; private set; } = default!;
|
||||
|
||||
public bool LoadConfigAndUserData { get; set; } = true;
|
||||
|
||||
public void SetCommandLineArgs(CommandLineArgs args)
|
||||
{
|
||||
_commandLineArgs = args;
|
||||
}
|
||||
|
||||
public bool Startup(Func<ILogHandler>? logHandlerFactory = null)
|
||||
internal bool StartupContinue(DisplayMode displayMode)
|
||||
{
|
||||
if (!StartupSystemSplash(logHandlerFactory))
|
||||
return false;
|
||||
_clyde.InitializePostWindowing();
|
||||
_clyde.SetWindowTitle(Options.DefaultWindowTitle);
|
||||
|
||||
_taskManager.Initialize();
|
||||
_fontManager.SetFontDpi((uint) _configurationManager.GetCVar(CVars.DisplayFontDpi));
|
||||
|
||||
// Disable load context usage on content start.
|
||||
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
|
||||
_modLoader.SetUseLoadContext(!_disableAssemblyLoadContext);
|
||||
_modLoader.SetEnableSandboxing(true);
|
||||
_modLoader.SetUseLoadContext(!ContentStart);
|
||||
_modLoader.SetEnableSandboxing(Options.Sandboxing);
|
||||
|
||||
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), "Content."))
|
||||
if (!_modLoader.TryLoadModulesFrom(Options.AssemblyDirectory, Options.ContentModulePrefix))
|
||||
{
|
||||
Logger.Fatal("Errors while loading content assemblies.");
|
||||
return false;
|
||||
@@ -109,17 +112,17 @@ namespace Robust.Client
|
||||
|
||||
_resourceCache.PreloadTextures();
|
||||
_userInterfaceManager.Initialize();
|
||||
_eyeManager.Initialize();
|
||||
_networkManager.Initialize(false);
|
||||
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
|
||||
_serializer.Initialize();
|
||||
_inputManager.Initialize();
|
||||
_console.Initialize();
|
||||
_prototypeManager.Initialize();
|
||||
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
|
||||
_prototypeManager.LoadDirectory(Options.PrototypeDirectory);
|
||||
_prototypeManager.Resync();
|
||||
_mapManager.Initialize();
|
||||
_entityManager.Initialize();
|
||||
IoCManager.Resolve<IEntityLookup>().Initialize();
|
||||
_gameStateManager.Initialize();
|
||||
_placementManager.Initialize();
|
||||
_viewVariablesManager.Initialize();
|
||||
@@ -138,9 +141,51 @@ namespace Robust.Client
|
||||
|
||||
GC.Collect();
|
||||
|
||||
// Setup main loop
|
||||
if (_mainLoop == null)
|
||||
{
|
||||
_mainLoop = new GameLoop(_gameTiming)
|
||||
{
|
||||
SleepMode = displayMode == DisplayMode.Headless ? SleepMode.Delay : SleepMode.None
|
||||
};
|
||||
}
|
||||
|
||||
_mainLoop.Tick += (sender, args) =>
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
{
|
||||
Tick(args);
|
||||
}
|
||||
};
|
||||
|
||||
_mainLoop.Render += (sender, args) =>
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
{
|
||||
_gameTiming.CurFrame++;
|
||||
_clyde.Render();
|
||||
}
|
||||
};
|
||||
_mainLoop.Input += (sender, args) =>
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
{
|
||||
Input(args);
|
||||
}
|
||||
};
|
||||
|
||||
_mainLoop.Update += (sender, args) =>
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
{
|
||||
Update(args);
|
||||
}
|
||||
};
|
||||
|
||||
_clyde.Ready();
|
||||
|
||||
if ((_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
|
||||
if (!Options.DisableCommandLineConnect &&
|
||||
(_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
|
||||
&& LaunchState.ConnectEndpoint != null)
|
||||
{
|
||||
_client.ConnectToServer(LaunchState.ConnectEndpoint);
|
||||
@@ -149,13 +194,34 @@ namespace Robust.Client
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool StartupSystemSplash(Func<ILogHandler>? logHandlerFactory)
|
||||
internal bool StartupSystemSplash(GameControllerOptions options, Func<ILogHandler>? logHandlerFactory)
|
||||
{
|
||||
Options = options;
|
||||
ReadInitialLaunchState();
|
||||
|
||||
SetupLogging(_logManager, logHandlerFactory ?? (() => new ConsoleLogHandler()));
|
||||
|
||||
_taskManager.Initialize();
|
||||
if (_commandLineArgs != null)
|
||||
{
|
||||
foreach (var (sawmill, level) in _commandLineArgs.LogLevels)
|
||||
{
|
||||
LogLevel? logLevel;
|
||||
if (level == "null")
|
||||
logLevel = null;
|
||||
else
|
||||
{
|
||||
if (!Enum.TryParse<LogLevel>(level, out var result))
|
||||
{
|
||||
System.Console.WriteLine($"LogLevel {level} does not exist!");
|
||||
continue;
|
||||
}
|
||||
logLevel = result;
|
||||
}
|
||||
_logManager.GetSawmill(sawmill).Level = logLevel;
|
||||
}
|
||||
}
|
||||
|
||||
ProgramShared.PrintRuntimeInfo(_logManager.RootSawmill);
|
||||
|
||||
// Figure out user data directory.
|
||||
var userDataDir = GetUserDataDir();
|
||||
@@ -167,9 +233,9 @@ namespace Robust.Client
|
||||
_configurationManager.LoadCVarsFromAssembly(typeof(GameController).Assembly); // Client
|
||||
_configurationManager.LoadCVarsFromAssembly(typeof(IConfigurationManager).Assembly); // Shared
|
||||
|
||||
if (LoadConfigAndUserData)
|
||||
if (Options.LoadConfigAndUserData)
|
||||
{
|
||||
var configFile = Path.Combine(userDataDir, "client_config.toml");
|
||||
var configFile = Path.Combine(userDataDir, Options.ConfigFileName);
|
||||
if (File.Exists(configFile))
|
||||
{
|
||||
// Load config from user data if available.
|
||||
@@ -191,9 +257,14 @@ namespace Robust.Client
|
||||
|
||||
ProfileOptSetup.Setup(_configurationManager);
|
||||
|
||||
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
|
||||
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
|
||||
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory, Options.AssemblyDirectory,
|
||||
Options.LoadContentResources, _loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
|
||||
|
||||
ProgramShared.DoMounts(_resourceCache, _commandLineArgs?.MountOptions, "Content.Client", _loaderArgs != null);
|
||||
if (_loaderArgs != null)
|
||||
{
|
||||
_stringSerializer.EnableCaching = false;
|
||||
@@ -206,18 +277,16 @@ namespace Robust.Client
|
||||
_clyde.KeyUp += KeyUp;
|
||||
_clyde.KeyDown += KeyDown;
|
||||
_clyde.MouseWheel += MouseWheel;
|
||||
_clyde.CloseWindow += Shutdown;
|
||||
_clyde.CloseWindow += args =>
|
||||
{
|
||||
if (args.Window == _clyde.MainWindow)
|
||||
{
|
||||
Shutdown("Main window closed");
|
||||
}
|
||||
};
|
||||
|
||||
// Bring display up as soon as resources are mounted.
|
||||
if (!_clyde.Initialize())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_clyde.SetWindowTitle("Space Station 14");
|
||||
|
||||
_fontManager.SetFontDpi((uint) _configurationManager.GetCVar(CVars.DisplayFontDpi));
|
||||
return true;
|
||||
return _clyde.InitializePreWindowing();
|
||||
}
|
||||
|
||||
private Stream? VerifierExtraLoadHandler(string arg)
|
||||
@@ -263,8 +332,10 @@ namespace Robust.Client
|
||||
|
||||
public void Shutdown(string? reason = null)
|
||||
{
|
||||
DebugTools.AssertNotNull(_mainLoop);
|
||||
|
||||
// Already got shut down I assume,
|
||||
if (!_mainLoop.Running)
|
||||
if (!_mainLoop!.Running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -294,12 +365,19 @@ namespace Robust.Client
|
||||
_timerManager.UpdateTimers(frameEventArgs);
|
||||
_taskManager.ProcessPendingTasks();
|
||||
|
||||
// GameStateManager is in full control of the simulation update.
|
||||
if (_client.RunLevel >= ClientRunLevel.Connected)
|
||||
// GameStateManager is in full control of the simulation update in multiplayer.
|
||||
if (_client.RunLevel == ClientRunLevel.InGame || _client.RunLevel == ClientRunLevel.Connected)
|
||||
{
|
||||
_gameStateManager.ApplyGameState();
|
||||
}
|
||||
|
||||
// In singleplayer, however, we're in full control instead.
|
||||
else if (_client.RunLevel == ClientRunLevel.SinglePlayerGame)
|
||||
{
|
||||
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds);
|
||||
_lookup.Update();
|
||||
}
|
||||
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
|
||||
}
|
||||
|
||||
@@ -320,11 +398,6 @@ namespace Robust.Client
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs);
|
||||
}
|
||||
|
||||
private void Render()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal static void SetupLogging(ILogManager logManager, Func<ILogHandler> logHandlerFactory)
|
||||
{
|
||||
logManager.RootSawmill.AddHandler(logHandlerFactory());
|
||||
@@ -397,7 +470,7 @@ namespace Robust.Client
|
||||
Clyde,
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
internal void Cleanup()
|
||||
{
|
||||
_networkManager.Shutdown("Client shutting down");
|
||||
_midiManager.Shutdown();
|
||||
75
Robust.Client/GameControllerOptions.cs
Normal file
75
Robust.Client/GameControllerOptions.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client
|
||||
{
|
||||
public class GameControllerOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether content sandboxing will be enabled & enforced.
|
||||
/// </summary>
|
||||
public bool Sandboxing { get; init; } = true;
|
||||
|
||||
// TODO: Expose mounting methods to games using Robust as a library.
|
||||
/// <summary>
|
||||
/// Lists of mount options to mount.
|
||||
/// </summary>
|
||||
public MountOptions MountOptions { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Name the userdata directory will have.
|
||||
/// </summary>
|
||||
public string UserDataDirectoryName { get; init; } = "Space Station 14";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the configuration file in the user data directory.
|
||||
/// </summary>
|
||||
public string ConfigFileName { get; init; } = "client_config.toml";
|
||||
|
||||
// TODO: Define engine branding from json file in resources.
|
||||
/// <summary>
|
||||
/// Default window title.
|
||||
/// </summary>
|
||||
public string DefaultWindowTitle { get; init; } = "Space Station 14";
|
||||
|
||||
/// <summary>
|
||||
/// Assemblies with this prefix will be loaded.
|
||||
/// </summary>
|
||||
public string ContentModulePrefix { get; init; } = "Content.";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the content build directory, for game pack mounting purposes.
|
||||
/// </summary>
|
||||
public string ContentBuildDirectory { get; init; } = "Content.Client";
|
||||
|
||||
/// <summary>
|
||||
/// Directory to load all assemblies from.
|
||||
/// </summary>
|
||||
public ResourcePath AssemblyDirectory { get; init; } = new(@"/Assemblies/");
|
||||
|
||||
/// <summary>
|
||||
/// Directory to load all prototypes from.
|
||||
/// </summary>
|
||||
public ResourcePath PrototypeDirectory { get; init; } = new(@"/Prototypes/");
|
||||
|
||||
/// <summary>
|
||||
/// Whether to disable mounting the "Resources/" folder on FULL_RELEASE.
|
||||
/// </summary>
|
||||
public bool ResourceMountDisabled { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to mount content resources when not on FULL_RELEASE.
|
||||
/// </summary>
|
||||
public bool LoadContentResources { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to load config and user data.
|
||||
/// </summary>
|
||||
public bool LoadConfigAndUserData { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to disable command line args server auto-connecting.
|
||||
/// </summary>
|
||||
public bool DisableCommandLineConnect { get; init; } = false;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,41 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class ClientComponentFactory : ComponentFactory
|
||||
internal class ClientComponentFactory : ComponentFactory
|
||||
{
|
||||
public ClientComponentFactory()
|
||||
public ClientComponentFactory(IDynamicTypeFactoryInternal typeFactory, IReflectionManager reflectionManager, IConsoleHost conHost)
|
||||
: base(typeFactory, reflectionManager, conHost)
|
||||
{
|
||||
// Required for the engine to work
|
||||
Register<MetaDataComponent>();
|
||||
RegisterReference<MetaDataComponent, IMetaDataComponent>();
|
||||
|
||||
// Required for the engine to work
|
||||
Register<TransformComponent>();
|
||||
RegisterReference<TransformComponent, ITransformComponent>();
|
||||
|
||||
Register<MapComponent>();
|
||||
RegisterReference<MapComponent, IMapComponent>();
|
||||
|
||||
Register<MapGridComponent>();
|
||||
RegisterReference<MapGridComponent, IMapGridComponent>();
|
||||
|
||||
Register<PhysicsComponent>();
|
||||
RegisterReference<PhysicsComponent, IPhysBody>();
|
||||
|
||||
Register<CollisionWakeComponent>();
|
||||
|
||||
Register<ContainerManagerComponent>();
|
||||
RegisterReference<ContainerManagerComponent, IContainerManager>();
|
||||
|
||||
RegisterIgnore("KeyBindingInput");
|
||||
|
||||
Register<InputComponent>();
|
||||
|
||||
Register<SpriteComponent>();
|
||||
RegisterReference<SpriteComponent, SharedSpriteComponent>();
|
||||
RegisterReference<SpriteComponent, ISpriteComponent>();
|
||||
|
||||
Register<ClientOccluderComponent>();
|
||||
RegisterReference<ClientOccluderComponent, OccluderComponent>();
|
||||
|
||||
Register<EyeComponent>();
|
||||
RegisterReference<EyeComponent, SharedEyeComponent>();
|
||||
|
||||
Register<AppearanceComponent>();
|
||||
RegisterReference<AppearanceComponent, SharedAppearanceComponent>();
|
||||
|
||||
Register<AppearanceTestComponent>();
|
||||
Register<SnapGridComponent>();
|
||||
|
||||
Register<ClientUserInterfaceComponent>();
|
||||
RegisterReference<ClientUserInterfaceComponent, SharedUserInterfaceComponent>();
|
||||
|
||||
Register<AnimationPlayerComponent>();
|
||||
|
||||
Register<TimerComponent>();
|
||||
RegisterClass<MetaDataComponent>();
|
||||
RegisterClass<TransformComponent>();
|
||||
RegisterClass<MapComponent>();
|
||||
RegisterClass<MapGridComponent>();
|
||||
RegisterClass<PhysicsComponent>();
|
||||
RegisterClass<CollisionWakeComponent>();
|
||||
RegisterClass<ClientUserInterfaceComponent>();
|
||||
RegisterClass<ContainerManagerComponent>();
|
||||
RegisterClass<InputComponent>();
|
||||
RegisterClass<SpriteComponent>();
|
||||
RegisterClass<ClientOccluderComponent>();
|
||||
RegisterClass<EyeComponent>();
|
||||
RegisterClass<AppearanceComponent>();
|
||||
RegisterClass<AppearanceTestComponent>();
|
||||
RegisterClass<SnapGridComponent>();
|
||||
RegisterClass<AnimationPlayerComponent>();
|
||||
RegisterClass<TimerComponent>();
|
||||
|
||||
#if DEBUG
|
||||
Register<DebugExceptionOnAddComponent>();
|
||||
Register<DebugExceptionInitializeComponent>();
|
||||
Register<DebugExceptionStartupComponent>();
|
||||
RegisterClass<DebugExceptionOnAddComponent>();
|
||||
RegisterClass<DebugExceptionInitializeComponent>();
|
||||
RegisterClass<DebugExceptionStartupComponent>();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Prometheus;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
@@ -13,288 +15,160 @@ namespace Robust.Client.GameObjects
|
||||
/// <summary>
|
||||
/// Manager for entities -- controls things like template loading and instantiation
|
||||
/// </summary>
|
||||
public sealed class ClientEntityManager : EntityManager, IClientEntityManager
|
||||
public sealed class ClientEntityManager : EntityManager, IClientEntityManagerInternal
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
#if EXCEPTION_TOLERANCE
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
#endif
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IClientNetManager _networkManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private int _nextClientEntityUid = EntityUid.ClientUid + 1;
|
||||
protected override int NextEntityUid { get; set; } = EntityUid.ClientUid + 1;
|
||||
|
||||
public override void Startup()
|
||||
public override void Initialize()
|
||||
{
|
||||
if (Started)
|
||||
{
|
||||
throw new InvalidOperationException("Startup() called multiple times");
|
||||
}
|
||||
SetupNetworking();
|
||||
ReceivedComponentMessage += (_, compMsg) => DispatchComponentMessage(compMsg);
|
||||
ReceivedSystemMessage += (_, systemMsg) => EventBus.RaiseEvent(EventSource.Network, systemMsg);
|
||||
|
||||
EntitySystemManager.Initialize();
|
||||
base.Startup();
|
||||
Started = true;
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
public List<EntityUid> ApplyEntityStates(EntityState[]? curEntStates, IEnumerable<EntityUid>? deletions,
|
||||
EntityState[]? nextEntStates)
|
||||
IEntity IClientEntityManagerInternal.CreateEntity(string? prototypeName, EntityUid? uid)
|
||||
{
|
||||
var toApply = new Dictionary<IEntity, (EntityState?, EntityState?)>();
|
||||
var toInitialize = new List<Entity>();
|
||||
var created = new List<EntityUid>();
|
||||
deletions ??= new EntityUid[0];
|
||||
return base.CreateEntity(prototypeName, uid);
|
||||
}
|
||||
|
||||
if (curEntStates != null && curEntStates.Length != 0)
|
||||
void IClientEntityManagerInternal.InitializeEntity(IEntity entity)
|
||||
{
|
||||
EntityManager.InitializeEntity((Entity)entity);
|
||||
}
|
||||
|
||||
void IClientEntityManagerInternal.StartEntity(IEntity entity)
|
||||
{
|
||||
base.StartEntity((Entity)entity);
|
||||
}
|
||||
|
||||
#region IEntityNetworkManager impl
|
||||
|
||||
public override IEntityNetworkManager EntityNetManager => this;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<NetworkComponentMessage>? ReceivedComponentMessage;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<object>? ReceivedSystemMessage;
|
||||
|
||||
private readonly PriorityQueue<(uint seq, MsgEntity msg)> _queue = new(new MessageTickComparer());
|
||||
private uint _incomingMsgSequence = 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetupNetworking()
|
||||
{
|
||||
_networkManager.RegisterNetMessage<MsgEntity>(HandleEntityNetworkMessage);
|
||||
}
|
||||
|
||||
public override void TickUpdate(float frameTime, Histogram? histogram)
|
||||
{
|
||||
using (histogram?.WithLabels("EntityNet").NewTimer())
|
||||
{
|
||||
foreach (var es in curEntStates)
|
||||
while (_queue.Count != 0 && _queue.Peek().msg.SourceTick <= _gameStateManager.CurServerTick)
|
||||
{
|
||||
//Known entities
|
||||
if (Entities.TryGetValue(es.Uid, out var entity))
|
||||
{
|
||||
toApply.Add(entity, (es, null));
|
||||
}
|
||||
else //Unknown entities
|
||||
{
|
||||
var metaState = (MetaDataComponentState?) es.ComponentStates
|
||||
?.FirstOrDefault(c => c.NetID == NetIDs.META_DATA);
|
||||
if (metaState == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!");
|
||||
}
|
||||
var newEntity = CreateEntity(metaState.PrototypeId, es.Uid);
|
||||
toApply.Add(newEntity, (es, null));
|
||||
toInitialize.Add(newEntity);
|
||||
created.Add(newEntity.Uid);
|
||||
}
|
||||
var (_, msg) = _queue.Take();
|
||||
// Logger.DebugS("net.ent", "Dispatching: {0}: {1}", seq, msg);
|
||||
DispatchMsgEntity(msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextEntStates != null && nextEntStates.Length != 0)
|
||||
{
|
||||
foreach (var es in nextEntStates)
|
||||
{
|
||||
if (Entities.TryGetValue(es.Uid, out var entity))
|
||||
{
|
||||
if (toApply.TryGetValue(entity, out var state))
|
||||
{
|
||||
toApply[entity] = (state.Item1, es);
|
||||
}
|
||||
else
|
||||
{
|
||||
toApply[entity] = (null, es);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure this is done after all entities have been instantiated.
|
||||
foreach (var kvStates in toApply)
|
||||
{
|
||||
var ent = kvStates.Key;
|
||||
var entity = (Entity) ent;
|
||||
HandleEntityState(entity.EntityManager.ComponentManager, entity, kvStates.Value.Item1,
|
||||
kvStates.Value.Item2);
|
||||
}
|
||||
|
||||
foreach (var id in deletions)
|
||||
{
|
||||
DeleteEntity(id);
|
||||
}
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
HashSet<Entity> brokenEnts = new HashSet<Entity>();
|
||||
#endif
|
||||
|
||||
foreach (var entity in toInitialize)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
InitializeEntity(entity);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("state", $"Server entity threw in Init: uid={entity.Uid}, proto={entity.Prototype}\n{e}");
|
||||
brokenEnts.Add(entity);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
foreach (var entity in toInitialize)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
if (brokenEnts.Contains(entity))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
#endif
|
||||
StartEntity(entity);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("state", $"Server entity threw in Start: uid={entity.Uid}, proto={entity.Prototype}\n{e}");
|
||||
brokenEnts.Add(entity);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
foreach (var entity in toInitialize)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
if (brokenEnts.Contains(entity))
|
||||
continue;
|
||||
#endif
|
||||
}
|
||||
#if EXCEPTION_TOLERANCE
|
||||
foreach (var entity in brokenEnts)
|
||||
{
|
||||
entity.Delete();
|
||||
}
|
||||
#endif
|
||||
|
||||
return created;
|
||||
base.TickUpdate(frameTime, histogram);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEntity CreateEntityUninitialized(string? prototypeName)
|
||||
public void SendSystemNetworkMessage(EntityEventArgs message)
|
||||
{
|
||||
return CreateEntity(prototypeName);
|
||||
SendSystemNetworkMessage(message, default(uint));
|
||||
}
|
||||
|
||||
public void SendSystemNetworkMessage(EntityEventArgs message, uint sequence)
|
||||
{
|
||||
var msg = _networkManager.CreateNetMessage<MsgEntity>();
|
||||
msg.Type = EntityMessageType.SystemMessage;
|
||||
msg.SystemMessage = message;
|
||||
msg.SourceTick = _gameTiming.CurTick;
|
||||
msg.Sequence = sequence;
|
||||
|
||||
_networkManager.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEntity CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates)
|
||||
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel channel)
|
||||
{
|
||||
var newEntity = CreateEntity(prototypeName, GenerateEntityUid());
|
||||
|
||||
if (TryGetEntity(coordinates.EntityId, out var entity))
|
||||
{
|
||||
newEntity.Transform.AttachParent(entity);
|
||||
newEntity.Transform.Coordinates = coordinates;
|
||||
}
|
||||
|
||||
return newEntity;
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEntity CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates)
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message)
|
||||
{
|
||||
var newEntity = CreateEntity(prototypeName, GenerateEntityUid());
|
||||
newEntity.Transform.AttachParent(_mapManager.GetMapEntity(coordinates.MapId));
|
||||
newEntity.Transform.WorldPosition = coordinates.Position;
|
||||
return newEntity;
|
||||
var netId = ComponentFactory.GetRegistration(component.GetType()).NetID;
|
||||
|
||||
if (!netId.HasValue)
|
||||
throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component));
|
||||
|
||||
var msg = _networkManager.CreateNetMessage<MsgEntity>();
|
||||
msg.Type = EntityMessageType.ComponentMessage;
|
||||
msg.EntityUid = entity.Uid;
|
||||
msg.NetId = netId.Value;
|
||||
msg.ComponentMessage = message;
|
||||
msg.SourceTick = _gameTiming.CurTick;
|
||||
|
||||
_networkManager.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEntity SpawnEntity(string? protoName, EntityCoordinates coordinates)
|
||||
private void HandleEntityNetworkMessage(MsgEntity message)
|
||||
{
|
||||
var newEnt = CreateEntityUninitialized(protoName, coordinates);
|
||||
InitializeAndStartEntity((Entity) newEnt);
|
||||
return newEnt;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEntity SpawnEntity(string? protoName, MapCoordinates coordinates)
|
||||
{
|
||||
var entity = CreateEntityUninitialized(protoName, coordinates);
|
||||
InitializeAndStartEntity((Entity) entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
protected override EntityUid GenerateEntityUid()
|
||||
{
|
||||
return new(_nextClientEntityUid++);
|
||||
}
|
||||
|
||||
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityState? curState,
|
||||
EntityState? nextState)
|
||||
{
|
||||
var compStateWork = new Dictionary<uint, (ComponentState? curState, ComponentState? nextState)>();
|
||||
var entityUid = entity.Uid;
|
||||
|
||||
if (curState?.ComponentChanges != null)
|
||||
if (message.SourceTick <= _gameStateManager.CurServerTick)
|
||||
{
|
||||
foreach (var compChange in curState.ComponentChanges)
|
||||
{
|
||||
if (compChange.Deleted)
|
||||
{
|
||||
if (compMan.TryGetComponent(entityUid, compChange.NetID, out var comp))
|
||||
{
|
||||
compMan.RemoveComponent(entityUid, comp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (compMan.HasComponent(entityUid, compChange.NetID))
|
||||
continue;
|
||||
|
||||
var newComp = (Component) _compFactory.GetComponent(compChange.ComponentName!);
|
||||
newComp.Owner = entity;
|
||||
compMan.AddComponent(entity, newComp, true);
|
||||
}
|
||||
}
|
||||
DispatchMsgEntity(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (curState?.ComponentStates != null)
|
||||
{
|
||||
foreach (var compState in curState.ComponentStates)
|
||||
{
|
||||
compStateWork[compState.NetID] = (compState, null);
|
||||
}
|
||||
}
|
||||
// MsgEntity is sent with ReliableOrdered so Lidgren guarantees ordering of incoming messages.
|
||||
// We still need to store a sequence input number to ensure ordering remains consistent in
|
||||
// the priority queue.
|
||||
_queue.Add((++_incomingMsgSequence, message));
|
||||
}
|
||||
|
||||
if (nextState?.ComponentStates != null)
|
||||
private void DispatchMsgEntity(MsgEntity message)
|
||||
{
|
||||
switch (message.Type)
|
||||
{
|
||||
foreach (var compState in nextState.ComponentStates)
|
||||
{
|
||||
if (compStateWork.TryGetValue(compState.NetID, out var state))
|
||||
{
|
||||
compStateWork[compState.NetID] = (state.curState, compState);
|
||||
}
|
||||
else
|
||||
{
|
||||
compStateWork[compState.NetID] = (null, compState);
|
||||
}
|
||||
}
|
||||
}
|
||||
case EntityMessageType.ComponentMessage:
|
||||
ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message));
|
||||
return;
|
||||
|
||||
foreach (var (netId, (cur, next)) in compStateWork)
|
||||
{
|
||||
if (compMan.TryGetComponent(entityUid, netId, out var component))
|
||||
{
|
||||
try
|
||||
{
|
||||
component.HandleComponentState(cur, next);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var wrapper = new ComponentStateApplyException(
|
||||
$"Failed to apply comp state: entity={component.Owner}, comp={component.Name}", e);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
_runtimeLog.LogException(wrapper, "Component state apply");
|
||||
#else
|
||||
throw wrapper;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The component can be null here due to interp.
|
||||
// Because the NEXT state will have a new component, but this one doesn't yet.
|
||||
// That's fine though.
|
||||
if (cur == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var eUid = entityUid;
|
||||
var eRegisteredNetUidName = _compFactory.GetRegistration(netId).Name;
|
||||
DebugTools.Assert(
|
||||
$"Component does not exist for state: entUid={eUid}, expectedNetId={netId}, expectedName={eRegisteredNetUidName}");
|
||||
}
|
||||
case EntityMessageType.SystemMessage:
|
||||
var msg = message.SystemMessage;
|
||||
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
|
||||
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(_playerManager.LocalPlayer!.Session), msg)!;
|
||||
ReceivedSystemMessage?.Invoke(this, msg);
|
||||
ReceivedSystemMessage?.Invoke(this, sessionMsg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class MessageTickComparer : IComparer<(uint seq, MsgEntity msg)>
|
||||
{
|
||||
public int Compare((uint seq, MsgEntity msg) x, (uint seq, MsgEntity msg) y)
|
||||
{
|
||||
var cmp = y.msg.SourceTick.CompareTo(x.msg.SourceTick);
|
||||
if (cmp != 0)
|
||||
{
|
||||
return cmp;
|
||||
}
|
||||
|
||||
return y.seq.CompareTo(x.seq);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// The client implementation of the Entity Network Manager.
|
||||
/// </summary>
|
||||
public class ClientEntityNetworkManager : IEntityNetworkManager
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager _networkManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<NetworkComponentMessage>? ReceivedComponentMessage;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<object>? ReceivedSystemMessage;
|
||||
|
||||
private readonly PriorityQueue<(uint seq, MsgEntity msg)> _queue = new(new MessageTickComparer());
|
||||
private uint _incomingMsgSequence = 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetupNetworking()
|
||||
{
|
||||
_networkManager.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, HandleEntityNetworkMessage);
|
||||
}
|
||||
|
||||
public void TickUpdate()
|
||||
{
|
||||
while (_queue.Count != 0 && _queue.Peek().msg.SourceTick <= _gameStateManager.CurServerTick)
|
||||
{
|
||||
var (_, msg) = _queue.Take();
|
||||
// Logger.DebugS("net.ent", "Dispatching: {0}: {1}", seq, msg);
|
||||
DispatchMsgEntity(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SendSystemNetworkMessage(EntityEventArgs message)
|
||||
{
|
||||
SendSystemNetworkMessage(message, default(uint));
|
||||
}
|
||||
|
||||
public void SendSystemNetworkMessage(EntityEventArgs message, uint sequence)
|
||||
{
|
||||
var msg = _networkManager.CreateNetMessage<MsgEntity>();
|
||||
msg.Type = EntityMessageType.SystemMessage;
|
||||
msg.SystemMessage = message;
|
||||
msg.SourceTick = _gameTiming.CurTick;
|
||||
msg.Sequence = sequence;
|
||||
|
||||
_networkManager.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel channel)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message)
|
||||
{
|
||||
if (!component.NetID.HasValue)
|
||||
throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component));
|
||||
|
||||
var msg = _networkManager.CreateNetMessage<MsgEntity>();
|
||||
msg.Type = EntityMessageType.ComponentMessage;
|
||||
msg.EntityUid = entity.Uid;
|
||||
msg.NetId = component.NetID.Value;
|
||||
msg.ComponentMessage = message;
|
||||
msg.SourceTick = _gameTiming.CurTick;
|
||||
|
||||
_networkManager.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
private void HandleEntityNetworkMessage(MsgEntity message)
|
||||
{
|
||||
if (message.SourceTick <= _gameStateManager.CurServerTick)
|
||||
{
|
||||
DispatchMsgEntity(message);
|
||||
return;
|
||||
}
|
||||
|
||||
// MsgEntity is sent with ReliableOrdered so Lidgren guarantees ordering of incoming messages.
|
||||
// We still need to store a sequence input number to ensure ordering remains consistent in
|
||||
// the priority queue.
|
||||
_queue.Add((++_incomingMsgSequence, message));
|
||||
}
|
||||
|
||||
private void DispatchMsgEntity(MsgEntity message)
|
||||
{
|
||||
switch (message.Type)
|
||||
{
|
||||
case EntityMessageType.ComponentMessage:
|
||||
ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message));
|
||||
return;
|
||||
|
||||
case EntityMessageType.SystemMessage:
|
||||
ReceivedSystemMessage?.Invoke(this, message.SystemMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class MessageTickComparer : IComparer<(uint seq, MsgEntity msg)>
|
||||
{
|
||||
public int Compare((uint seq, MsgEntity msg) x, (uint seq, MsgEntity msg) y)
|
||||
{
|
||||
var cmp = y.msg.SourceTick.CompareTo(x.msg.SourceTick);
|
||||
if (cmp != 0)
|
||||
{
|
||||
return cmp;
|
||||
}
|
||||
|
||||
return y.seq.CompareTo(x.seq);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(SharedAppearanceComponent))]
|
||||
public sealed class AppearanceComponent : SharedAppearanceComponent
|
||||
{
|
||||
[ViewVariables]
|
||||
@@ -94,8 +95,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
EntitySystem.Get<AppearanceSystem>()
|
||||
.EnqueueAppearanceUpdate(this);
|
||||
EntitySystem.Get<AppearanceSystem>().EnqueueUpdate(this);
|
||||
_appearanceDirty = true;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace Robust.Client.GameObjects
|
||||
_appearanceDirty = false;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
@@ -115,21 +115,6 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
|
||||
internal class SpriteLayerToggle : AppearanceVisualizer
|
||||
{
|
||||
public const string NAME = "sprite_layer_toggle";
|
||||
|
||||
public readonly object Key;
|
||||
public readonly int SpriteLayer;
|
||||
|
||||
public SpriteLayerToggle(object key, int spriteLayer)
|
||||
{
|
||||
Key = key;
|
||||
SpriteLayer = spriteLayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -10,6 +10,7 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(SharedEyeComponent))]
|
||||
public class EyeComponent : SharedEyeComponent
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
@@ -25,7 +26,7 @@ namespace Robust.Client.GameObjects
|
||||
[DataField("drawFov")]
|
||||
private bool _setDrawFovOnInitialize = true;
|
||||
[DataField("zoom")]
|
||||
private Vector2 _setZoomOnInitialize = Vector2.One/2f;
|
||||
private Vector2 _setZoomOnInitialize = Vector2.One;
|
||||
private Vector2 _offset = Vector2.Zero;
|
||||
|
||||
public IEye? Eye => _eye;
|
||||
@@ -115,7 +116,7 @@ namespace Robust.Client.GameObjects
|
||||
public MapCoordinates? Position => _eye?.Position;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
@@ -155,7 +156,7 @@ namespace Robust.Client.GameObjects
|
||||
VisibilityMask = state.VisibilityMask;
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
protected override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(OccluderComponent))]
|
||||
internal sealed class ClientOccluderComponent : OccluderComponent
|
||||
{
|
||||
internal SnapGridComponent? SnapGrid { get; private set; }
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
[ViewVariables] private (GridId, Vector2i) _lastPosition;
|
||||
[ViewVariables] internal OccluderDir Occluding { get; private set; }
|
||||
@@ -29,39 +32,36 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
if (Owner.TryGetComponent(out SnapGridComponent? snap))
|
||||
if (Owner.Transform.Anchored)
|
||||
{
|
||||
SnapGrid = snap;
|
||||
SnapGrid.OnPositionChanged += SnapGridOnPositionChanged;
|
||||
|
||||
SnapGridOnPositionChanged();
|
||||
AnchorStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void SnapGridOnPositionChanged()
|
||||
public void AnchorStateChanged()
|
||||
{
|
||||
SendDirty();
|
||||
_lastPosition = (Owner.Transform.GridID, SnapGrid!.Position);
|
||||
|
||||
if(!Owner.Transform.Anchored)
|
||||
return;
|
||||
|
||||
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
|
||||
_lastPosition = (Owner.Transform.GridID, grid.TileIndicesFor(Owner.Transform.Coordinates));
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
if (SnapGrid != null)
|
||||
{
|
||||
SnapGrid.OnPositionChanged -= SnapGridOnPositionChanged;
|
||||
}
|
||||
|
||||
SendDirty();
|
||||
}
|
||||
|
||||
private void SendDirty()
|
||||
{
|
||||
if (SnapGrid != null)
|
||||
if (Owner.Transform.Anchored)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
|
||||
new OccluderDirtyEvent(Owner, _lastPosition, SnapGrid.Offset));
|
||||
new OccluderDirtyEvent(Owner, _lastPosition));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,16 +69,18 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Occluding = OccluderDir.None;
|
||||
|
||||
if (Deleted || SnapGrid == null)
|
||||
if (Deleted || !Owner.Transform.Anchored)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void CheckDir(Direction dir, OccluderDir oclDir)
|
||||
{
|
||||
foreach (var neighbor in SnapGrid.GetInDir(dir))
|
||||
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
|
||||
var position = Owner.Transform.Coordinates;
|
||||
foreach (var neighbor in grid.GetInDir(position, dir))
|
||||
{
|
||||
if (neighbor.TryGetComponent(out ClientOccluderComponent? comp) && comp.Enabled)
|
||||
if (Owner.EntityManager.ComponentManager.TryGetComponent(neighbor, out ClientOccluderComponent? comp) && comp.Enabled)
|
||||
{
|
||||
Occluding |= oclDir;
|
||||
break;
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -14,12 +18,12 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IPointLightComponent))]
|
||||
[NetworkedComponent()]
|
||||
public class PointLightComponent : Component, IPointLightComponent, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
public override string Name => "PointLight";
|
||||
public override uint? NetID => NetIDs.POINT_LIGHT;
|
||||
|
||||
internal bool TreeUpdateQueued { get; set; }
|
||||
|
||||
@@ -43,11 +47,29 @@ namespace Robust.Client.GameObjects
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set => _enabled = value;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PointLightUpdateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool ContainerOccluded { get; set; }
|
||||
public bool ContainerOccluded
|
||||
{
|
||||
get => _containerOccluded;
|
||||
set
|
||||
{
|
||||
if (_containerOccluded == value) return;
|
||||
|
||||
_containerOccluded = value;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PointLightUpdateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
private bool _containerOccluded;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the light mask should automatically rotate with the entity. (like a flashlight)
|
||||
@@ -116,30 +138,13 @@ namespace Robust.Client.GameObjects
|
||||
public bool VisibleNested
|
||||
{
|
||||
get => _visibleNested;
|
||||
set
|
||||
{
|
||||
if (_visibleNested == value) return;
|
||||
_visibleNested = value;
|
||||
if (value)
|
||||
{
|
||||
if (Owner.Transform.Parent == null) return;
|
||||
|
||||
_lightOnParent = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_lightOnParent) return;
|
||||
|
||||
_lightOnParent = false;
|
||||
}
|
||||
}
|
||||
set => _visibleNested = value;
|
||||
}
|
||||
|
||||
[DataField("radius")]
|
||||
private float _radius = 5f;
|
||||
[DataField("nestedvisible")]
|
||||
private bool _visibleNested = true;
|
||||
private bool _lightOnParent;
|
||||
[DataField("color")]
|
||||
private Color _color = Color.White;
|
||||
[DataField("offset")]
|
||||
@@ -166,8 +171,10 @@ namespace Robust.Client.GameObjects
|
||||
get => _radius;
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseTo(value, _radius)) return;
|
||||
|
||||
_radius = MathF.Max(value, 0.01f); // setting radius to 0 causes exceptions, so just use a value close enough to zero that it's unnoticeable.
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedMessage(this));
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedEvent(this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +186,9 @@ namespace Robust.Client.GameObjects
|
||||
Mask = null;
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
internal RenderingTreeComponent? RenderTree { get; set; }
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
if (_maskPath != null)
|
||||
@@ -187,42 +197,13 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdateMask();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
if (message is ParentChangedMessage msg)
|
||||
{
|
||||
HandleTransformParentChanged(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTransformParentChanged(ParentChangedMessage obj)
|
||||
{
|
||||
// TODO: this does not work for things nested multiply layers deep.
|
||||
if (!VisibleNested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj.NewParent != null && obj.NewParent.IsValid())
|
||||
{
|
||||
_lightOnParent = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lightOnParent = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
protected override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
@@ -230,7 +211,7 @@ namespace Robust.Client.GameObjects
|
||||
if (map != MapId.Nullspace)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
|
||||
new RenderTreeRemoveLightMessage(this, map));
|
||||
new RenderTreeRemoveLightEvent(this, map));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,13 +229,18 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public struct PointLightRadiusChangedMessage
|
||||
public class PointLightRadiusChangedEvent : EntityEventArgs
|
||||
{
|
||||
public PointLightComponent PointLightComponent { get; }
|
||||
|
||||
public PointLightRadiusChangedMessage(PointLightComponent pointLightComponent)
|
||||
public PointLightRadiusChangedEvent(PointLightComponent pointLightComponent)
|
||||
{
|
||||
PointLightComponent = pointLightComponent;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class PointLightUpdateEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(SharedSpriteComponent))]
|
||||
[ComponentReference(typeof(ISpriteComponent))]
|
||||
public sealed class SpriteComponent : SharedSpriteComponent, ISpriteComponent,
|
||||
IComponentDebug, ISerializationHooks
|
||||
{
|
||||
@@ -40,7 +42,13 @@ namespace Robust.Client.GameObjects
|
||||
public override bool Visible
|
||||
{
|
||||
get => _visible;
|
||||
set => _visible = value;
|
||||
set
|
||||
{
|
||||
if (_visible == value) return;
|
||||
_visible = value;
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("drawdepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
|
||||
@@ -123,6 +131,9 @@ namespace Robust.Client.GameObjects
|
||||
[DataField("directional")]
|
||||
private bool _directional = true;
|
||||
|
||||
[ViewVariables]
|
||||
internal RenderingTreeComponent? RenderTree { get; set; } = null;
|
||||
|
||||
[DataField("layerDatums")]
|
||||
private List<PrototypeLayerData> LayerDatums
|
||||
{
|
||||
@@ -138,7 +149,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
set
|
||||
{
|
||||
if(value == null) return;
|
||||
if (value == null) return;
|
||||
|
||||
Layers.Clear();
|
||||
foreach (var layerDatum in value)
|
||||
@@ -148,11 +159,12 @@ namespace Robust.Client.GameObjects
|
||||
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
|
||||
{
|
||||
var path = TextureRoot / layerDatum.RsiPath;
|
||||
try
|
||||
|
||||
if (IoCManager.Resolve<IResourceCache>().TryGetResource(path, out RSIResource? resource))
|
||||
{
|
||||
layer.RSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(path).RSI;
|
||||
layer.RSI = resource.RSI;
|
||||
}
|
||||
catch
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
|
||||
}
|
||||
@@ -251,7 +263,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
_layerMapShared = true;
|
||||
UpdateIsInert();
|
||||
QueueUpdateIsInert();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,17 +306,30 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
[DataField("sprite", readOnly: true)] private string? rsi;
|
||||
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new ();
|
||||
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new();
|
||||
|
||||
[DataField("state", readOnly: true)] private string? state;
|
||||
[DataField("texture", readOnly: true)] private string? texture;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool ContainerOccluded { get; set; }
|
||||
public bool ContainerOccluded
|
||||
{
|
||||
get => _containerOccluded;
|
||||
set
|
||||
{
|
||||
if (_containerOccluded == value) return;
|
||||
_containerOccluded = value;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
private bool _containerOccluded;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool TreeUpdateQueued { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] private bool _inertUpdateQueued;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ShaderInstance? PostShader { get; set; }
|
||||
|
||||
@@ -334,13 +359,13 @@ namespace Robust.Client.GameObjects
|
||||
if (!string.IsNullOrWhiteSpace(rsi))
|
||||
{
|
||||
var rsiPath = TextureRoot / rsi;
|
||||
try
|
||||
if(IoCManager.Resolve<IResourceCache>().TryGetResource(rsiPath, out RSIResource? resource))
|
||||
{
|
||||
BaseRSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(rsiPath).RSI;
|
||||
BaseRSI = resource.RSI;
|
||||
}
|
||||
catch (Exception e)
|
||||
else
|
||||
{
|
||||
Logger.ErrorS(SpriteComponent.LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
|
||||
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'.", rsiPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -468,7 +493,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public int AddBlankLayer(int? newIndex = null)
|
||||
{
|
||||
var layer = new Layer(this) {Visible = false};
|
||||
var layer = new Layer(this) { Visible = false };
|
||||
return AddLayer(layer, newIndex);
|
||||
}
|
||||
|
||||
@@ -497,13 +522,13 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public int AddLayer(Texture? texture, int? newIndex = null)
|
||||
{
|
||||
var layer = new Layer(this) {Texture = texture};
|
||||
var layer = new Layer(this) { Texture = texture };
|
||||
return AddLayer(layer, newIndex);
|
||||
}
|
||||
|
||||
public int AddLayer(RSI.StateId stateId, int? newIndex = null)
|
||||
{
|
||||
var layer = new Layer(this) {State = stateId};
|
||||
var layer = new Layer(this) { State = stateId };
|
||||
if (BaseRSI != null && BaseRSI.TryGetState(stateId, out var state))
|
||||
{
|
||||
layer.AnimationTimeLeft = state.GetDelay(0);
|
||||
@@ -549,7 +574,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public int AddLayer(RSI.StateId stateId, RSI? rsi, int? newIndex = null)
|
||||
{
|
||||
var layer = new Layer(this) {State = stateId, RSI = rsi};
|
||||
var layer = new Layer(this) { State = stateId, RSI = rsi };
|
||||
if (rsi != null && rsi.TryGetState(stateId, out var state))
|
||||
{
|
||||
layer.AnimationTimeLeft = state.GetDelay(0);
|
||||
@@ -605,7 +630,7 @@ namespace Robust.Client.GameObjects
|
||||
index = Layers.Count - 1;
|
||||
}
|
||||
|
||||
UpdateIsInert();
|
||||
QueueUpdateIsInert();
|
||||
return index;
|
||||
}
|
||||
|
||||
@@ -632,7 +657,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
UpdateIsInert();
|
||||
QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void RemoveLayer(object layerKey)
|
||||
@@ -1274,8 +1299,8 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var layerColor = color * layer.Color;
|
||||
|
||||
var position = -(Vector2) texture.Size / (2f * EyeManager.PixelsPerMeter) + layer.Offset;
|
||||
var textureSize = texture.Size / (float) EyeManager.PixelsPerMeter;
|
||||
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter) + layer.Offset;
|
||||
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
|
||||
var quad = Box2.FromDimensions(position, textureSize);
|
||||
|
||||
// TODO: Implement layer-specific rotation and scale.
|
||||
@@ -1295,7 +1320,7 @@ namespace Robust.Client.GameObjects
|
||||
public static Angle CalcRectWorldAngle(Angle worldAngle, int numDirections)
|
||||
{
|
||||
var theta = worldAngle.Theta;
|
||||
var segSize = (MathF.PI*2) / (numDirections * 2);
|
||||
var segSize = (MathF.PI * 2) / (numDirections * 2);
|
||||
var segments = (int)(theta / segSize);
|
||||
var odd = segments % 2;
|
||||
var result = theta - (segments * segSize) - (odd * segSize);
|
||||
@@ -1345,18 +1370,6 @@ namespace Robust.Client.GameObjects
|
||||
return texture;
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
var map = Owner.Transform.MapID;
|
||||
if (map != MapId.Nullspace)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
|
||||
new RenderTreeRemoveSpriteMessage(this, map));
|
||||
}
|
||||
}
|
||||
|
||||
public void FrameUpdate(float delta)
|
||||
{
|
||||
foreach (var t in Layers)
|
||||
@@ -1407,7 +1420,7 @@ namespace Robust.Client.GameObjects
|
||||
if (curState == null)
|
||||
return;
|
||||
|
||||
var thestate = (SpriteComponentState) curState;
|
||||
var thestate = (SpriteComponentState)curState;
|
||||
|
||||
Visible = thestate.Visible;
|
||||
DrawDepth = thestate.DrawDepth;
|
||||
@@ -1495,8 +1508,20 @@ namespace Robust.Client.GameObjects
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdateIsInert()
|
||||
private void QueueUpdateIsInert()
|
||||
{
|
||||
if (_inertUpdateQueued)
|
||||
return;
|
||||
|
||||
_inertUpdateQueued = true;
|
||||
// Yes that null check is valid because of that stupid fucking dummy IEntity.
|
||||
// Who thought that was a good idea.
|
||||
Owner?.EntityManager?.EventBus?.RaiseEvent(EventSource.Local, new SpriteUpdateInertEvent {Sprite = this});
|
||||
}
|
||||
|
||||
internal void DoUpdateIsInert()
|
||||
{
|
||||
_inertUpdateQueued = false;
|
||||
IsInert = true;
|
||||
|
||||
foreach (var layer in Layers)
|
||||
@@ -1576,9 +1601,10 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendFormat(
|
||||
"vis/depth/scl/rot/ofs/col/diral/dir: {0}/{1}/{2}/{3}/{4}/{5}/{6}/{7}\n",
|
||||
"vis/depth/scl/rot/ofs/col/norot/override/dir: {0}/{1}/{2}/{3}/{4}/{5}/{6}/{8}/{7}\n",
|
||||
Visible, DrawDepth, Scale, Rotation, Offset,
|
||||
Color, Directional, GetDir(RSI.State.DirectionType.Dir8, Owner.Transform.WorldRotation)
|
||||
Color, NoRotation, GetDir(RSI.State.DirectionType.Dir8, Owner.Transform.WorldRotation),
|
||||
DirectionOverride
|
||||
);
|
||||
|
||||
foreach (var layer in Layers)
|
||||
@@ -1845,14 +1871,14 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
AutoAnimated = value;
|
||||
|
||||
_parent.UpdateIsInert();
|
||||
_parent.QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetVisible(bool value)
|
||||
{
|
||||
Visible = value;
|
||||
|
||||
_parent.UpdateIsInert();
|
||||
_parent.QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetRsi(RSI? rsi)
|
||||
@@ -1884,7 +1910,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
_parent.UpdateIsInert();
|
||||
_parent.QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetState(RSI.StateId stateId)
|
||||
@@ -1916,7 +1942,7 @@ namespace Robust.Client.GameObjects
|
||||
AnimationTime = 0;
|
||||
AnimationTimeLeft = state.GetDelay(0);
|
||||
|
||||
_parent.UpdateIsInert();
|
||||
_parent.QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetTexture(Texture? texture)
|
||||
@@ -1924,7 +1950,7 @@ namespace Robust.Client.GameObjects
|
||||
State = default;
|
||||
Texture = texture;
|
||||
|
||||
_parent.UpdateIsInert();
|
||||
_parent.QueueUpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetOffset(Vector2 offset)
|
||||
@@ -1976,13 +2002,13 @@ namespace Robust.Client.GameObjects
|
||||
switch (layerProp)
|
||||
{
|
||||
case "texture":
|
||||
LayerSetTexture(index, (string) value);
|
||||
LayerSetTexture(index, (string)value);
|
||||
return;
|
||||
case "state":
|
||||
LayerSetState(index, (string) value);
|
||||
LayerSetState(index, (string)value);
|
||||
return;
|
||||
case "color":
|
||||
LayerSetColor(index, (Color) value);
|
||||
LayerSetColor(index, (Color)value);
|
||||
return;
|
||||
default:
|
||||
throw new ArgumentException($"Unknown layer property '{layerProp}'");
|
||||
@@ -2027,7 +2053,7 @@ namespace Robust.Client.GameObjects
|
||||
yield break;
|
||||
}
|
||||
|
||||
var dummy = new DummyIconEntity {Prototype = prototype};
|
||||
var dummy = new DummyIconEntity { Prototype = prototype };
|
||||
var spriteComponent = dummy.AddComponent<SpriteComponent>();
|
||||
|
||||
if (prototype.Components.TryGetValue("Appearance", out _))
|
||||
@@ -2071,7 +2097,7 @@ namespace Robust.Client.GameObjects
|
||||
return GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
var dummy = new DummyIconEntity {Prototype = prototype};
|
||||
var dummy = new DummyIconEntity { Prototype = prototype };
|
||||
var spriteComponent = dummy.AddComponent<SpriteComponent>();
|
||||
dummy.Delete();
|
||||
|
||||
@@ -2108,7 +2134,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
var typeFactory = IoCManager.Resolve<IDynamicTypeFactoryInternal>();
|
||||
var serializationManager = IoCManager.Resolve<ISerializationManager>();
|
||||
var comp = (T) typeFactory.CreateInstanceUnchecked(typeof(T));
|
||||
var comp = (T)typeFactory.CreateInstanceUnchecked(typeof(T));
|
||||
_components[typeof(T)] = comp;
|
||||
comp.Owner = this;
|
||||
|
||||
@@ -2142,7 +2168,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public T GetComponent<T>()
|
||||
{
|
||||
return (T) _components[typeof(T)];
|
||||
return (T)_components[typeof(T)];
|
||||
}
|
||||
|
||||
public IComponent GetComponent(Type type)
|
||||
@@ -2154,7 +2180,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
component = null;
|
||||
if (!_components.TryGetValue(typeof(T), out var value)) return false;
|
||||
component = (T) value;
|
||||
component = (T)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2176,6 +2202,10 @@ namespace Robust.Client.GameObjects
|
||||
return null;
|
||||
}
|
||||
|
||||
public void QueueDelete()
|
||||
{
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
}
|
||||
@@ -2190,10 +2220,12 @@ namespace Robust.Client.GameObjects
|
||||
return Enumerable.Empty<T>();
|
||||
}
|
||||
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public void SendMessage(IComponent? owner, ComponentMessage message)
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public void SendNetworkMessage(IComponent owner, ComponentMessage message, INetChannel? channel = null)
|
||||
{
|
||||
}
|
||||
@@ -2204,4 +2236,14 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal sealed class SpriteUpdateEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal struct SpriteUpdateInertEvent
|
||||
{
|
||||
public SpriteComponent Sprite;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class RenderingTreeComponent : Component
|
||||
{
|
||||
public override string Name => "RenderingTree";
|
||||
|
||||
internal DynamicTree<SpriteComponent> SpriteTree { get; private set; } = new(SpriteAabbFunc);
|
||||
internal DynamicTree<PointLightComponent> LightTree { get; private set; } = new(LightAabbFunc);
|
||||
|
||||
private static Box2 SpriteAabbFunc(in SpriteComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
|
||||
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
|
||||
return new Box2(pos, pos);
|
||||
}
|
||||
|
||||
private static Box2 LightAabbFunc(in PointLightComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
var boxSize = value.Radius * 2;
|
||||
|
||||
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
|
||||
return Box2.CenteredAround(pos, (boxSize, boxSize));
|
||||
}
|
||||
|
||||
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null)
|
||||
{
|
||||
worldPos ??= value.Owner.Transform.WorldPosition;
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
|
||||
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
|
||||
return new Box2(pos, pos);
|
||||
}
|
||||
|
||||
internal static Box2 LightAabbFunc(PointLightComponent value, Vector2? worldPos = null)
|
||||
{
|
||||
worldPos ??= value.Owner.Transform.WorldPosition;
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
var boxSize = value.Radius * 2;
|
||||
|
||||
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
|
||||
return Box2.CenteredAround(pos, (boxSize, boxSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,14 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(SharedUserInterfaceComponent))]
|
||||
public class ClientUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
@@ -23,6 +23,9 @@ namespace Robust.Client.GameObjects
|
||||
[DataField("interfaces", readOnly: true)]
|
||||
private List<PrototypeData> _interfaceData = new();
|
||||
|
||||
[ViewVariables]
|
||||
public IEnumerable<BoundUserInterface> Interfaces => _openInterfaces.Values;
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
_interfaces.Clear();
|
||||
@@ -33,48 +36,40 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel,
|
||||
ICommonSession? session = null)
|
||||
internal void MessageReceived(BoundUIWrapMessage msg)
|
||||
{
|
||||
base.HandleNetworkMessage(message, netChannel, session);
|
||||
|
||||
switch (message)
|
||||
switch (msg.Message)
|
||||
{
|
||||
case BoundInterfaceMessageWrapMessage wrapped:
|
||||
// Double nested switches who needs readability anyways.
|
||||
switch (wrapped.Message)
|
||||
case OpenBoundInterfaceMessage _:
|
||||
if (_openInterfaces.ContainsKey(msg.UiKey))
|
||||
{
|
||||
case OpenBoundInterfaceMessage _:
|
||||
if (_openInterfaces.ContainsKey(wrapped.UiKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
OpenInterface(wrapped);
|
||||
break;
|
||||
OpenInterface(msg);
|
||||
break;
|
||||
|
||||
case CloseBoundInterfaceMessage _:
|
||||
Close(wrapped.UiKey, true);
|
||||
break;
|
||||
case CloseBoundInterfaceMessage _:
|
||||
Close(msg.UiKey, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (_openInterfaces.TryGetValue(wrapped.UiKey, out var bi))
|
||||
{
|
||||
bi.InternalReceiveMessage(wrapped.Message);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (_openInterfaces.TryGetValue(msg.UiKey, out var bi))
|
||||
{
|
||||
bi.InternalReceiveMessage(msg.Message);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenInterface(BoundInterfaceMessageWrapMessage wrapped)
|
||||
private void OpenInterface(BoundUIWrapMessage wrapped)
|
||||
{
|
||||
var data = _interfaces[wrapped.UiKey];
|
||||
// TODO: This type should be cached, but I'm too lazy.
|
||||
var type = _reflectionManager.LooseGetType(data.ClientType);
|
||||
var boundInterface = (BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[]{this, wrapped.UiKey});
|
||||
var boundInterface =
|
||||
(BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[] {this, wrapped.UiKey});
|
||||
boundInterface.Open();
|
||||
_openInterfaces[wrapped.UiKey] = boundInterface;
|
||||
}
|
||||
@@ -86,7 +81,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
if(!remoteCall)
|
||||
if (!remoteCall)
|
||||
SendMessage(new CloseBoundInterfaceMessage(), uiKey);
|
||||
_openInterfaces.Remove(uiKey);
|
||||
boundUserInterface.Dispose();
|
||||
@@ -94,7 +89,8 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
internal void SendMessage(BoundUserInterfaceMessage message, object uiKey)
|
||||
{
|
||||
SendNetworkMessage(new BoundInterfaceMessageWrapMessage(message, uiKey));
|
||||
EntitySystem.Get<UserInterfaceSystem>()
|
||||
.Send(new BoundUIWrapMessage(Owner.Uid, message, uiKey));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Robust.Client.GameObjects
|
||||
DebugTools.AssertNotNull(localPlayer);
|
||||
|
||||
var sequence = IoCManager.Resolve<IClientGameStateManager>().SystemMessageDispatched(msg);
|
||||
entityManager.EntityNetManager.SendSystemNetworkMessage(msg, sequence);
|
||||
entityManager.EntityNetManager?.SendSystemNetworkMessage(msg, sequence);
|
||||
|
||||
var eventArgs = new EntitySessionEventArgs(localPlayer!.Session);
|
||||
|
||||
|
||||
@@ -1,51 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class AppearanceSystem : EntitySystem
|
||||
{
|
||||
private readonly Queue<AppearanceComponent> _updatesQueued = new();
|
||||
private readonly Queue<AppearanceComponent> _queuedUpdates = new();
|
||||
|
||||
public void EnqueueUpdate(AppearanceComponent component)
|
||||
{
|
||||
_queuedUpdates.Enqueue(component);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
while (_updatesQueued.TryDequeue(out var appearance))
|
||||
while (_queuedUpdates.TryDequeue(out var appearance))
|
||||
{
|
||||
UpdateComponent(appearance);
|
||||
if (appearance.Deleted)
|
||||
return;
|
||||
|
||||
foreach (var visualizer in appearance.Visualizers)
|
||||
{
|
||||
visualizer.OnChangeData(appearance);
|
||||
}
|
||||
|
||||
appearance.UnmarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateComponent(AppearanceComponent component)
|
||||
{
|
||||
if (component.Deleted)
|
||||
return;
|
||||
|
||||
foreach (var visualizer in component.Visualizers)
|
||||
{
|
||||
switch (visualizer)
|
||||
{
|
||||
case AppearanceComponent.SpriteLayerToggle spriteLayerToggle:
|
||||
UpdateSpriteLayerToggle(component, spriteLayerToggle);
|
||||
break;
|
||||
|
||||
default:
|
||||
visualizer.OnChangeData(component);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateSpriteLayerToggle(AppearanceComponent component, AppearanceComponent.SpriteLayerToggle toggle)
|
||||
{
|
||||
component.TryGetData(toggle.Key, out bool visible);
|
||||
var sprite = component.Owner.GetComponent<SpriteComponent>();
|
||||
sprite.LayerSetVisible(toggle.SpriteLayer, visible);
|
||||
}
|
||||
|
||||
public void EnqueueAppearanceUpdate(AppearanceComponent component)
|
||||
{
|
||||
_updatesQueued.Enqueue(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,17 +41,6 @@ namespace Robust.Client.GameObjects
|
||||
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
UnsubscribeNetworkEvent<PlayAudioEntityMessage>();
|
||||
UnsubscribeNetworkEvent<PlayAudioGlobalMessage>();
|
||||
UnsubscribeNetworkEvent<PlayAudioPositionalMessage>();
|
||||
UnsubscribeNetworkEvent<StopAudioMessageClient>();
|
||||
|
||||
UnsubscribeLocalEvent<SoundSystem.QueryAudioSystem>();
|
||||
}
|
||||
|
||||
private void StopAudioMessageHandler(StopAudioMessageClient ev)
|
||||
{
|
||||
var stream = _playingClydeStreams.Find(p => p.NetIdentifier == ev.Identifier);
|
||||
@@ -265,7 +254,7 @@ namespace Robust.Client.GameObjects
|
||||
if (!source.SetPosition(entity.Transform.WorldPosition))
|
||||
{
|
||||
source.Dispose();
|
||||
Logger.Warning("Can't play positional audio, can't set position.");
|
||||
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -312,7 +301,7 @@ namespace Robust.Client.GameObjects
|
||||
if (!source.SetPosition(coordinates.ToMapPos(EntityManager)))
|
||||
{
|
||||
source.Dispose();
|
||||
Logger.Warning("Can't play positional audio, can't set position.");
|
||||
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -20,10 +21,11 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private readonly Queue<IEntity> _dirtyEntities = new();
|
||||
private readonly Queue<EntityUid> _dirtyEntities = new();
|
||||
|
||||
private uint _updateGeneration;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -32,6 +34,8 @@ namespace Robust.Client.GameObjects
|
||||
UpdatesAfter.Add(typeof(PhysicsSystem));
|
||||
|
||||
SubscribeLocalEvent<OccluderDirtyEvent>(HandleDirtyEvent);
|
||||
|
||||
SubscribeLocalEvent<ClientOccluderComponent, AnchorStateChangedEvent>(HandleAnchorChanged);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
@@ -47,8 +51,8 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
while (_dirtyEntities.TryDequeue(out var entity))
|
||||
{
|
||||
if (!entity.Deleted
|
||||
&& entity.TryGetComponent(out ClientOccluderComponent? occluder)
|
||||
if (EntityManager.EntityExists(entity)
|
||||
&& ComponentManager.TryGetComponent(entity, out ClientOccluderComponent? occluder)
|
||||
&& occluder.UpdateGeneration != _updateGeneration)
|
||||
{
|
||||
occluder.Update();
|
||||
@@ -58,6 +62,11 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleAnchorChanged(EntityUid uid, ClientOccluderComponent component, AnchorStateChangedEvent args)
|
||||
{
|
||||
component.AnchorStateChanged();
|
||||
}
|
||||
|
||||
private void HandleDirtyEvent(OccluderDirtyEvent ev)
|
||||
{
|
||||
var sender = ev.Sender;
|
||||
@@ -65,13 +74,14 @@ namespace Robust.Client.GameObjects
|
||||
sender.TryGetComponent(out ClientOccluderComponent? iconSmooth)
|
||||
&& iconSmooth.Running)
|
||||
{
|
||||
var snapGrid = sender.GetComponent<SnapGridComponent>();
|
||||
var grid1 = _mapManager.GetGrid(sender.Transform.GridID);
|
||||
var coords = sender.Transform.Coordinates;
|
||||
|
||||
_dirtyEntities.Enqueue(sender);
|
||||
AddValidEntities(snapGrid.GetInDir(Direction.North));
|
||||
AddValidEntities(snapGrid.GetInDir(Direction.South));
|
||||
AddValidEntities(snapGrid.GetInDir(Direction.East));
|
||||
AddValidEntities(snapGrid.GetInDir(Direction.West));
|
||||
_dirtyEntities.Enqueue(sender.Uid);
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.North));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.South));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.East));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.West));
|
||||
}
|
||||
|
||||
// Entity is no longer valid, update around the last position it was at.
|
||||
@@ -79,28 +89,23 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
var pos = ev.LastPosition.Value.pos;
|
||||
|
||||
AddValidEntities(grid.GetSnapGridCell(pos + new Vector2i(1, 0), ev.Offset));
|
||||
AddValidEntities(grid.GetSnapGridCell(pos + new Vector2i(-1, 0), ev.Offset));
|
||||
AddValidEntities(grid.GetSnapGridCell(pos + new Vector2i(0, 1), ev.Offset));
|
||||
AddValidEntities(grid.GetSnapGridCell(pos + new Vector2i(0, -1), ev.Offset));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(1, 0)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(-1, 0)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(0, 1)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(0, -1)));
|
||||
}
|
||||
}
|
||||
|
||||
private void AddValidEntities(IEnumerable<IEntity> candidates)
|
||||
private void AddValidEntities(IEnumerable<EntityUid> candidates)
|
||||
{
|
||||
foreach (var entity in candidates)
|
||||
{
|
||||
if (entity.HasComponent<ClientOccluderComponent>())
|
||||
if (ComponentManager.HasComponent<ClientOccluderComponent>(entity))
|
||||
{
|
||||
_dirtyEntities.Enqueue(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddValidEntities(IEnumerable<IComponent> candidates)
|
||||
{
|
||||
AddValidEntities(candidates.Select(c => c.Owner));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -108,15 +113,13 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
internal sealed class OccluderDirtyEvent : EntityEventArgs
|
||||
{
|
||||
public OccluderDirtyEvent(IEntity sender, (GridId grid, Vector2i pos)? lastPosition, SnapGridOffset offset)
|
||||
public OccluderDirtyEvent(IEntity sender, (GridId grid, Vector2i pos)? lastPosition)
|
||||
{
|
||||
LastPosition = lastPosition;
|
||||
Offset = offset;
|
||||
Sender = sender;
|
||||
}
|
||||
|
||||
public (GridId grid, Vector2i pos)? LastPosition { get; }
|
||||
public SnapGridOffset Offset { get; }
|
||||
public IEntity Sender { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
#if DEBUG
|
||||
using System;
|
||||
using System.Text;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
internal sealed class DebugGridTileLookupSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
_enabled = value;
|
||||
|
||||
if (_enabled)
|
||||
{
|
||||
_label.Visible = true;
|
||||
LastTile = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
_label.Text = null;
|
||||
_label.Visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
|
||||
private (GridId Grid, Vector2i Indices) LastTile;
|
||||
|
||||
// Label and shit that follows cursor
|
||||
private Label _label = new()
|
||||
{
|
||||
Visible = false,
|
||||
};
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<SendGridTileLookupMessage>(HandleSentEntities);
|
||||
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_label);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
UnsubscribeNetworkEvent<SendGridTileLookupMessage>();
|
||||
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.RemoveChild(_label);
|
||||
}
|
||||
|
||||
private void RequestEntities(GridId gridId, Vector2i indices)
|
||||
{
|
||||
if (gridId == GridId.Invalid) return;
|
||||
RaiseNetworkEvent(new RequestGridTileLookupMessage(gridId, indices));
|
||||
}
|
||||
|
||||
private void HandleSentEntities(SendGridTileLookupMessage message)
|
||||
{
|
||||
if (!Enabled) return;
|
||||
var text = new StringBuilder();
|
||||
text.AppendLine($"GridId: {LastTile.Grid}, Tile: {LastTile.Indices}");
|
||||
|
||||
for (var i = 0; i < message.Entities.Count; i++)
|
||||
{
|
||||
var uid = message.Entities[i];
|
||||
|
||||
if (!EntityManager.TryGetEntity(uid, out var entity)) continue;
|
||||
|
||||
text.AppendLine(entity.ToString());
|
||||
}
|
||||
|
||||
_label.Text = text.ToString();
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
if (!Enabled) return;
|
||||
|
||||
var mousePos = _inputManager.MouseScreenPosition;
|
||||
var worldPos = _eyeManager.ScreenToMap(mousePos);
|
||||
|
||||
GridId gridId;
|
||||
Vector2i tile;
|
||||
|
||||
if (_mapManager.TryFindGridAt(worldPos, out var grid))
|
||||
{
|
||||
gridId = grid.Index;
|
||||
tile = grid.WorldToTile(worldPos.Position);
|
||||
}
|
||||
else
|
||||
{
|
||||
gridId = GridId.Invalid;
|
||||
tile = new Vector2i((int) MathF.Floor(worldPos.Position.X), (int) MathF.Floor(worldPos.Position.Y));
|
||||
}
|
||||
|
||||
LayoutContainer.SetPosition(_label, mousePos.Position);
|
||||
|
||||
if ((gridId, tile).Equals(LastTile)) return;
|
||||
|
||||
_label.Text = null;
|
||||
LastTile = (gridId, tile);
|
||||
RequestEntities(gridId, tile);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RequestTileEntities : IConsoleCommand
|
||||
{
|
||||
public string Command => "tilelookup";
|
||||
public string Description => "Used for debugging GridTileLookupSystem";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<DebugGridTileLookupSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,84 @@
|
||||
#if DEBUG
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
internal sealed class DebugLightTreeSystem : EntitySystem
|
||||
{
|
||||
private DebugLightOverlay? _lightOverlay;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
|
||||
if (_enabled)
|
||||
{
|
||||
_lightOverlay = new DebugLightOverlay(
|
||||
IoCManager.Resolve<IEntityLookup>(),
|
||||
IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IMapManager>(),
|
||||
Get<RenderingTreeSystem>());
|
||||
|
||||
overlayManager.AddOverlay(_lightOverlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
overlayManager.RemoveOverlay(_lightOverlay!);
|
||||
_lightOverlay = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
|
||||
private sealed class DebugLightOverlay : Overlay
|
||||
{
|
||||
private IEntityLookup _lookup;
|
||||
private IEyeManager _eyeManager;
|
||||
private IMapManager _mapManager;
|
||||
|
||||
private RenderingTreeSystem _tree;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public DebugLightOverlay(IEntityLookup lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
|
||||
{
|
||||
_lookup = lookup;
|
||||
_eyeManager = eyeManager;
|
||||
_mapManager = mapManager;
|
||||
_tree = tree;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var map = _eyeManager.CurrentMap;
|
||||
if (map == MapId.Nullspace) return;
|
||||
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
foreach (var tree in _tree.GetLightTrees(map, viewport))
|
||||
{
|
||||
foreach (var light in tree)
|
||||
{
|
||||
var aabb = _lookup.GetWorldAabbFromEntity(light.Owner);
|
||||
if (!aabb.Intersects(viewport)) continue;
|
||||
|
||||
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.1f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -67,9 +67,10 @@ namespace Robust.Client.GameObjects
|
||||
//Create effect from creation message
|
||||
var effect = new Effect(message, resourceCache, _mapManager, _entityManager);
|
||||
effect.Deathtime = gameTiming.CurTime + message.LifeTime;
|
||||
if (effect.AttachedEntityUid != null)
|
||||
if (effect.AttachedEntityUid != null
|
||||
&& _entityManager.TryGetEntity(effect.AttachedEntityUid.Value, out var attachedEntity))
|
||||
{
|
||||
effect.AttachedEntity = _entityManager.GetEntity(effect.AttachedEntityUid.Value);
|
||||
effect.AttachedEntity = attachedEntity;
|
||||
}
|
||||
|
||||
_Effects.Add(effect);
|
||||
@@ -346,11 +347,11 @@ namespace Robust.Client.GameObjects
|
||||
_entityManager = entityManager;
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var map = _owner.eyeManager.CurrentMap;
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) handle;
|
||||
var worldHandle = args.WorldHandle;
|
||||
ShaderInstance? currentShader = null;
|
||||
var player = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Robust.Client.GameObjects
|
||||
private void DispatchInputCommand(FullInputCmdMessage message)
|
||||
{
|
||||
_stateManager.InputCommandDispatched(message);
|
||||
EntityNetworkManager.SendSystemNetworkMessage(message, message.InputSequence);
|
||||
EntityManager.EntityNetManager?.SendSystemNetworkMessage(message, message.InputSequence);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -173,4 +173,24 @@ namespace Robust.Client.GameObjects
|
||||
AttachedEntity = attachedEntity;
|
||||
}
|
||||
}
|
||||
|
||||
public class PlayerAttachedEvent : EntityEventArgs
|
||||
{
|
||||
public PlayerAttachedEvent(IEntity entity)
|
||||
{
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
public IEntity Entity { get; }
|
||||
}
|
||||
|
||||
public class PlayerDetachedEvent : EntityEventArgs
|
||||
{
|
||||
public PlayerDetachedEvent(IEntity entity)
|
||||
{
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
public IEntity Entity { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Physics;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -15,23 +21,48 @@ namespace Robust.Client.GameObjects
|
||||
[UsedImplicitly]
|
||||
public sealed class RenderingTreeSystem : EntitySystem
|
||||
{
|
||||
internal const string LoggerSawmill = "rendertree";
|
||||
|
||||
// Nullspace is not indexed. Keep that in mind.
|
||||
|
||||
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
|
||||
|
||||
private readonly Dictionary<MapId, Dictionary<GridId, MapTrees>> _gridTrees = new();
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private readonly List<SpriteComponent> _spriteQueue = new();
|
||||
private readonly List<PointLightComponent> _lightQueue = new();
|
||||
|
||||
internal DynamicTree<SpriteComponent> GetSpriteTreeForMap(MapId map, GridId grid)
|
||||
private HashSet<EntityUid> _checkedChildren = new();
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="CVars.MaxLightRadius"/>
|
||||
/// </summary>
|
||||
public float MaxLightRadius { get; private set; }
|
||||
|
||||
internal IEnumerable<RenderingTreeComponent> GetRenderTrees(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
return _gridTrees[map][grid].SpriteTree;
|
||||
if (mapId == MapId.Nullspace) yield break;
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||
{
|
||||
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<RenderingTreeComponent>();
|
||||
}
|
||||
|
||||
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
|
||||
}
|
||||
|
||||
internal DynamicTree<PointLightComponent> GetLightTreeForMap(MapId map, GridId grid)
|
||||
internal IEnumerable<DynamicTree<SpriteComponent>> GetSpriteTrees(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
return _gridTrees[map][grid].LightTree;
|
||||
foreach (var comp in GetRenderTrees(mapId, worldAABB))
|
||||
{
|
||||
yield return comp.SpriteTree;
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable<DynamicTree<PointLightComponent>> GetLightTrees(MapId mapId, Box2 worldAABB)
|
||||
{
|
||||
foreach (var comp in GetRenderTrees(mapId, worldAABB))
|
||||
{
|
||||
yield return comp.LightTree;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -43,169 +74,164 @@ namespace Robust.Client.GameObjects
|
||||
UpdatesAfter.Add(typeof(PhysicsSystem));
|
||||
|
||||
_mapManager.MapCreated += MapManagerOnMapCreated;
|
||||
_mapManager.MapDestroyed += MapManagerOnMapDestroyed;
|
||||
_mapManager.OnGridCreated += MapManagerOnGridCreated;
|
||||
_mapManager.OnGridRemoved += MapManagerOnGridRemoved;
|
||||
|
||||
SubscribeLocalEvent<EntMapIdChangedMessage>(EntMapIdChanged);
|
||||
SubscribeLocalEvent<MoveEvent>(EntMoved);
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(EntParentChanged);
|
||||
SubscribeLocalEvent<PointLightRadiusChangedMessage>(PointLightRadiusChanged);
|
||||
SubscribeLocalEvent<RenderTreeRemoveSpriteMessage>(RemoveSprite);
|
||||
SubscribeLocalEvent<RenderTreeRemoveLightMessage>(RemoveLight);
|
||||
// Due to how recursion works, this must be done.
|
||||
SubscribeLocalEvent<MoveEvent>(AnythingMoved);
|
||||
|
||||
SubscribeLocalEvent<SpriteComponent, EntMapIdChangedMessage>(SpriteMapChanged);
|
||||
SubscribeLocalEvent<SpriteComponent, EntParentChangedMessage>(SpriteParentChanged);
|
||||
SubscribeLocalEvent<SpriteComponent, ComponentRemove>(RemoveSprite);
|
||||
SubscribeLocalEvent<SpriteComponent, SpriteUpdateEvent>(HandleSpriteUpdate);
|
||||
|
||||
SubscribeLocalEvent<PointLightComponent, EntMapIdChangedMessage>(LightMapChanged);
|
||||
SubscribeLocalEvent<PointLightComponent, EntParentChangedMessage>(LightParentChanged);
|
||||
SubscribeLocalEvent<PointLightComponent, PointLightRadiusChangedEvent>(PointLightRadiusChanged);
|
||||
SubscribeLocalEvent<PointLightComponent, RenderTreeRemoveLightEvent>(RemoveLight);
|
||||
SubscribeLocalEvent<PointLightComponent, PointLightUpdateEvent>(HandleLightUpdate);
|
||||
|
||||
SubscribeLocalEvent<RenderingTreeComponent, ComponentRemove>(HandleTreeRemove);
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.MaxLightRadius, value => MaxLightRadius = value, true);
|
||||
}
|
||||
|
||||
private void HandleLightUpdate(EntityUid uid, PointLightComponent component, PointLightUpdateEvent args)
|
||||
{
|
||||
if (component.TreeUpdateQueued) return;
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void HandleSpriteUpdate(EntityUid uid, SpriteComponent component, SpriteUpdateEvent args)
|
||||
{
|
||||
if (component.TreeUpdateQueued) return;
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void AnythingMoved(MoveEvent args)
|
||||
{
|
||||
AnythingMovedSubHandler(args.Sender.Transform);
|
||||
}
|
||||
|
||||
private void AnythingMovedSubHandler(ITransformComponent sender)
|
||||
{
|
||||
// To avoid doing redundant updates (and we don't need to update a grid's children ever)
|
||||
if (!_checkedChildren.Add(sender.Owner.Uid) ||
|
||||
sender.Owner.HasComponent<RenderingTreeComponent>()) return;
|
||||
|
||||
// This recursive search is needed, as MoveEvent is defined to not care about indirect events like children.
|
||||
// WHATEVER YOU DO, DON'T REPLACE THIS WITH SPAMMING EVENTS UNLESS YOU HAVE A GUARANTEE IT WON'T LAG THE GC.
|
||||
// (Struct-based events ok though)
|
||||
// Ironically this was lagging the GC lolz
|
||||
if (sender.Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||
QueueSpriteUpdate(sprite);
|
||||
|
||||
if (sender.Owner.TryGetComponent(out PointLightComponent? light))
|
||||
QueueLightUpdate(light);
|
||||
|
||||
foreach (ITransformComponent child in sender.Children)
|
||||
{
|
||||
AnythingMovedSubHandler(child);
|
||||
}
|
||||
}
|
||||
|
||||
// For the RemoveX methods
|
||||
// If the Transform is removed BEFORE the Sprite/Light,
|
||||
// then the MapIdChanged code will handle and remove it (because MapId gets set to nullspace).
|
||||
// Otherwise these will still have their past MapId and that's all we need..
|
||||
|
||||
#region SpriteHandlers
|
||||
private void SpriteMapChanged(EntityUid uid, SpriteComponent component, EntMapIdChangedMessage args)
|
||||
{
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void SpriteParentChanged(EntityUid uid, SpriteComponent component, EntParentChangedMessage args)
|
||||
{
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void RemoveSprite(EntityUid uid, SpriteComponent component, ComponentRemove args)
|
||||
{
|
||||
ClearSprite(component);
|
||||
}
|
||||
|
||||
private void ClearSprite(SpriteComponent component)
|
||||
{
|
||||
if (component.RenderTree == null) return;
|
||||
|
||||
component.RenderTree.SpriteTree.Remove(component);
|
||||
component.RenderTree = null;
|
||||
}
|
||||
|
||||
private void QueueSpriteUpdate(SpriteComponent component)
|
||||
{
|
||||
if (component.TreeUpdateQueued) return;
|
||||
|
||||
component.TreeUpdateQueued = true;
|
||||
_spriteQueue.Add(component);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region LightHandlers
|
||||
private void LightMapChanged(EntityUid uid, PointLightComponent component, EntMapIdChangedMessage args)
|
||||
{
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void LightParentChanged(EntityUid uid, PointLightComponent component, EntParentChangedMessage args)
|
||||
{
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void PointLightRadiusChanged(EntityUid uid, PointLightComponent component, PointLightRadiusChangedEvent args)
|
||||
{
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void RemoveLight(EntityUid uid, PointLightComponent component, RenderTreeRemoveLightEvent args)
|
||||
{
|
||||
ClearLight(component);
|
||||
}
|
||||
|
||||
private void ClearLight(PointLightComponent component)
|
||||
{
|
||||
if (component.RenderTree == null) return;
|
||||
|
||||
component.RenderTree.LightTree.Remove(component);
|
||||
component.RenderTree = null;
|
||||
}
|
||||
|
||||
private void QueueLightUpdate(PointLightComponent component)
|
||||
{
|
||||
if (component.TreeUpdateQueued) return;
|
||||
|
||||
component.TreeUpdateQueued = true;
|
||||
_lightQueue.Add(component);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_mapManager.MapCreated -= MapManagerOnMapCreated;
|
||||
_mapManager.MapDestroyed -= MapManagerOnMapDestroyed;
|
||||
_mapManager.OnGridCreated -= MapManagerOnGridCreated;
|
||||
_mapManager.OnGridRemoved -= MapManagerOnGridRemoved;
|
||||
|
||||
UnsubscribeLocalEvent<EntMapIdChangedMessage>();
|
||||
UnsubscribeLocalEvent<MoveEvent>();
|
||||
UnsubscribeLocalEvent<EntParentChangedMessage>();
|
||||
UnsubscribeLocalEvent<PointLightRadiusChangedMessage>();
|
||||
UnsubscribeLocalEvent<RenderTreeRemoveSpriteMessage>();
|
||||
UnsubscribeLocalEvent<RenderTreeRemoveLightMessage>();
|
||||
}
|
||||
|
||||
// For these next 2 methods (the Remove* ones):
|
||||
// If the Transform is removed BEFORE the Sprite/Light,
|
||||
// then the MapIdChanged code will handle and remove it (because MapId gets set to nullspace).
|
||||
// Otherwise these will still have their past MapId and that's all we need..
|
||||
private void RemoveLight(RenderTreeRemoveLightMessage ev)
|
||||
private void HandleTreeRemove(EntityUid uid, RenderingTreeComponent component, ComponentRemove args)
|
||||
{
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Map, MapTrees.LightAabbFunc(ev.Light), true))
|
||||
foreach (var sprite in component.SpriteTree)
|
||||
{
|
||||
_gridTrees[ev.Map][gridId].LightTree.Remove(ev.Light);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveSprite(RenderTreeRemoveSpriteMessage ev)
|
||||
{
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Map, MapTrees.SpriteAabbFunc(ev.Sprite), true))
|
||||
{
|
||||
_gridTrees[ev.Map][gridId].SpriteTree.Remove(ev.Sprite);
|
||||
}
|
||||
}
|
||||
|
||||
private void PointLightRadiusChanged(PointLightRadiusChangedMessage ev)
|
||||
{
|
||||
QueueUpdateLight(ev.PointLightComponent);
|
||||
}
|
||||
|
||||
private void EntParentChanged(EntParentChangedMessage ev)
|
||||
{
|
||||
UpdateEntity(ev.Entity);
|
||||
}
|
||||
|
||||
private void EntMoved(MoveEvent ev)
|
||||
{
|
||||
UpdateEntity(ev.Sender);
|
||||
}
|
||||
|
||||
private void UpdateEntity(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out SpriteComponent? spriteComponent))
|
||||
{
|
||||
if (!spriteComponent.TreeUpdateQueued)
|
||||
{
|
||||
spriteComponent.TreeUpdateQueued = true;
|
||||
|
||||
_spriteQueue.Add(spriteComponent);
|
||||
}
|
||||
sprite.RenderTree = null;
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out PointLightComponent? light))
|
||||
foreach (var light in component.LightTree)
|
||||
{
|
||||
QueueUpdateLight(light);
|
||||
light.RenderTree = null;
|
||||
}
|
||||
|
||||
foreach (var child in entity.Transform.ChildEntityUids)
|
||||
{
|
||||
UpdateEntity(EntityManager.GetEntity(child));
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueUpdateLight(PointLightComponent light)
|
||||
{
|
||||
if (!light.TreeUpdateQueued)
|
||||
{
|
||||
light.TreeUpdateQueued = true;
|
||||
|
||||
_lightQueue.Add(light);
|
||||
}
|
||||
}
|
||||
|
||||
private void EntMapIdChanged(EntMapIdChangedMessage ev)
|
||||
{
|
||||
// Nullspace is a valid map ID for stuff to have but we also aren't gonna bother indexing it.
|
||||
// So that's why there's a GetValueOrDefault.
|
||||
var oldMapTrees = _gridTrees.GetValueOrDefault(ev.OldMapId);
|
||||
|
||||
// TODO: MMMM probably a better way to do this.
|
||||
if (ev.Entity.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
if (oldMapTrees != null)
|
||||
{
|
||||
foreach (var (_, gridTree) in oldMapTrees)
|
||||
{
|
||||
gridTree.SpriteTree.Remove(sprite);
|
||||
}
|
||||
}
|
||||
|
||||
var bounds = MapTrees.SpriteAabbFunc(sprite);
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Entity.Transform.MapID, bounds, true))
|
||||
{
|
||||
Box2 gridBounds;
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
gridBounds = bounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
gridBounds = bounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
|
||||
}
|
||||
_gridTrees[ev.Entity.Transform.MapID][gridId].SpriteTree.AddOrUpdate(sprite, gridBounds);
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.Entity.TryGetComponent(out PointLightComponent? light))
|
||||
{
|
||||
if (oldMapTrees != null)
|
||||
{
|
||||
foreach (var (_, gridTree) in oldMapTrees)
|
||||
{
|
||||
gridTree.LightTree.Remove(light);
|
||||
}
|
||||
}
|
||||
|
||||
var bounds = MapTrees.LightAabbFunc(light);
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Entity.Transform.MapID, bounds, true))
|
||||
{
|
||||
Box2 gridBounds;
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
gridBounds = bounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
gridBounds = bounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
|
||||
}
|
||||
_gridTrees[ev.Entity.Transform.MapID][gridId].LightTree.AddOrUpdate(light, gridBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MapManagerOnMapDestroyed(object? sender, MapEventArgs e)
|
||||
{
|
||||
_gridTrees.Remove(e.Map);
|
||||
component.SpriteTree.Clear();
|
||||
component.LightTree.Clear();
|
||||
}
|
||||
|
||||
private void MapManagerOnMapCreated(object? sender, MapEventArgs e)
|
||||
@@ -215,109 +241,125 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
_gridTrees.Add(e.Map, new Dictionary<GridId, MapTrees>
|
||||
{
|
||||
{GridId.Invalid, new MapTrees()}
|
||||
});
|
||||
_mapManager.GetMapEntity(e.Map).EnsureComponent<RenderingTreeComponent>();
|
||||
}
|
||||
|
||||
private void MapManagerOnGridCreated(MapId mapId, GridId gridId)
|
||||
{
|
||||
_gridTrees[mapId].Add(gridId, new MapTrees());
|
||||
EntityManager.GetEntity(_mapManager.GetGrid(gridId).GridEntityId).EnsureComponent<RenderingTreeComponent>();
|
||||
}
|
||||
|
||||
private void MapManagerOnGridRemoved(MapId mapId, GridId gridId)
|
||||
internal static RenderingTreeComponent? GetRenderTree(IEntity entity)
|
||||
{
|
||||
_gridTrees[mapId].Remove(gridId);
|
||||
if (entity.Transform.MapID == MapId.Nullspace ||
|
||||
entity.HasComponent<RenderingTreeComponent>()) return null;
|
||||
|
||||
var parent = entity.Transform.Parent?.Owner;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (parent == null) break;
|
||||
|
||||
if (parent.TryGetComponent(out RenderingTreeComponent? comp)) return comp;
|
||||
parent = parent.Transform.Parent?.Owner;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
foreach (var queuedUpdateSprite in _spriteQueue)
|
||||
_checkedChildren.Clear();
|
||||
|
||||
foreach (var sprite in _spriteQueue)
|
||||
{
|
||||
var map = queuedUpdateSprite.Owner.Transform.MapID;
|
||||
if (map == MapId.Nullspace)
|
||||
sprite.TreeUpdateQueued = false;
|
||||
if (!sprite.Visible || sprite.ContainerOccluded)
|
||||
{
|
||||
ClearSprite(sprite);
|
||||
continue;
|
||||
}
|
||||
|
||||
var mapTree = _gridTrees[map];
|
||||
var oldMapTree = sprite.RenderTree;
|
||||
var newMapTree = GetRenderTree(sprite.Owner);
|
||||
// TODO: Temp PVS guard
|
||||
var worldPos = sprite.Owner.Transform.WorldPosition;
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map,
|
||||
MapTrees.SpriteAabbFunc(queuedUpdateSprite), true))
|
||||
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
|
||||
{
|
||||
mapTree[gridId].SpriteTree.AddOrUpdate(queuedUpdateSprite);
|
||||
ClearSprite(sprite);
|
||||
continue;
|
||||
}
|
||||
|
||||
queuedUpdateSprite.TreeUpdateQueued = false;
|
||||
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos);
|
||||
|
||||
// If we're on a new map then clear the old one.
|
||||
if (oldMapTree != newMapTree)
|
||||
{
|
||||
ClearSprite(sprite);
|
||||
newMapTree?.SpriteTree.Add(sprite, aabb);
|
||||
}
|
||||
else
|
||||
{
|
||||
newMapTree?.SpriteTree.Update(sprite, aabb);
|
||||
}
|
||||
|
||||
sprite.RenderTree = newMapTree;
|
||||
}
|
||||
|
||||
foreach (var queuedUpdateLight in _lightQueue)
|
||||
foreach (var light in _lightQueue)
|
||||
{
|
||||
var map = queuedUpdateLight.Owner.Transform.MapID;
|
||||
if (map == MapId.Nullspace)
|
||||
light.TreeUpdateQueued = false;
|
||||
|
||||
if (!light.Enabled || light.ContainerOccluded)
|
||||
{
|
||||
ClearLight(light);
|
||||
continue;
|
||||
}
|
||||
|
||||
var mapTree = _gridTrees[map];
|
||||
var oldMapTree = light.RenderTree;
|
||||
var newMapTree = GetRenderTree(light.Owner);
|
||||
// TODO: Temp PVS guard
|
||||
var worldPos = light.Owner.Transform.WorldPosition;
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map,
|
||||
MapTrees.LightAabbFunc(queuedUpdateLight), true))
|
||||
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
|
||||
{
|
||||
mapTree[gridId].LightTree.AddOrUpdate(queuedUpdateLight);
|
||||
ClearLight(light);
|
||||
continue;
|
||||
}
|
||||
|
||||
queuedUpdateLight.TreeUpdateQueued = false;
|
||||
// TODO: Events need a bit of cleanup so we only validate this on initialize and radius changed events
|
||||
// this is fine for now IMO as it's 1 float check for every light that moves
|
||||
if (light.Radius > MaxLightRadius)
|
||||
{
|
||||
Logger.WarningS(LoggerSawmill, $"Light radius for {light.Owner} set above max radius of {MaxLightRadius}. This may lead to pop-in.");
|
||||
}
|
||||
|
||||
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
|
||||
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos);
|
||||
|
||||
// If we're on a new map then clear the old one.
|
||||
if (oldMapTree != newMapTree)
|
||||
{
|
||||
ClearLight(light);
|
||||
newMapTree?.LightTree.Add(light, aabb);
|
||||
}
|
||||
else
|
||||
{
|
||||
newMapTree?.LightTree.Update(light, aabb);
|
||||
}
|
||||
|
||||
light.RenderTree = newMapTree;
|
||||
}
|
||||
|
||||
_spriteQueue.Clear();
|
||||
_lightQueue.Clear();
|
||||
}
|
||||
|
||||
private sealed class MapTrees
|
||||
{
|
||||
public readonly DynamicTree<SpriteComponent> SpriteTree;
|
||||
public readonly DynamicTree<PointLightComponent> LightTree;
|
||||
|
||||
public MapTrees()
|
||||
{
|
||||
SpriteTree = new DynamicTree<SpriteComponent>(SpriteAabbFunc);
|
||||
LightTree = new DynamicTree<PointLightComponent>(LightAabbFunc);
|
||||
}
|
||||
|
||||
internal static Box2 SpriteAabbFunc(in SpriteComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
|
||||
return new Box2(worldPos, worldPos);
|
||||
}
|
||||
|
||||
internal static Box2 LightAabbFunc(in PointLightComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
|
||||
var boxSize = value.Radius * 2;
|
||||
return Box2.CenteredAround(worldPos, (boxSize, boxSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal struct RenderTreeRemoveSpriteMessage
|
||||
internal class RenderTreeRemoveLightEvent : EntityEventArgs
|
||||
{
|
||||
public RenderTreeRemoveSpriteMessage(SpriteComponent sprite, MapId map)
|
||||
{
|
||||
Sprite = sprite;
|
||||
Map = map;
|
||||
}
|
||||
|
||||
public SpriteComponent Sprite { get; }
|
||||
public MapId Map { get; }
|
||||
}
|
||||
|
||||
internal struct RenderTreeRemoveLightMessage
|
||||
{
|
||||
public RenderTreeRemoveLightMessage(PointLightComponent light, MapId map)
|
||||
public RenderTreeRemoveLightEvent(PointLightComponent light, MapId map)
|
||||
{
|
||||
Light = light;
|
||||
Map = map;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using JetBrains.Annotations;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -16,10 +16,29 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private RenderingTreeSystem _treeSystem = default!;
|
||||
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_treeSystem = Get<RenderingTreeSystem>();
|
||||
SubscribeLocalEvent<SpriteUpdateInertEvent>(QueueUpdateInert);
|
||||
}
|
||||
|
||||
private void QueueUpdateInert(SpriteUpdateInertEvent ev)
|
||||
{
|
||||
_inertUpdateQueue.Enqueue(ev.Sprite);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
var renderTreeSystem = EntitySystemManager.GetEntitySystem<RenderingTreeSystem>();
|
||||
while (_inertUpdateQueue.TryDequeue(out var sprite))
|
||||
{
|
||||
sprite.DoUpdateIsInert();
|
||||
}
|
||||
|
||||
// So we could calculate the correct size of the entities based on the contents of their sprite...
|
||||
// Or we can just assume that no entity is larger than 10x10 and get a stupid easy check.
|
||||
@@ -31,22 +50,11 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(currentMap, pvsBounds, true))
|
||||
foreach (var comp in _treeSystem.GetRenderTrees(currentMap, pvsBounds))
|
||||
{
|
||||
Box2 gridBounds;
|
||||
var bounds = pvsBounds.Translated(-comp.Owner.Transform.WorldPosition);
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
gridBounds = pvsBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
gridBounds = pvsBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
|
||||
}
|
||||
|
||||
var mapTree = renderTreeSystem.GetSpriteTreeForMap(currentMap, gridId);
|
||||
|
||||
mapTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
|
||||
comp.SpriteTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
|
||||
{
|
||||
if (value.IsInert)
|
||||
{
|
||||
@@ -55,7 +63,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
value.FrameUpdate(state);
|
||||
return true;
|
||||
}, gridBounds, approx: true);
|
||||
}, bounds, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class UserInterfaceSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<BoundUIWrapMessage>(MessageReceived);
|
||||
SubscribeLocalEvent<ClientUserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
|
||||
}
|
||||
|
||||
private void OnUserInterfaceShutdown(EntityUid uid, ClientUserInterfaceComponent component, ComponentShutdown args)
|
||||
{
|
||||
foreach (var bui in component.Interfaces)
|
||||
{
|
||||
bui.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void MessageReceived(BoundUIWrapMessage ev)
|
||||
{
|
||||
var cmp = ComponentManager.GetComponent<ClientUserInterfaceComponent>(ev.Entity);
|
||||
|
||||
cmp.MessageReceived(ev);
|
||||
}
|
||||
|
||||
internal void Send(BoundUIWrapMessage msg)
|
||||
{
|
||||
RaiseNetworkEvent(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public interface IClientEntityManager : IEntityManager
|
||||
public interface IClientEntityManager : IEntityManager, IEntityNetworkManager
|
||||
{
|
||||
/// <returns>The list of new entities created.</returns>
|
||||
List<EntityUid> ApplyEntityStates(EntityState[]? curEntStates, IEnumerable<EntityUid>? deletions,
|
||||
EntityState[]? nextEntStates);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
15
Robust.Client/GameObjects/IClientEntityManagerInternal.cs
Normal file
15
Robust.Client/GameObjects/IClientEntityManagerInternal.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
internal interface IClientEntityManagerInternal : IClientEntityManager
|
||||
{
|
||||
// These methods are used by the Game State Manager.
|
||||
|
||||
IEntity CreateEntity(string? prototypeName, EntityUid? uid = null);
|
||||
|
||||
void InitializeEntity(IEntity entity);
|
||||
|
||||
void StartEntity(IEntity entity);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class PlayerAttachedMsg : ComponentMessage
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class PlayerDetachedMsg : ComponentMessage
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,32 @@
|
||||
// ReSharper disable once RedundantUsingDirective
|
||||
// Used in EXCEPTION_TOLERANCE preprocessor
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameStates
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[UsedImplicitly]
|
||||
public class ClientGameStateManager : IClientGameStateManager
|
||||
{
|
||||
private GameStateProcessor _processor = default!;
|
||||
@@ -31,17 +38,23 @@ namespace Robust.Client.GameStates
|
||||
_pendingSystemMessages
|
||||
= new();
|
||||
|
||||
[Dependency] private readonly IClientEntityManager _entities = default!;
|
||||
private uint _metaCompNetId;
|
||||
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly IClientNetManager _network = default!;
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IClientMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IClientGameTiming _timing = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IComponentManager _componentManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
#if EXCEPTION_TOLERANCE
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
#endif
|
||||
|
||||
/// <inheritdoc />
|
||||
public int MinBufferSize => _processor.MinBufferSize;
|
||||
@@ -72,8 +85,8 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
_processor = new GameStateProcessor(_timing);
|
||||
|
||||
_network.RegisterNetMessage<MsgState>(MsgState.NAME, HandleStateMessage);
|
||||
_network.RegisterNetMessage<MsgStateAck>(MsgStateAck.NAME);
|
||||
_network.RegisterNetMessage<MsgState>(HandleStateMessage);
|
||||
_network.RegisterNetMessage<MsgStateAck>();
|
||||
_client.RunLevelChanged += RunLevelChanged;
|
||||
|
||||
_config.OnValueChanged(CVars.NetInterp, b => _processor.Interpolation = b, true);
|
||||
@@ -90,6 +103,12 @@ namespace Robust.Client.GameStates
|
||||
Predicting = _config.GetCVar(CVars.NetPredict);
|
||||
PredictTickBias = _config.GetCVar(CVars.NetPredictTickBias);
|
||||
PredictLagBias = _config.GetCVar(CVars.NetPredictLagBias);
|
||||
|
||||
var metaId = _compFactory.GetRegistration(typeof(MetaDataComponent)).NetID;
|
||||
if (!metaId.HasValue)
|
||||
throw new InvalidOperationException("MetaDataComponent does not have a NetId.");
|
||||
|
||||
_metaCompNetId = metaId.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -201,6 +220,11 @@ namespace Robust.Client.GameStates
|
||||
ResetPredictedEntities(_timing.CurTick);
|
||||
}
|
||||
|
||||
if (!curState.Extrapolated)
|
||||
{
|
||||
_processor.UpdateFullRep(curState);
|
||||
}
|
||||
|
||||
// Store last tick we got from the GameStateProcessor.
|
||||
_lastProcessedTick = _timing.CurTick;
|
||||
|
||||
@@ -310,6 +334,8 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private void ResetPredictedEntities(GameTick curTick)
|
||||
{
|
||||
var bus = (EntityEventBus) _entities.EventBus;
|
||||
|
||||
foreach (var entity in _entities.GetEntities())
|
||||
{
|
||||
// TODO: 99% there's an off-by-one here.
|
||||
@@ -327,17 +353,18 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
// TODO: handle component deletions/creations.
|
||||
foreach (var comp in _componentManager.GetNetComponents(entity.Uid))
|
||||
foreach (var (netId, comp) in _componentManager.GetNetComponents(entity.Uid))
|
||||
{
|
||||
DebugTools.AssertNotNull(comp.NetID);
|
||||
DebugTools.AssertNotNull(netId);
|
||||
|
||||
if (comp.LastModifiedTick < curTick || !last.TryGetValue(comp.NetID!.Value, out var compState))
|
||||
if (comp.LastModifiedTick < curTick || !last.TryGetValue(netId, out var compState))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.DebugS(CVars.NetPredict.Name, $" And also its component {comp.Name}");
|
||||
// TODO: Handle interpolation.
|
||||
bus.RaiseComponentEvent(comp, new ComponentHandleState(compState, null));
|
||||
comp.HandleComponentState(compState, null);
|
||||
}
|
||||
}
|
||||
@@ -351,24 +378,22 @@ namespace Robust.Client.GameStates
|
||||
// so that we can later roll back to it (if necessary).
|
||||
var outputData = new Dictionary<EntityUid, Dictionary<uint, ComponentState>>();
|
||||
|
||||
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
|
||||
var player = _players.LocalPlayer.Session;
|
||||
|
||||
foreach (var createdEntity in createdEntities)
|
||||
{
|
||||
var compData = new Dictionary<uint, ComponentState>();
|
||||
outputData.Add(createdEntity, compData);
|
||||
|
||||
foreach (var component in _componentManager.GetNetComponents(createdEntity))
|
||||
foreach (var (netId, component) in _componentManager.GetNetComponents(createdEntity))
|
||||
{
|
||||
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
|
||||
|
||||
var player = _players.LocalPlayer.Session;
|
||||
var state = component.GetComponentState(player);
|
||||
|
||||
if (state.GetType() == typeof(ComponentState))
|
||||
{
|
||||
if(state.GetType() == typeof(ComponentState))
|
||||
continue;
|
||||
}
|
||||
|
||||
compData.Add(state.NetID, state);
|
||||
compData.Add(netId, state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,8 +410,8 @@ namespace Robust.Client.GameStates
|
||||
private List<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
|
||||
{
|
||||
_config.TickProcessMessages();
|
||||
_mapManager.ApplyGameStatePre(curState.MapData);
|
||||
var createdEntities = _entities.ApplyEntityStates(curState.EntityStates, curState.EntityDeletions,
|
||||
_mapManager.ApplyGameStatePre(curState.MapData, curState.EntityStates);
|
||||
var createdEntities = ApplyEntityStates(curState.EntityStates, curState.EntityDeletions,
|
||||
nextState?.EntityStates);
|
||||
_players.ApplyPlayerStates(curState.PlayerStates);
|
||||
_mapManager.ApplyGameStatePost(curState.MapData);
|
||||
@@ -394,6 +419,227 @@ namespace Robust.Client.GameStates
|
||||
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState));
|
||||
return createdEntities;
|
||||
}
|
||||
|
||||
private List<EntityUid> ApplyEntityStates(EntityState[]? curEntStates, IEnumerable<EntityUid>? deletions,
|
||||
EntityState[]? nextEntStates)
|
||||
{
|
||||
var toApply = new Dictionary<IEntity, (EntityState?, EntityState?)>();
|
||||
var toInitialize = new List<Entity>();
|
||||
var created = new List<EntityUid>();
|
||||
deletions ??= new EntityUid[0];
|
||||
|
||||
if (curEntStates != null && curEntStates.Length != 0)
|
||||
{
|
||||
foreach (var es in curEntStates)
|
||||
{
|
||||
//Known entities
|
||||
if (_entities.TryGetEntity(es.Uid, out var entity))
|
||||
{
|
||||
// Logger.Debug($"[{IGameTiming.TickStampStatic}] MOD {es.Uid}");
|
||||
toApply.Add(entity, (es, null));
|
||||
}
|
||||
else //Unknown entities
|
||||
{
|
||||
var metaState = (MetaDataComponentState?) es.ComponentChanges?.FirstOrDefault(c => c.NetID == _metaCompNetId).State;
|
||||
if (metaState == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!");
|
||||
}
|
||||
// Logger.Debug($"[{IGameTiming.TickStampStatic}] CREATE {es.Uid} {metaState.PrototypeId}");
|
||||
var newEntity = (Entity)_entities.CreateEntity(metaState.PrototypeId, es.Uid);
|
||||
toApply.Add(newEntity, (es, null));
|
||||
toInitialize.Add(newEntity);
|
||||
created.Add(newEntity.Uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nextEntStates != null && nextEntStates.Length != 0)
|
||||
{
|
||||
foreach (var es in nextEntStates)
|
||||
{
|
||||
if (_entities.TryGetEntity(es.Uid, out var entity))
|
||||
{
|
||||
if (toApply.TryGetValue(entity, out var state))
|
||||
{
|
||||
toApply[entity] = (state.Item1, es);
|
||||
}
|
||||
else
|
||||
{
|
||||
toApply[entity] = (null, es);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var bus = (EntityEventBus) _entities.EventBus;
|
||||
|
||||
// Make sure this is done after all entities have been instantiated.
|
||||
foreach (var kvStates in toApply)
|
||||
{
|
||||
var ent = kvStates.Key;
|
||||
var entity = (Entity) ent;
|
||||
HandleEntityState(entity.EntityManager.ComponentManager, entity, bus, kvStates.Value.Item1,
|
||||
kvStates.Value.Item2);
|
||||
}
|
||||
|
||||
foreach (var id in deletions)
|
||||
{
|
||||
// Logger.Debug($"[{IGameTiming.TickStampStatic}] DELETE {id}");
|
||||
_entities.DeleteEntity(id);
|
||||
}
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
HashSet<Entity> brokenEnts = new HashSet<Entity>();
|
||||
#endif
|
||||
|
||||
foreach (var entity in toInitialize)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
_entities.InitializeEntity(entity);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("state", $"Server entity threw in Init: uid={entity.Uid}, proto={entity.Prototype}\n{e}");
|
||||
brokenEnts.Add(entity);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
foreach (var entity in toInitialize)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
if (brokenEnts.Contains(entity))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
#endif
|
||||
_entities.StartEntity(entity);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("state", $"Server entity threw in Start: uid={entity.Uid}, proto={entity.Prototype}\n{e}");
|
||||
brokenEnts.Add(entity);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
foreach (var entity in toInitialize)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
if (brokenEnts.Contains(entity))
|
||||
continue;
|
||||
#endif
|
||||
}
|
||||
#if EXCEPTION_TOLERANCE
|
||||
foreach (var entity in brokenEnts)
|
||||
{
|
||||
entity.Delete();
|
||||
}
|
||||
#endif
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityEventBus bus, EntityState? curState,
|
||||
EntityState? nextState)
|
||||
{
|
||||
var compStateWork = new Dictionary<ushort, (ComponentState? curState, ComponentState? nextState)>();
|
||||
var entityUid = entity.Uid;
|
||||
|
||||
if (curState?.ComponentChanges != null)
|
||||
{
|
||||
foreach (var compChange in curState.ComponentChanges)
|
||||
{
|
||||
if (compChange.Deleted)
|
||||
{
|
||||
if (compMan.TryGetComponent(entityUid, compChange.NetID, out var comp))
|
||||
{
|
||||
compMan.RemoveComponent(entityUid, comp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Right now we just assume every state from an unseen entity is added
|
||||
|
||||
if (compMan.HasComponent(entityUid, compChange.NetID))
|
||||
continue;
|
||||
|
||||
var newComp = (Component) _compFactory.GetComponent(compChange.NetID);
|
||||
newComp.Owner = entity;
|
||||
compMan.AddComponent(entity, newComp, true);
|
||||
|
||||
compStateWork[compChange.NetID] = (compChange.State, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (curState?.ComponentChanges != null)
|
||||
{
|
||||
foreach (var compChange in curState.ComponentChanges)
|
||||
{
|
||||
compStateWork[compChange.NetID] = (compChange.State, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextState?.ComponentChanges != null)
|
||||
{
|
||||
foreach (var compState in nextState.ComponentChanges)
|
||||
{
|
||||
if (compStateWork.TryGetValue(compState.NetID, out var state))
|
||||
{
|
||||
compStateWork[compState.NetID] = (state.curState, compState.State);
|
||||
}
|
||||
else
|
||||
{
|
||||
compStateWork[compState.NetID] = (null, compState.State);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (netId, (cur, next)) in compStateWork)
|
||||
{
|
||||
if (compMan.TryGetComponent(entityUid, (ushort) netId, out var component))
|
||||
{
|
||||
try
|
||||
{
|
||||
bus.RaiseComponentEvent(component, new ComponentHandleState(cur, next));
|
||||
component.HandleComponentState(cur, next);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var wrapper = new ComponentStateApplyException(
|
||||
$"Failed to apply comp state: entity={component.Owner}, comp={component.Name}", e);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
_runtimeLog.LogException(wrapper, "Component state apply");
|
||||
#else
|
||||
throw wrapper;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The component can be null here due to interp.
|
||||
// Because the NEXT state will have a new component, but this one doesn't yet.
|
||||
// That's fine though.
|
||||
if (cur == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var eUid = entityUid;
|
||||
var eRegisteredNetUidName = _compFactory.GetRegistration(netId).Name;
|
||||
DebugTools.Assert(
|
||||
$"Component does not exist for state: entUid={eUid}, expectedNetId={netId}, expectedName={eRegisteredNetUidName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class GameStateAppliedArgs : EventArgs
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -149,11 +149,6 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
Logger.DebugS("net.state", $"Applying State: ext={curState!.Extrapolated}, cTick={_timing.CurTick}, fSeq={curState.FromSequence}, tSeq={curState.ToSequence}, buf={_stateBuffer.Count}");
|
||||
}
|
||||
|
||||
if (!curState!.Extrapolated)
|
||||
{
|
||||
UpdateFullRep(curState);
|
||||
}
|
||||
}
|
||||
|
||||
var cState = curState!;
|
||||
@@ -162,8 +157,10 @@ namespace Robust.Client.GameStates
|
||||
return applyNextState;
|
||||
}
|
||||
|
||||
private void UpdateFullRep(GameState state)
|
||||
public void UpdateFullRep(GameState state)
|
||||
{
|
||||
// Logger.Debug($"UPDATE FULL REP: {string.Join(", ", state.EntityStates?.Select(e => e.Uid) ?? Enumerable.Empty<EntityUid>())}");
|
||||
|
||||
if (state.FromSequence == GameTick.Zero)
|
||||
{
|
||||
// Full state.
|
||||
@@ -198,14 +195,10 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
compData.Remove(change.NetID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entityState.ComponentStates != null)
|
||||
{
|
||||
foreach (var compState in entityState.ComponentStates)
|
||||
{
|
||||
compData[compState.NetID] = compState;
|
||||
else if (change.State is not null)
|
||||
{
|
||||
compData[change.NetID] = change.State;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,42 +127,41 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (!_netManager.IsConnected)
|
||||
return;
|
||||
|
||||
switch (currentSpace)
|
||||
switch (args.Space)
|
||||
{
|
||||
case OverlaySpace.ScreenSpace:
|
||||
DrawScreen(handle);
|
||||
DrawScreen(args);
|
||||
break;
|
||||
case OverlaySpace.WorldSpace:
|
||||
DrawWorld(handle);
|
||||
DrawWorld(args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawWorld(DrawingHandleBase handle)
|
||||
private void DrawWorld(in OverlayDrawArgs args)
|
||||
{
|
||||
bool pvsEnabled = _configurationManager.GetCVar<bool>("net.pvs");
|
||||
|
||||
if(!pvsEnabled)
|
||||
return;
|
||||
|
||||
float pvsSize = _configurationManager.GetCVar<float>("net.maxupdaterange");
|
||||
float pvsRange = _configurationManager.GetCVar<float>("net.maxupdaterange");
|
||||
var pvsCenter = _eyeManager.CurrentEye.Position;
|
||||
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsSize, pvsSize));
|
||||
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsRange * 2, pvsRange * 2));
|
||||
|
||||
var worldHandle = (DrawingHandleWorld)handle;
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
worldHandle.DrawRect(pvsBox, Color.Red, false);
|
||||
}
|
||||
|
||||
private void DrawScreen(DrawingHandleBase handle)
|
||||
private void DrawScreen(in OverlayDrawArgs args)
|
||||
{
|
||||
// remember, 0,0 is top left of ui with +X right and +Y down
|
||||
var screenHandle = (DrawingHandleScreen) handle;
|
||||
var screenHandle = args.ScreenHandle;
|
||||
|
||||
for (int i = 0; i < _netEnts.Count; i++)
|
||||
{
|
||||
@@ -179,7 +178,7 @@ namespace Robust.Client.GameStates
|
||||
var yPos = 10 + _lineHeight * i;
|
||||
var name = $"({netEnt.Id}) {ent.Prototype?.ID}";
|
||||
var color = CalcTextColor(ref netEnt);
|
||||
DrawString(screenHandle, _font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
|
||||
screenHandle.DrawString(_font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
|
||||
DrawTrafficBox(screenHandle, ref netEnt, xPos, yPos);
|
||||
}
|
||||
}
|
||||
@@ -224,17 +223,6 @@ namespace Robust.Client.GameStates
|
||||
base.DisposeBehavior();
|
||||
}
|
||||
|
||||
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str, Color textColor)
|
||||
{
|
||||
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
|
||||
|
||||
foreach (var rune in str.EnumerateRunes())
|
||||
{
|
||||
var advance = font.DrawChar(handle, rune, baseLine, 1, textColor);
|
||||
baseLine += new Vector2(advance, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private struct NetEntity
|
||||
{
|
||||
public GameTick LastUpdate;
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Robust.Client.GameStates
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
|
||||
private const int HistorySize = 60 * 3; // number of ticks to keep in history.
|
||||
private const int TargetPayloadBps = 56000 / 8; // Target Payload size in Bytes per second. A mind-numbing fifty-six thousand bits per second, who would ever need more?
|
||||
@@ -90,17 +91,14 @@ namespace Robust.Client.GameStates
|
||||
sb.Append($"\n Changes:");
|
||||
foreach (var compChange in entState.ComponentChanges)
|
||||
{
|
||||
var del = compChange.Deleted ? 'D' : 'C';
|
||||
sb.Append($"\n [{del}]{compChange.NetID}:{compChange.ComponentName}");
|
||||
}
|
||||
}
|
||||
var registration = _componentFactory.GetRegistration(compChange.NetID);
|
||||
var create = compChange.Created ? 'C' : '\0';
|
||||
var mod = !(compChange.Created || compChange.Created) ? 'M' : '\0';
|
||||
var del = compChange.Deleted ? 'D' : '\0';
|
||||
sb.Append($"\n [{create}{mod}{del}]{compChange.NetID}:{registration.Name}");
|
||||
|
||||
if (entState.ComponentStates is not null)
|
||||
{
|
||||
sb.Append($"\n States:");
|
||||
foreach (var compState in entState.ComponentStates)
|
||||
{
|
||||
sb.Append($"\n {compState.NetID}:{compState.GetType().Name}");
|
||||
if(compChange.State is not null)
|
||||
sb.Append($"\n STATE:{compChange.State.GetType().Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +151,7 @@ namespace Robust.Client.GameStates
|
||||
_history.RemoveRange(0, over);
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
// remember, 0,0 is top left of ui with +X right and +Y down
|
||||
|
||||
@@ -161,6 +159,7 @@ namespace Robust.Client.GameStates
|
||||
var width = HistorySize;
|
||||
var height = 500;
|
||||
var drawSizeThreshold = Math.Min(_totalHistoryPayload / HistorySize, 300);
|
||||
var handle = args.ScreenHandle;
|
||||
|
||||
// bottom payload line
|
||||
handle.DrawLine(new Vector2(leftMargin, height), new Vector2(leftMargin + width, height), Color.DarkGray.WithAlpha(0.8f));
|
||||
@@ -183,7 +182,7 @@ namespace Robust.Client.GameStates
|
||||
// Draw size if above average
|
||||
if (drawSizeThreshold * 1.5 < state.Payload)
|
||||
{
|
||||
DrawString((DrawingHandleScreen) handle, _font, new Vector2(xOff, yoff - _font.GetLineHeight(1)), state.Payload.ToString());
|
||||
handle.DrawString(_font, new Vector2(xOff, yoff - _font.GetLineHeight(1)), state.Payload.ToString());
|
||||
}
|
||||
|
||||
// second tick marks
|
||||
@@ -223,14 +222,14 @@ namespace Robust.Client.GameStates
|
||||
handle.DrawLine(new Vector2(leftMargin, midYoff), new Vector2(leftMargin + width, midYoff), Color.DarkGray.WithAlpha(0.8f));
|
||||
|
||||
// payload text
|
||||
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin + width, warnYoff), "56K");
|
||||
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin + width, midYoff), "33.6K");
|
||||
handle.DrawString(_font, new Vector2(leftMargin + width, warnYoff), "56K");
|
||||
handle.DrawString(_font, new Vector2(leftMargin + width, midYoff), "33.6K");
|
||||
|
||||
// interp text info
|
||||
if(lastLagY != -1)
|
||||
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin + width, lastLagY), $"{lastLagMs.ToString()}ms");
|
||||
handle.DrawString(_font, new Vector2(leftMargin + width, lastLagY), $"{lastLagMs.ToString()}ms");
|
||||
|
||||
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
|
||||
handle.DrawString(_font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
|
||||
}
|
||||
|
||||
protected override void DisposeBehavior()
|
||||
@@ -240,17 +239,6 @@ namespace Robust.Client.GameStates
|
||||
base.DisposeBehavior();
|
||||
}
|
||||
|
||||
private void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
|
||||
{
|
||||
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
|
||||
|
||||
foreach (var rune in str.EnumerateRunes())
|
||||
{
|
||||
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
|
||||
baseLine += new Vector2(advance, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private class NetShowGraphCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "net_graph";
|
||||
@@ -306,9 +294,9 @@ namespace Robust.Client.GameStates
|
||||
shell.WriteError("Invalid argument: Needs to be 0 or an entityId.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var overlayMan = IoCManager.Resolve<IOverlayManager>();
|
||||
|
||||
|
||||
if (overlayMan.HasOverlay(typeof(NetGraphOverlay)))
|
||||
{
|
||||
var netOverlay = overlayMan.GetOverlay<NetGraphOverlay>();
|
||||
|
||||
@@ -26,8 +26,9 @@ namespace Robust.Client.GameStates
|
||||
_shader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var handle = args.DrawingHandle;
|
||||
handle.UseShader(_shader);
|
||||
var worldHandle = (DrawingHandleWorld) handle;
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
@@ -50,9 +50,9 @@ namespace Robust.Client.Graphics
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void GetViewMatrix(out Matrix3 viewMatrix)
|
||||
public void GetViewMatrix(out Matrix3 viewMatrix, Vector2 renderScale)
|
||||
{
|
||||
var scaleMat = Matrix3.CreateScale(_scale.X, _scale.Y);
|
||||
var scaleMat = Matrix3.CreateScale(_scale.X * renderScale.X, _scale.Y * renderScale.Y);
|
||||
var rotMat = Matrix3.CreateRotation(_rotation);
|
||||
var transMat = Matrix3.CreateTranslation(-_coords.Position);
|
||||
|
||||
@@ -60,9 +60,9 @@ namespace Robust.Client.Graphics
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void GetViewMatrixInv(out Matrix3 viewMatrixInv)
|
||||
public void GetViewMatrixInv(out Matrix3 viewMatrixInv, Vector2 renderScale)
|
||||
{
|
||||
GetViewMatrix(out var viewMatrix);
|
||||
GetViewMatrix(out var viewMatrix, renderScale);
|
||||
viewMatrixInv = Matrix3.Invert(viewMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -19,6 +21,7 @@ namespace Robust.Client.Graphics
|
||||
|
||||
[Dependency] private readonly IClyde _displayManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
|
||||
// We default to this when we get set to a null eye.
|
||||
private readonly FixedEye _defaultEye = new();
|
||||
@@ -32,11 +35,18 @@ namespace Robust.Client.Graphics
|
||||
set => _currentEye = value;
|
||||
}
|
||||
|
||||
public IViewportControl MainViewport { get; set; } = default!;
|
||||
|
||||
public void ClearCurrentEye()
|
||||
{
|
||||
_currentEye = _defaultEye;
|
||||
}
|
||||
|
||||
void IEyeManager.Initialize()
|
||||
{
|
||||
MainViewport = _uiManager.MainViewport;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapId CurrentMap => CurrentEye.Position.MapId;
|
||||
|
||||
@@ -49,7 +59,7 @@ namespace Robust.Client.Graphics
|
||||
var topRight = ScreenToMap(new Vector2(vpSize.X, 0));
|
||||
var bottomRight = ScreenToMap(vpSize);
|
||||
var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y));
|
||||
|
||||
|
||||
var left = MathHelper.Min(topLeft.X, topRight.X, bottomRight.X, bottomLeft.X);
|
||||
var bottom = MathHelper.Min(topLeft.Y, topRight.Y, bottomRight.Y, bottomLeft.Y);
|
||||
var right = MathHelper.Max(topLeft.X, topRight.X, bottomRight.X, bottomLeft.X);
|
||||
@@ -61,16 +71,7 @@ namespace Robust.Client.Graphics
|
||||
/// <inheritdoc />
|
||||
public Vector2 WorldToScreen(Vector2 point)
|
||||
{
|
||||
var newPoint = point;
|
||||
|
||||
CurrentEye.GetViewMatrix(out var viewMatrix);
|
||||
newPoint = viewMatrix * newPoint;
|
||||
|
||||
// (inlined version of UiProjMatrix)
|
||||
newPoint *= new Vector2(1, -1) * PixelsPerMeter;
|
||||
newPoint += _displayManager.ScreenSize / 2f;
|
||||
|
||||
return newPoint;
|
||||
return MainViewport.WorldToScreen(point);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -103,29 +104,23 @@ namespace Robust.Client.Graphics
|
||||
|
||||
public ScreenCoordinates MapToScreen(MapCoordinates point)
|
||||
{
|
||||
return new(WorldToScreen(point.Position));
|
||||
return new(WorldToScreen(point.Position), MainViewport.Window?.Id ?? default);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapCoordinates ScreenToMap(ScreenCoordinates point)
|
||||
{
|
||||
return ScreenToMap(point.Position);
|
||||
var (pos, window) = point;
|
||||
if (window != MainViewport.Window?.Id)
|
||||
return default;
|
||||
|
||||
return MainViewport.ScreenToMap(pos);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapCoordinates ScreenToMap(Vector2 point)
|
||||
{
|
||||
var newPoint = point;
|
||||
|
||||
// (inlined version of UiProjMatrix^-1)
|
||||
newPoint -= _displayManager.ScreenSize / 2f;
|
||||
newPoint *= new Vector2(1, -1) / PixelsPerMeter;
|
||||
|
||||
// view matrix
|
||||
CurrentEye.GetViewMatrixInv(out var viewMatrixInv);
|
||||
newPoint = viewMatrixInv * newPoint;
|
||||
|
||||
return new MapCoordinates(newPoint, CurrentMap);
|
||||
return MainViewport.ScreenToMap(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +41,15 @@ namespace Robust.Client.Graphics
|
||||
/// world space to camera space.
|
||||
/// </summary>
|
||||
/// <param name="viewMatrix">View matrix for this camera.</param>
|
||||
void GetViewMatrix(out Matrix3 viewMatrix);
|
||||
/// <param name="renderScale"></param>
|
||||
void GetViewMatrix(out Matrix3 viewMatrix, Vector2 renderScale);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the inverted view matrix for this eye, used to convert a point from
|
||||
/// camera space to world space.
|
||||
/// </summary>
|
||||
/// <param name="viewMatrixInv">Inverted view matrix for this camera.</param>
|
||||
void GetViewMatrixInv(out Matrix3 viewMatrixInv);
|
||||
/// <param name="renderScale"></param>
|
||||
void GetViewMatrixInv(out Matrix3 viewMatrixInv, Vector2 renderScale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
@@ -17,6 +18,8 @@ namespace Robust.Client.Graphics
|
||||
/// </remarks>
|
||||
IEye CurrentEye { get; set; }
|
||||
|
||||
IViewportControl MainViewport { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the map on which the current eye is "placed".
|
||||
/// </summary>
|
||||
@@ -72,5 +75,6 @@ namespace Robust.Client.Graphics
|
||||
MapCoordinates ScreenToMap(Vector2 point);
|
||||
|
||||
void ClearCurrentEye();
|
||||
void Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
internal bool IsEfxSupported;
|
||||
|
||||
private ISawmill _openALSawmill = default!;
|
||||
|
||||
private void _initializeAudio()
|
||||
{
|
||||
_openALSawmill = Logger.GetSawmill("clyde.oal");
|
||||
|
||||
_audioOpenDevice();
|
||||
|
||||
// Create OpenAL context.
|
||||
@@ -53,7 +57,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
|
||||
|
||||
ConfigurationManager.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
}
|
||||
|
||||
private void _audioCreateContext()
|
||||
@@ -74,14 +78,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_alContextExtensions.Add(extension);
|
||||
}
|
||||
|
||||
Logger.DebugS("clyde.oal", "OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor));
|
||||
Logger.DebugS("clyde.oal", "OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer));
|
||||
Logger.DebugS("clyde.oal", "OpenAL Version: {0}", AL.Get(ALGetString.Version));
|
||||
_openALSawmill.Debug("OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor));
|
||||
_openALSawmill.Debug("OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer));
|
||||
_openALSawmill.Debug("OpenAL Version: {0}", AL.Get(ALGetString.Version));
|
||||
}
|
||||
|
||||
private void _audioOpenDevice()
|
||||
{
|
||||
var preferredDevice = ConfigurationManager.GetCVar(CVars.AudioDevice);
|
||||
var preferredDevice = _cfg.GetCVar(CVars.AudioDevice);
|
||||
|
||||
// Open device.
|
||||
if (!string.IsNullOrEmpty(preferredDevice))
|
||||
@@ -89,7 +93,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_openALDevice = ALC.OpenDevice(preferredDevice);
|
||||
if (_openALDevice == IntPtr.Zero)
|
||||
{
|
||||
Logger.WarningS("clyde.oal", "Unable to open preferred audio device '{0}': {1}. Falling back default.",
|
||||
_openALSawmill.Warning("Unable to open preferred audio device '{0}': {1}. Falling back default.",
|
||||
preferredDevice, ALC.GetError(ALDevice.Null));
|
||||
|
||||
_openALDevice = ALC.OpenDevice(null);
|
||||
@@ -153,7 +157,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Clear out finalized audio sources.
|
||||
while (_sourceDisposeQueue.TryDequeue(out var handles))
|
||||
{
|
||||
Logger.DebugS("clyde.oal", "Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
_openALSawmill.Debug("Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
if (IsEfxSupported) RemoveEfx(handles);
|
||||
AL.DeleteSource(handles.sourceHandle);
|
||||
_checkAlError();
|
||||
@@ -163,7 +167,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Clear out finalized buffered audio sources.
|
||||
while (_bufferedSourceDisposeQueue.TryDequeue(out var handles))
|
||||
{
|
||||
Logger.DebugS("clyde.oal", "Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
_openALSawmill.Debug("Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
if (IsEfxSupported) RemoveEfx(handles);
|
||||
AL.DeleteSource(handles.sourceHandle);
|
||||
_checkAlError();
|
||||
@@ -211,24 +215,24 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
private static void _checkAlcError(ALDevice device,
|
||||
private void _checkAlcError(ALDevice device,
|
||||
[CallerMemberName] string callerMember = "",
|
||||
[CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = ALC.GetError(device);
|
||||
if (error != AlcError.NoError)
|
||||
{
|
||||
Logger.ErrorS("clyde.oal", "[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
|
||||
_openALSawmill.Error("[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
|
||||
}
|
||||
}
|
||||
|
||||
private static void _checkAlError([CallerMemberName] string callerMember = "",
|
||||
private void _checkAlError([CallerMemberName] string callerMember = "",
|
||||
[CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = AL.GetError();
|
||||
if (error != ALError.NoError)
|
||||
{
|
||||
Logger.ErrorS("clyde.oal", "[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
|
||||
_openALSawmill.Error("[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,6 +334,35 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return new AudioStream(handle, length, wav.NumChannels, name);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
|
||||
{
|
||||
var fmt = channels switch
|
||||
{
|
||||
1 => ALFormat.Mono16,
|
||||
2 => ALFormat.Stereo16,
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(channels), "Only stereo and mono is currently supported")
|
||||
};
|
||||
|
||||
var buffer = AL.GenBuffer();
|
||||
_checkAlError();
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (short* ptr = samples)
|
||||
{
|
||||
AL.BufferData(buffer, fmt, (IntPtr) ptr, samples.Length * sizeof(short), sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
return new AudioStream(handle, length, channels, name);
|
||||
}
|
||||
|
||||
private sealed class LoadedAudioSample
|
||||
{
|
||||
public readonly int BufferHandle;
|
||||
@@ -381,14 +414,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.SourcePlay(SourceHandle);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void StopPlaying()
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.SourceStop(SourceHandle);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public bool IsPlaying
|
||||
@@ -407,14 +440,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.GetSource(SourceHandle, ALSourceb.Looping, out var ret);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
return ret;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourceb.Looping, value);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +455,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourceb.SourceRelative, true);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolume(float decibels)
|
||||
@@ -436,7 +469,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
_gain = MathF.Pow(10, decibels / 10);
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float scale)
|
||||
{
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
}
|
||||
_gain = scale;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
@@ -453,7 +500,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
gain *= gain * gain;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * gain);
|
||||
}
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
private void SetOcclusionEfx(float gain, float cutoff)
|
||||
@@ -473,7 +520,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.SecOffset, seconds);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public bool SetPosition(Vector2 position)
|
||||
@@ -499,7 +546,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
#endif
|
||||
|
||||
AL.Source(SourceHandle, ALSource3f.Position, x, y, 0);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -526,14 +573,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
AL.Source(SourceHandle, ALSource3f.Velocity, x, y, 0);
|
||||
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetPitch(float pitch)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.Pitch, pitch);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
~AudioSource()
|
||||
@@ -559,7 +606,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (FilterHandle != 0) EFX.DeleteFilter(FilterHandle);
|
||||
AL.DeleteSource(SourceHandle);
|
||||
_master._audioSources.Remove(SourceHandle);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
SourceHandle = -1;
|
||||
@@ -609,7 +656,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.SourcePlay(stackalloc int[] {SourceHandle!.Value});
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void StopPlaying()
|
||||
@@ -617,7 +664,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.SourceStop(SourceHandle!.Value);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public bool IsPlaying
|
||||
@@ -643,7 +690,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_mono = false;
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSourceb.SourceRelative, true);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetLooping()
|
||||
@@ -662,7 +709,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
_gain = MathF.Pow(10, decibels / 10);
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float scale)
|
||||
{
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle!.Value, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
}
|
||||
_gain = scale;
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
@@ -680,7 +741,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Gain, gain * _gain);
|
||||
}
|
||||
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
private void SetOcclusionEfx(float gain, float cutoff)
|
||||
@@ -700,7 +761,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.SecOffset, seconds);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public bool SetPosition(Vector2 position)
|
||||
@@ -717,7 +778,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_mono = true;
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSource3f.Position, x, y, 0);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -744,7 +805,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
AL.Source(SourceHandle!.Value, ALSource3f.Velocity, x, y, 0);
|
||||
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetPitch(float pitch)
|
||||
@@ -752,7 +813,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Pitch, pitch);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
~BufferedAudioSource()
|
||||
@@ -770,7 +831,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
if (SourceHandle == null) return;
|
||||
|
||||
if (!disposing || Thread.CurrentThread != _master._mainThread)
|
||||
if (!disposing || Thread.CurrentThread != _master._gameThread)
|
||||
{
|
||||
// We can't run this code inside another thread so tell Clyde to clear it up later.
|
||||
_master.DeleteBufferedSourceOnMainThread(SourceHandle.Value, FilterHandle);
|
||||
@@ -783,7 +844,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
AL.DeleteSource(SourceHandle.Value);
|
||||
AL.DeleteBuffers(BufferHandles);
|
||||
_master._bufferedAudioSources.Remove(SourceHandle.Value);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
SourceHandle = null;
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using GlfwImage = OpenToolkit.GraphicsLibraryFramework.Image;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal sealed partial class Clyde
|
||||
{
|
||||
// These are actually Cursor* but we can't do that because no pointer generic arguments.
|
||||
// Need a queue to dispose cursors since the GLFW methods aren't allowed from non-main thread (finalizers).
|
||||
// And they also aren't re-entrant.
|
||||
private readonly ConcurrentQueue<IntPtr> _cursorDisposeQueue = new();
|
||||
|
||||
private readonly Dictionary<StandardCursorShape, CursorImpl> _standardCursors =
|
||||
new();
|
||||
|
||||
// Keep current active cursor around so it doesn't get garbage collected.
|
||||
private CursorImpl? _currentCursor;
|
||||
|
||||
public ICursor GetStandardCursor(StandardCursorShape shape)
|
||||
{
|
||||
return _standardCursors[shape];
|
||||
}
|
||||
|
||||
public unsafe ICursor CreateCursor(Image<Rgba32> image, Vector2i hotSpot)
|
||||
{
|
||||
fixed (Rgba32* pixPtr = image.GetPixelSpan())
|
||||
{
|
||||
var gImg = new GlfwImage(image.Width, image.Height, (byte*) pixPtr);
|
||||
var (hotX, hotY) = hotSpot;
|
||||
var ptr = GLFW.CreateCursor(gImg, hotX, hotY);
|
||||
|
||||
return new CursorImpl(this, ptr, false);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetCursor(ICursor? cursor)
|
||||
{
|
||||
if (_currentCursor == cursor)
|
||||
{
|
||||
// Nothing has to be done!
|
||||
return;
|
||||
}
|
||||
|
||||
if (cursor == null)
|
||||
{
|
||||
_currentCursor = null;
|
||||
GLFW.SetCursor(_glfwWindow, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(cursor is CursorImpl impl) || impl.Owner != this)
|
||||
{
|
||||
throw new ArgumentException("Cursor is not created by this clyde instance.");
|
||||
}
|
||||
|
||||
if (impl.Cursor == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(cursor));
|
||||
}
|
||||
|
||||
_currentCursor = impl;
|
||||
GLFW.SetCursor(_glfwWindow, impl.Cursor);
|
||||
}
|
||||
|
||||
private unsafe void FlushCursorDispose()
|
||||
{
|
||||
while (_cursorDisposeQueue.TryDequeue(out var cursor))
|
||||
{
|
||||
var ptr = (Cursor*) cursor;
|
||||
|
||||
if (_currentCursor != null && ptr == _currentCursor.Cursor)
|
||||
{
|
||||
// Currently active cursor getting disposed.
|
||||
_currentCursor = null;
|
||||
}
|
||||
|
||||
GLFW.DestroyCursor(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitCursors()
|
||||
{
|
||||
unsafe void AddStandardCursor(StandardCursorShape standardShape, CursorShape shape)
|
||||
{
|
||||
var ptr = GLFW.CreateStandardCursor(shape);
|
||||
|
||||
var impl = new CursorImpl(this, ptr, true);
|
||||
|
||||
_standardCursors.Add(standardShape, impl);
|
||||
}
|
||||
|
||||
AddStandardCursor(StandardCursorShape.Arrow, CursorShape.Arrow);
|
||||
AddStandardCursor(StandardCursorShape.IBeam, CursorShape.IBeam);
|
||||
AddStandardCursor(StandardCursorShape.Crosshair, CursorShape.Crosshair);
|
||||
AddStandardCursor(StandardCursorShape.Hand, CursorShape.Hand);
|
||||
AddStandardCursor(StandardCursorShape.HResize, CursorShape.HResize);
|
||||
AddStandardCursor(StandardCursorShape.VResize, CursorShape.VResize);
|
||||
}
|
||||
|
||||
private sealed class CursorImpl : ICursor
|
||||
{
|
||||
private readonly bool _standard;
|
||||
public Clyde Owner { get; }
|
||||
public unsafe Cursor* Cursor { get; private set; }
|
||||
|
||||
public unsafe CursorImpl(Clyde clyde, Cursor* pointer, bool standard)
|
||||
{
|
||||
_standard = standard;
|
||||
Owner = clyde;
|
||||
Cursor = pointer;
|
||||
}
|
||||
|
||||
~CursorImpl()
|
||||
{
|
||||
DisposeImpl();
|
||||
}
|
||||
|
||||
private unsafe void DisposeImpl()
|
||||
{
|
||||
Owner._cursorDisposeQueue.Enqueue((IntPtr) Cursor);
|
||||
Cursor = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_standard)
|
||||
{
|
||||
throw new InvalidOperationException("Can't dispose standard cursor shape.");
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
DisposeImpl();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
static Clyde()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
|
||||
if (OperatingSystem.IsWindows() &&
|
||||
RuntimeInformation.ProcessArchitecture == Architecture.X64 &&
|
||||
Environment.GetEnvironmentVariable("ROBUST_INTEGRATED_GPU") != "1")
|
||||
{
|
||||
@@ -28,20 +28,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NativeLibrary.SetDllImportResolver(typeof(GL).Assembly, (name, assembly, path) =>
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
|
||||
if (OperatingSystem.IsLinux()
|
||||
&& _dllMapLinux.TryGetValue(name, out var mappedName))
|
||||
{
|
||||
return NativeLibrary.Load(mappedName);
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
|
||||
if (OperatingSystem.IsMacOS()
|
||||
&& _dllMapMacOS.TryGetValue(name, out mappedName))
|
||||
{
|
||||
return NativeLibrary.Load(mappedName);
|
||||
|
||||
172
Robust.Client/Graphics/Clyde/Clyde.Events.cs
Normal file
172
Robust.Client/Graphics/Clyde/Clyde.Events.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
// Makes switching easier.
|
||||
#if EXCEPTION_TOLERANCE
|
||||
#define EXCEPTION_TOLERANCE_LOCAL
|
||||
using System;
|
||||
using Robust.Shared.Log;
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal sealed partial class Clyde
|
||||
{
|
||||
// To avoid re-entrancy bollocks we need ANOTHER queue here to actually dispatch our raw input events.
|
||||
// Yes, on top of the two queues inside the GLFW impl.
|
||||
// Because the GLFW-impl queue has to get flushed to avoid deadlocks on window creation
|
||||
// which is ALSO where key events get raised from in a re-entrant fashion. Yay.
|
||||
private readonly Queue<DEventBase> _eventDispatchQueue = new();
|
||||
|
||||
private void DispatchEvents()
|
||||
{
|
||||
while (_eventDispatchQueue.TryDequeue(out var ev))
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE_LOCAL
|
||||
try
|
||||
#endif
|
||||
{
|
||||
DispatchSingleEvent(ev);
|
||||
}
|
||||
#if EXCEPTION_TOLERANCE_LOCAL
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("clyde.win", $"Error dispatching window event {ev.GetType().Name}:\n{e}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void DispatchSingleEvent(DEventBase ev)
|
||||
{
|
||||
switch (ev)
|
||||
{
|
||||
case DEventKeyDown(var args):
|
||||
KeyDown?.Invoke(args);
|
||||
break;
|
||||
case DEventKeyUp(var args):
|
||||
KeyUp?.Invoke(args);
|
||||
break;
|
||||
case DEventMouseMove(var args):
|
||||
MouseMove?.Invoke(args);
|
||||
break;
|
||||
case DEventMouseEnterLeave(var args):
|
||||
MouseEnterLeave?.Invoke(args);
|
||||
break;
|
||||
case DEventScroll(var args):
|
||||
MouseWheel?.Invoke(args);
|
||||
break;
|
||||
case DEventText(var args):
|
||||
TextEntered?.Invoke(args);
|
||||
break;
|
||||
case DEventWindowClosed(var reg, var args):
|
||||
reg.Closed?.Invoke(args);
|
||||
CloseWindow?.Invoke(args);
|
||||
|
||||
if (reg.DisposeOnClose && !reg.IsMainWindow)
|
||||
DoDestroyWindow(reg);
|
||||
break;
|
||||
case DEventWindowContentScaleChanged(var args):
|
||||
OnWindowScaleChanged?.Invoke(args);
|
||||
break;
|
||||
case DEventWindowFocus(var args):
|
||||
OnWindowFocused?.Invoke(args);
|
||||
break;
|
||||
case DEventWindowResized(var args):
|
||||
OnWindowResized?.Invoke(args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void SendKeyUp(KeyEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventKeyUp(ev));
|
||||
}
|
||||
|
||||
private void SendKeyDown(KeyEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventKeyDown(ev));
|
||||
}
|
||||
|
||||
private void SendScroll(MouseWheelEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventScroll(ev));
|
||||
}
|
||||
|
||||
private void SendCloseWindow(WindowReg windowReg, WindowClosedEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventWindowClosed(windowReg, ev));
|
||||
}
|
||||
|
||||
private void SendWindowResized(WindowReg reg, Vector2i oldSize)
|
||||
{
|
||||
if (reg.IsMainWindow)
|
||||
{
|
||||
UpdateMainWindowLoadedRtSize();
|
||||
GL.Viewport(0, 0, reg.FramebufferSize.X, reg.FramebufferSize.Y);
|
||||
CheckGlError();
|
||||
}
|
||||
else
|
||||
{
|
||||
reg.RenderTexture!.Dispose();
|
||||
CreateWindowRenderTexture(reg);
|
||||
}
|
||||
|
||||
var eventArgs = new WindowResizedEventArgs(
|
||||
oldSize,
|
||||
reg.FramebufferSize,
|
||||
reg.Handle);
|
||||
|
||||
_eventDispatchQueue.Enqueue(new DEventWindowResized(eventArgs));
|
||||
}
|
||||
|
||||
private void SendWindowContentScaleChanged(WindowContentScaleEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventWindowContentScaleChanged(ev));
|
||||
}
|
||||
|
||||
private void SendWindowFocus(WindowFocusedEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventWindowFocus(ev));
|
||||
}
|
||||
|
||||
private void SendText(TextEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventText(ev));
|
||||
}
|
||||
|
||||
private void SendMouseMove(MouseMoveEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventMouseMove(ev));
|
||||
}
|
||||
|
||||
private void SendMouseEnterLeave(MouseEnterLeaveEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventMouseEnterLeave(ev));
|
||||
}
|
||||
|
||||
// D stands for Dispatch
|
||||
private abstract record DEventBase;
|
||||
|
||||
private sealed record DEventKeyUp(KeyEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventKeyDown(KeyEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventScroll(MouseWheelEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventWindowClosed(WindowReg Reg, WindowClosedEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventWindowResized(WindowResizedEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventWindowContentScaleChanged(WindowContentScaleEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventWindowFocus(WindowFocusedEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventText(TextEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventMouseMove(MouseMoveEventArgs Args) : DEventBase;
|
||||
private sealed record DEventMouseEnterLeave(MouseEnterLeaveEventArgs Args) : DEventBase;
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
var extensions = GetGLExtensions();
|
||||
|
||||
Logger.DebugS("clyde.ogl", "OpenGL capabilities:");
|
||||
_sawmillOgl.Debug("OpenGL capabilities:");
|
||||
|
||||
if (!_isGLES)
|
||||
{
|
||||
@@ -81,7 +81,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// We're ES <3.2, KHR_debug is extension and needs KHR suffixes.
|
||||
_isGLKhrDebugESExtension = true;
|
||||
Logger.DebugS("clyde.ogl", " khr_debug is ES extension!");
|
||||
_sawmillOgl.Debug(" khr_debug is ES extension!");
|
||||
}
|
||||
|
||||
CheckGLCap(ref _hasGLVertexArrayObject, "vertex_array_object", (3, 0));
|
||||
@@ -104,7 +104,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// This is 3.2 or extensions
|
||||
_hasGLFloatFramebuffers = !_isGLES;
|
||||
|
||||
Logger.DebugS("clyde.ogl", $" GLES: {_isGLES}");
|
||||
_sawmillOgl.Debug($" GLES: {_isGLES}");
|
||||
|
||||
void CheckGLCap(ref bool cap, string capName, (int major, int minor)? versionMin = null,
|
||||
params string[] exts)
|
||||
@@ -115,15 +115,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var prev = cap;
|
||||
var cVarName = $"display.ogl_block_{capName}";
|
||||
var block = ConfigurationManager.GetCVar<bool>(cVarName);
|
||||
var block = _cfg.GetCVar<bool>(cVarName);
|
||||
|
||||
if (block)
|
||||
{
|
||||
cap = false;
|
||||
Logger.DebugS("clyde.ogl", $" {cVarName} SET, BLOCKING {capName} (was: {prev})");
|
||||
_sawmillOgl.Debug($" {cVarName} SET, BLOCKING {capName} (was: {prev})");
|
||||
}
|
||||
|
||||
Logger.DebugS("clyde.ogl", $" {capName}: {cap}");
|
||||
_sawmillOgl.Debug($" {capName}: {cap}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
foreach (var cvar in cvars)
|
||||
{
|
||||
ConfigurationManager.RegisterCVar($"display.ogl_block_{cvar}", false);
|
||||
_cfg.RegisterCVar($"display.ogl_block_{cvar}", false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,14 +168,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
extensionsText += extension;
|
||||
extensions.Add(extension);
|
||||
}
|
||||
Logger.DebugS("clyde.ogl", "OpenGL Extensions: {0}", extensionsText);
|
||||
_sawmillOgl.Debug("OpenGL Extensions: {0}", extensionsText);
|
||||
return extensions;
|
||||
}
|
||||
else
|
||||
{
|
||||
// GLES uses the (old?) API
|
||||
var extensions = GL.GetString(StringName.Extensions);
|
||||
Logger.DebugS("clyde.ogl", "OpenGL Extensions: {0}", extensions);
|
||||
_sawmillOgl.Debug("OpenGL Extensions: {0}", extensions);
|
||||
return new HashSet<string>(extensions.Split(' '));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
@@ -24,18 +25,28 @@ namespace Robust.Client.Graphics.Clyde
|
||||
=
|
||||
new();
|
||||
|
||||
public void Render()
|
||||
public unsafe void Render()
|
||||
{
|
||||
CheckTransferringScreenshots();
|
||||
|
||||
var allMinimized = true;
|
||||
foreach (var windowReg in _windowing!.AllWindows)
|
||||
{
|
||||
if (!windowReg.IsMinimized)
|
||||
{
|
||||
allMinimized = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var size = ScreenSize;
|
||||
if (size.X == 0 || size.Y == 0 || _isMinimized)
|
||||
if (size.X == 0 || size.Y == 0 || allMinimized)
|
||||
{
|
||||
ClearFramebuffer(Color.Black);
|
||||
|
||||
// We have to keep running swapbuffers here
|
||||
// or else the user's PC will turn into a heater!!
|
||||
SwapBuffers();
|
||||
SwapMainBuffers();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -51,7 +62,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
ClearFramebuffer(Color.Black);
|
||||
|
||||
// Update shared UBOs.
|
||||
_updateUniformConstants(_framebufferSize);
|
||||
_updateUniformConstants(_windowing.MainWindow!.FramebufferSize);
|
||||
|
||||
{
|
||||
CalcScreenMatrices(ScreenSize, out var proj, out var view);
|
||||
@@ -63,103 +74,133 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
DrawSplash(_renderHandle);
|
||||
FlushRenderQueue();
|
||||
SwapBuffers();
|
||||
SwapMainBuffers();
|
||||
return;
|
||||
}
|
||||
|
||||
RenderOverlays(OverlaySpace.ScreenSpaceBelowWorld);
|
||||
|
||||
_mainViewport.Eye = _eyeManager.CurrentEye;
|
||||
RenderViewport(_mainViewport); //Worldspace overlays are rendered here.
|
||||
foreach (var weak in _viewports.Values)
|
||||
{
|
||||
var handle = _renderHandle.DrawingHandleScreen;
|
||||
var tex = _mainViewport.RenderTarget.Texture;
|
||||
|
||||
handle.DrawTexture(tex, (0, 0));
|
||||
FlushRenderQueue();
|
||||
if (weak.TryGetTarget(out var viewport) && viewport.AutomaticRender)
|
||||
RenderViewport(viewport);
|
||||
}
|
||||
|
||||
TakeScreenshot(ScreenshotType.BeforeUI);
|
||||
|
||||
RenderOverlays(OverlaySpace.ScreenSpace);
|
||||
|
||||
using (DebugGroup("UI"))
|
||||
{
|
||||
_userInterfaceManager.Render(_renderHandle);
|
||||
FlushRenderQueue();
|
||||
}
|
||||
|
||||
TakeScreenshot(ScreenshotType.AfterUI);
|
||||
TakeScreenshot(ScreenshotType.Final);
|
||||
|
||||
BlitSecondaryWindows();
|
||||
|
||||
// And finally, swap those buffers!
|
||||
SwapBuffers();
|
||||
SwapMainBuffers();
|
||||
}
|
||||
|
||||
private void RenderOverlays(OverlaySpace space)
|
||||
private void RenderOverlays(Viewport vp, OverlaySpace space, in Box2 worldBox)
|
||||
{
|
||||
using (DebugGroup($"Overlays: {space}"))
|
||||
{
|
||||
var list = new List<Overlay>();
|
||||
|
||||
foreach (var overlay in _overlayManager.AllOverlays)
|
||||
var list = GetOverlaysForSpace(space);
|
||||
foreach (var overlay in list)
|
||||
{
|
||||
if ((overlay.Space & space) != 0)
|
||||
if (overlay.RequestScreenTexture)
|
||||
{
|
||||
list.Add(overlay);
|
||||
}
|
||||
}
|
||||
FlushRenderQueue();
|
||||
list.Sort(OverlayComparer.Instance);
|
||||
foreach (var overlay in list) {
|
||||
if (overlay.RequestScreenTexture) {
|
||||
FlushRenderQueue();
|
||||
UpdateOverlayScreenTexture(space, _mainViewport.RenderTarget);
|
||||
UpdateOverlayScreenTexture(space, vp.RenderTarget);
|
||||
}
|
||||
if (overlay.OverwriteTargetFrameBuffer()) {
|
||||
|
||||
if (overlay.OverwriteTargetFrameBuffer())
|
||||
{
|
||||
ClearFramebuffer(default);
|
||||
}
|
||||
overlay.ClydeRender(_renderHandle, space);
|
||||
FlushRenderQueue();
|
||||
|
||||
overlay.ClydeRender(_renderHandle, space, null, vp, new UIBox2i((0, 0), vp.Size), worldBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderOverlaysDirect(
|
||||
Viewport vp,
|
||||
IViewportControl vpControl,
|
||||
DrawingHandleBase handle,
|
||||
OverlaySpace space,
|
||||
in UIBox2i bounds)
|
||||
{
|
||||
var list = GetOverlaysForSpace(space);
|
||||
|
||||
var worldBounds = CalcWorldBounds(vp);
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, worldBounds);
|
||||
|
||||
foreach (var overlay in list)
|
||||
{
|
||||
overlay.Draw(args);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Overlay> GetOverlaysForSpace(OverlaySpace space)
|
||||
{
|
||||
var list = new List<Overlay>();
|
||||
|
||||
foreach (var overlay in _overlayManager.AllOverlays)
|
||||
{
|
||||
if ((overlay.Space & space) != 0)
|
||||
{
|
||||
list.Add(overlay);
|
||||
}
|
||||
}
|
||||
|
||||
list.Sort(OverlayComparer.Instance);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private ClydeTexture? ScreenBufferTexture;
|
||||
private GLHandle screenBufferHandle;
|
||||
private Vector2 lastFrameSize;
|
||||
|
||||
/// <summary>
|
||||
/// Sends SCREEN_TEXTURE to all overlays in the given OverlaySpace that request it.
|
||||
/// </summary>
|
||||
private bool UpdateOverlayScreenTexture(OverlaySpace space, RenderTexture texture) {
|
||||
private bool UpdateOverlayScreenTexture(OverlaySpace space, RenderTexture texture)
|
||||
{
|
||||
//This currently does NOT consider viewports and just grabs the current screen framebuffer. This will need to be improved upon in the future.
|
||||
List<Overlay> oTargets = new List<Overlay>();
|
||||
foreach (var overlay in _overlayManager.AllOverlays) {
|
||||
if (overlay.RequestScreenTexture && overlay.Space == space) {
|
||||
foreach (var overlay in _overlayManager.AllOverlays)
|
||||
{
|
||||
if (overlay.RequestScreenTexture && overlay.Space == space)
|
||||
{
|
||||
oTargets.Add(overlay);
|
||||
}
|
||||
}
|
||||
if (oTargets.Count > 0 && ScreenBufferTexture != null) {
|
||||
if (lastFrameSize != _framebufferSize) {
|
||||
|
||||
if (oTargets.Count > 0 && ScreenBufferTexture != null)
|
||||
{
|
||||
if (lastFrameSize != texture.Size)
|
||||
{
|
||||
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, _hasGLSrgb ? PixelInternalFormat.Srgb8Alpha8 : PixelInternalFormat.Rgba8, _framebufferSize.X, _framebufferSize.Y, 0,
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0,
|
||||
_hasGLSrgb ? PixelInternalFormat.Srgb8Alpha8 : PixelInternalFormat.Rgba8, texture.Size.X,
|
||||
texture.Size.Y, 0,
|
||||
PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
|
||||
}
|
||||
lastFrameSize = _framebufferSize;
|
||||
|
||||
lastFrameSize = texture.Size;
|
||||
CopyRenderTextureToTexture(texture, ScreenBufferTexture);
|
||||
foreach (Overlay overlay in oTargets) {
|
||||
foreach (Overlay overlay in oTargets)
|
||||
{
|
||||
overlay.ScreenTexture = ScreenBufferTexture;
|
||||
}
|
||||
|
||||
oTargets.Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private void DrawEntities(Viewport viewport, Box2 worldBounds)
|
||||
{
|
||||
if (_eyeManager.CurrentMap == MapId.Nullspace || !_mapManager.HasMapEntity(_eyeManager.CurrentMap))
|
||||
@@ -167,7 +208,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return;
|
||||
}
|
||||
|
||||
RenderOverlays(OverlaySpace.WorldSpaceBelowEntities);
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldBounds);
|
||||
|
||||
var screenSize = viewport.Size;
|
||||
|
||||
@@ -218,7 +259,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
flushed = true;
|
||||
}
|
||||
|
||||
overlay.ClydeRender(_renderHandle, OverlaySpace.WorldSpace);
|
||||
overlay.ClydeRender(
|
||||
_renderHandle,
|
||||
OverlaySpace.WorldSpace,
|
||||
null,
|
||||
viewport,
|
||||
new UIBox2i((0, 0), viewport.Size),
|
||||
worldBounds);
|
||||
overlayIndex = j;
|
||||
continue;
|
||||
}
|
||||
@@ -237,13 +284,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var spriteRT = spriteBB.TopRight;
|
||||
|
||||
// finally we can calculate screen bounding in pixels
|
||||
var screenLB = _eyeManager.WorldToScreen(spriteLB);
|
||||
var screenRT = _eyeManager.WorldToScreen(spriteRT);
|
||||
var screenLB = viewport.WorldToLocal(spriteLB);
|
||||
var screenRT = viewport.WorldToLocal(spriteRT);
|
||||
|
||||
// we need to scale RT a for effects like emission or highlight
|
||||
// scale can be passed with PostShader as variable in future
|
||||
var postShadeScale = 1.25f;
|
||||
var screenSpriteSize = (Vector2i)((screenRT - screenLB) * postShadeScale).Rounded();
|
||||
var screenSpriteSize = (Vector2i) ((screenRT - screenLB) * postShadeScale).Rounded();
|
||||
screenSpriteSize.Y = -screenSpriteSize.Y;
|
||||
|
||||
// I'm not 100% sure why it works, but without it post-shader
|
||||
@@ -268,8 +315,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// which is necessary for light application,
|
||||
// but it's ACTUALLY drawing into the center of the render target.
|
||||
var spritePos = spriteBB.Center;
|
||||
var screenPos = _eyeManager.WorldToScreen(spritePos);
|
||||
var (roundedX, roundedY) = roundedPos = (Vector2i)screenPos;
|
||||
var screenPos = viewport.WorldToLocal(spritePos);
|
||||
var (roundedX, roundedY) = roundedPos = (Vector2i) screenPos;
|
||||
var flippedPos = new Vector2i(roundedX, screenSize.Y - roundedY);
|
||||
flippedPos -= entityPostRenderTarget.Size / 2;
|
||||
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));
|
||||
@@ -303,6 +350,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
_renderHandle.SetProjView(oldProj, oldView);
|
||||
_renderHandle.UseShader(null);
|
||||
|
||||
// TODO: cache this properly across frames.
|
||||
entityPostRenderTarget.DisposeDeferred();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,33 +366,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private void ProcessSpriteEntities(MapId map, Box2 worldBounds,
|
||||
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list)
|
||||
{
|
||||
var spriteSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
|
||||
foreach (var comp in _entitySystemManager.GetEntitySystem<RenderingTreeSystem>().GetRenderTrees(map, worldBounds))
|
||||
{
|
||||
Box2 gridBounds;
|
||||
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
gridBounds = worldBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
|
||||
}
|
||||
|
||||
var tree = spriteSystem.GetSpriteTreeForMap(map, gridId);
|
||||
|
||||
tree.QueryAabb(ref list, ((
|
||||
comp.SpriteTree.QueryAabb(ref list, ((
|
||||
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
|
||||
in SpriteComponent value) =>
|
||||
{
|
||||
// TODO: Probably value in storing this as its own DynamicTree
|
||||
if (value.ContainerOccluded || !value.Visible)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var entity = value.Owner;
|
||||
var transform = entity.Transform;
|
||||
|
||||
@@ -354,7 +385,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
entry.yWorldPos = worldPos.Y;
|
||||
return true;
|
||||
|
||||
}), gridBounds, approx: true);
|
||||
}), bounds, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,13 +396,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
handle.DrawingHandleScreen.DrawTexture(texture, (ScreenSize - texture.Size) / 2);
|
||||
}
|
||||
|
||||
private void RenderViewport(Viewport viewport)
|
||||
private void RenderInRenderTarget(RenderTargetBase rt, Action a)
|
||||
{
|
||||
if (viewport.Eye == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: for the love of god all this state pushing/popping needs to be cleaned up.
|
||||
|
||||
var oldTransform = _currentMatrixModel;
|
||||
@@ -380,28 +406,54 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Have to flush the render queue so that all commands finish rendering to the previous framebuffer.
|
||||
FlushRenderQueue();
|
||||
|
||||
var eye = viewport.Eye;
|
||||
|
||||
var oldVp = _currentViewport;
|
||||
_currentViewport = viewport;
|
||||
|
||||
var state = PushRenderStateFull();
|
||||
|
||||
{
|
||||
// Actual code that isn't just pushing/popping renderer state so we can return safely.
|
||||
|
||||
var rt = _currentViewport.RenderTarget;
|
||||
BindRenderTargetFull(RtToLoaded(rt));
|
||||
ClearFramebuffer(default);
|
||||
SetViewportImmediate(Box2i.FromDimensions(Vector2i.Zero, rt.Size));
|
||||
_updateUniformConstants(viewport.Size);
|
||||
_updateUniformConstants(rt.Size);
|
||||
CalcScreenMatrices(rt.Size, out var proj, out var view);
|
||||
SetProjViewFull(proj, view);
|
||||
|
||||
CalcWorldMatrices(rt.Size, eye, out var proj, out var view);
|
||||
// Smugleaf moment
|
||||
a();
|
||||
|
||||
FlushRenderQueue();
|
||||
}
|
||||
|
||||
FenceRenderTarget(rt);
|
||||
|
||||
PopRenderStateFull(state);
|
||||
_updateUniformConstants(_currentRenderTarget.Size);
|
||||
|
||||
SetScissorFull(oldScissor);
|
||||
_currentMatrixModel = oldTransform;
|
||||
}
|
||||
|
||||
private void RenderViewport(Viewport viewport)
|
||||
{
|
||||
if (viewport.Eye == null || viewport.Eye.Position.MapId == MapId.Nullspace)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RenderInRenderTarget(viewport.RenderTarget, () =>
|
||||
{
|
||||
using var _ = DebugGroup($"Viewport: {viewport.Name}");
|
||||
|
||||
var oldVp = _currentViewport;
|
||||
|
||||
_currentViewport = viewport;
|
||||
var eye = viewport.Eye;
|
||||
|
||||
// Actual code that isn't just pushing/popping renderer state so we can return safely.
|
||||
|
||||
CalcWorldMatrices(viewport.RenderTarget.Size, viewport.RenderScale, eye, out var proj, out var view);
|
||||
SetProjViewFull(proj, view);
|
||||
|
||||
// Calculate world-space AABB for camera, to cull off-screen things.
|
||||
var worldBounds = Box2.CenteredAround(eye.Position.Position,
|
||||
_framebufferSize / (float) EyeManager.PixelsPerMeter * eye.Zoom);
|
||||
var worldBounds = CalcWorldBounds(viewport);
|
||||
|
||||
if (_eyeManager.CurrentMap != MapId.Nullspace)
|
||||
{
|
||||
@@ -410,6 +462,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
DrawLightsAndFov(viewport, worldBounds, eye);
|
||||
}
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowWorld, worldBounds);
|
||||
FlushRenderQueue();
|
||||
|
||||
using (DebugGroup("Grids"))
|
||||
{
|
||||
_drawGrids(worldBounds);
|
||||
@@ -421,18 +476,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
DrawEntities(viewport, worldBounds);
|
||||
}
|
||||
|
||||
RenderOverlays(OverlaySpace.WorldSpaceBelowFOV);
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowFOV, worldBounds);
|
||||
|
||||
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawFov)
|
||||
{
|
||||
GL.Clear(ClearBufferMask.StencilBufferBit);
|
||||
GL.Enable(EnableCap.StencilTest);
|
||||
GL.StencilOp(OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Replace);
|
||||
GL.StencilFunc(StencilFunction.Always, 1, 0xFF);
|
||||
GL.StencilMask(0xFF);
|
||||
ApplyFovToBuffer(viewport, eye);
|
||||
GL.StencilMask(0x00);
|
||||
GL.Disable(EnableCap.StencilTest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,7 +496,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// So there are distortions from incorrect projection.
|
||||
_renderHandle.UseShader(_fovDebugShaderInstance);
|
||||
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3.Identity);
|
||||
var pos = UIBox2.FromDimensions(ScreenSize / 2 - (200, 200), (400, 400));
|
||||
var pos = UIBox2.FromDimensions(viewport.Size / 2 - (200, 200), (400, 400));
|
||||
_renderHandle.DrawingHandleScreen.DrawTextureRect(FovTexture, pos);
|
||||
}
|
||||
|
||||
@@ -458,24 +506,25 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3.Identity);
|
||||
_renderHandle.DrawingHandleScreen.DrawTextureRect(
|
||||
viewport.WallBleedIntermediateRenderTarget2.Texture,
|
||||
UIBox2.FromDimensions(Vector2.Zero, ScreenSize), new Color(1, 1, 1, 0.5f));
|
||||
UIBox2.FromDimensions(Vector2.Zero, viewport.Size), new Color(1, 1, 1, 0.5f));
|
||||
}
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldBounds);
|
||||
FlushRenderQueue();
|
||||
|
||||
RenderOverlays(OverlaySpace.WorldSpace);
|
||||
_currentViewport = oldVp;
|
||||
});
|
||||
}
|
||||
|
||||
GL.StencilFunc(StencilFunction.Notequal, 1, 0xFF);
|
||||
GL.Disable(EnableCap.DepthTest);
|
||||
RenderOverlays(OverlaySpace.WorldSpaceFOVStencil);
|
||||
GL.Disable(EnableCap.StencilTest);
|
||||
}
|
||||
private static Box2 CalcWorldBounds(Viewport viewport)
|
||||
{
|
||||
var eye = viewport.Eye;
|
||||
if (eye == null)
|
||||
return default;
|
||||
|
||||
PopRenderStateFull(state);
|
||||
_updateUniformConstants(oldVp?.Size ?? _framebufferSize);
|
||||
|
||||
SetScissorFull(oldScissor);
|
||||
_currentMatrixModel = oldTransform;
|
||||
_currentViewport = oldVp;
|
||||
// TODO: This seems completely unfit by lacking things like rotation handling.
|
||||
return Box2.CenteredAround(eye.Position.Position,
|
||||
viewport.Size / viewport.RenderScale / EyeManager.PixelsPerMeter * eye.Zoom);
|
||||
}
|
||||
|
||||
private sealed class OverlayComparer : IComparer<Overlay>
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, _loadedTextures[target.TextureId].OpenGLObject.Handle);
|
||||
CheckGlError();
|
||||
GL.CopyTexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 0, 0, _framebufferSize.X, _framebufferSize.Y);
|
||||
GL.CopyTexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 0, 0, source.Size.X, source.Size.Y);
|
||||
CheckGlError();
|
||||
|
||||
if (pause && store != null) {
|
||||
@@ -160,12 +160,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void CheckGlErrorInternal(string? path, int line)
|
||||
private void CheckGlErrorInternal(string? path, int line)
|
||||
{
|
||||
var err = GL.GetError();
|
||||
if (err != ErrorCode.NoError)
|
||||
{
|
||||
Logger.ErrorS("clyde.ogl", $"OpenGL error: {err} at {path}:{line}\n{Environment.StackTrace}");
|
||||
_sawmillOgl.Error($"OpenGL error: {err} at {path}:{line}\n{Environment.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void LoadGLProc<T>(string name, out T field) where T : Delegate
|
||||
{
|
||||
var proc = _graphicsContext.GetProcAddress(name);
|
||||
var proc = _windowing!.GraphicsBindingContext.GetProcAddress(name);
|
||||
if (proc == IntPtr.Zero || proc == new IntPtr(1) || proc == new IntPtr(2))
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to load GL function '{name}'!");
|
||||
|
||||
@@ -149,16 +149,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// FOV FBO.
|
||||
_fovRenderTarget = CreateRenderTarget((FovMapSize, 2),
|
||||
new RenderTargetFormatParameters(_hasGLFloatFramebuffers ? RenderTargetColorFormat.RG32F : RenderTargetColorFormat.Rgba8, true),
|
||||
new TextureSampleParameters {WrapMode = TextureWrapMode.Repeat},
|
||||
new TextureSampleParameters { WrapMode = TextureWrapMode.Repeat },
|
||||
nameof(_fovRenderTarget));
|
||||
|
||||
if (_hasGLSamplerObjects)
|
||||
{
|
||||
_fovFilterSampler = new GLHandle(GL.GenSampler());
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMagFilter, (int) All.Linear);
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMinFilter, (int) All.Linear);
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapS, (int) All.Repeat);
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapT, (int) All.Repeat);
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMagFilter, (int)All.Linear);
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureMinFilter, (int)All.Linear);
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapS, (int)All.Repeat);
|
||||
GL.SamplerParameter(_fovFilterSampler.Handle, SamplerParameterName.TextureWrapT, (int)All.Repeat);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
@@ -180,20 +180,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_fovCalculationProgram = _compileProgram(depthVert, depthFrag, attribLocations, "Shadow Depth Program");
|
||||
|
||||
var debugShader = _resourceCache.GetResource<ShaderSourceResource>("/Shaders/Internal/depth-debug.swsl");
|
||||
_fovDebugShaderInstance = (ClydeShaderInstance) InstanceShader(debugShader.ClydeHandle);
|
||||
_fovDebugShaderInstance = (ClydeShaderInstance)InstanceShader(debugShader.ClydeHandle);
|
||||
|
||||
ClydeHandle LoadShaderHandle(string path)
|
||||
{
|
||||
try
|
||||
if (_resourceCache.TryGetResource(path, out ShaderSourceResource? resource))
|
||||
{
|
||||
var shaderSource = _resourceCache.GetResource<ShaderSourceResource>(path);
|
||||
return shaderSource.ClydeHandle;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning($"Can't load shader {path}\n{ex.GetType().Name}: {ex.Message}");
|
||||
return default;
|
||||
return resource.ClydeHandle;
|
||||
}
|
||||
|
||||
Logger.Warning($"Can't load shader {path}\n");
|
||||
return default;
|
||||
}
|
||||
|
||||
_lightSoftShaderHandle = LoadShaderHandle("/Shaders/Internal/light-soft.swsl");
|
||||
@@ -214,7 +211,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// Calculate maximum distance for the projection based on screen size.
|
||||
var screenSizeCut = viewport.Size / EyeManager.PixelsPerMeter;
|
||||
var maxDist = (float) Math.Max(screenSizeCut.X, screenSizeCut.Y);
|
||||
var maxDist = (float)Math.Max(screenSizeCut.X, screenSizeCut.Y);
|
||||
|
||||
// FOV is rendered twice.
|
||||
// Once with back face culling like regular lighting.
|
||||
@@ -497,24 +494,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GetLightsToRender(MapId map, in Box2 worldBounds)
|
||||
{
|
||||
var renderingTreeSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
|
||||
var enlargedBounds = worldBounds.Enlarged(renderingTreeSystem.MaxLightRadius);
|
||||
|
||||
// Use worldbounds for this one as we only care if the light intersects our actual bounds
|
||||
var state = (this, worldBounds, count: 0);
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
|
||||
foreach (var comp in renderingTreeSystem.GetRenderTrees(map, enlargedBounds))
|
||||
{
|
||||
Box2 gridBounds;
|
||||
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
gridBounds = worldBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
|
||||
}
|
||||
|
||||
var lightTree = renderingTreeSystem.GetLightTreeForMap(map, gridId);
|
||||
|
||||
lightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
|
||||
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
|
||||
{
|
||||
var transform = light.Owner.Transform;
|
||||
|
||||
@@ -524,12 +513,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Don't insert into trees for these, same as sprites.
|
||||
if (!light.Enabled || light.ContainerOccluded)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var lightPos = transform.WorldMatrix.Transform(light.Offset);
|
||||
|
||||
var circle = new Circle(lightPos, light.Radius);
|
||||
@@ -544,7 +527,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
|
||||
|
||||
return true;
|
||||
}, gridBounds);
|
||||
}, bounds);
|
||||
}
|
||||
|
||||
if (state.count > _maxLightsPerScene)
|
||||
@@ -590,7 +573,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
SetupGlobalUniformsImmediate(shader, viewport.LightRenderTarget.Texture);
|
||||
|
||||
shader.SetUniformMaybe("size", (Vector2) viewport.WallBleedIntermediateRenderTarget1.Size);
|
||||
shader.SetUniformMaybe("size", (Vector2)viewport.WallBleedIntermediateRenderTarget1.Size);
|
||||
shader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
|
||||
|
||||
var size = viewport.WallBleedIntermediateRenderTarget1.Size;
|
||||
@@ -604,7 +587,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// Have to scale the blurring radius based on viewport size and camera zoom.
|
||||
const float refCameraHeight = 14;
|
||||
var cameraSize = eye.Zoom.Y * viewport.Size.Y / EyeManager.PixelsPerMeter;
|
||||
var cameraSize = eye.Zoom.Y * viewport.Size.Y * (1 / viewport.RenderScale.Y) / EyeManager.PixelsPerMeter;
|
||||
// 7e-3f is just a magic factor that makes it look ok.
|
||||
var factor = 7e-3f * (refCameraHeight / cameraSize);
|
||||
|
||||
@@ -672,6 +655,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void ApplyFovToBuffer(Viewport viewport, IEye eye)
|
||||
{
|
||||
GL.Clear(ClearBufferMask.StencilBufferBit);
|
||||
GL.Enable(EnableCap.StencilTest);
|
||||
GL.StencilOp(OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Replace);
|
||||
GL.StencilFunc(StencilFunction.Always, 1, 0xFF);
|
||||
GL.StencilMask(0xFF);
|
||||
|
||||
// Applies FOV to the final framebuffer.
|
||||
|
||||
var fovShader = _loadedShaders[_fovShaderHandle].Program;
|
||||
@@ -685,6 +674,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
fovShader.SetUniformMaybe("center", eye.Position.Position);
|
||||
|
||||
DrawBlit(viewport, fovShader);
|
||||
|
||||
GL.StencilMask(0x00);
|
||||
GL.Disable(EnableCap.StencilTest);
|
||||
_isStencilling = false;
|
||||
}
|
||||
|
||||
private void ApplyLightingFovToBuffer(Viewport viewport, IEye eye)
|
||||
@@ -799,7 +792,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
occluderTree.QueryAabb((in OccluderComponent sOccluder) =>
|
||||
{
|
||||
var occluder = (ClientOccluderComponent) sOccluder;
|
||||
var occluder = (ClientOccluderComponent)sOccluder;
|
||||
var transform = occluder.Owner.Transform;
|
||||
if (!occluder.Enabled)
|
||||
{
|
||||
@@ -883,11 +876,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// DddD
|
||||
// HHhh
|
||||
// deflection
|
||||
arrayVIBuffer[avi++] = (byte) ((((vi + 1) & 2) != 0) ? 0 : 255);
|
||||
arrayVIBuffer[avi++] = (byte)((((vi + 1) & 2) != 0) ? 0 : 255);
|
||||
// height
|
||||
arrayVIBuffer[avi++] = (byte) (((vi & 2) != 0) ? 0 : 255);
|
||||
arrayVIBuffer[avi++] = (byte)(((vi & 2) != 0) ? 0 : 255);
|
||||
}
|
||||
QuadBatchIndexWrite(indexBuffer, ref ii, (ushort) aiBase);
|
||||
QuadBatchIndexWrite(indexBuffer, ref ii, (ushort)aiBase);
|
||||
}
|
||||
|
||||
// North face (TL/TR)
|
||||
@@ -921,7 +914,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
arrayMaskBuffer[ami + 3] = new Vector2(blX, blY);
|
||||
|
||||
// Generate mask indices.
|
||||
QuadBatchIndexWrite(indexMaskBuffer, ref imi, (ushort) ami);
|
||||
QuadBatchIndexWrite(indexMaskBuffer, ref imi, (ushort)ami);
|
||||
|
||||
ami += 4;
|
||||
|
||||
@@ -963,7 +956,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var lightMapSize = GetLightMapSize(viewport.Size);
|
||||
var lightMapSizeQuart = GetLightMapSize(viewport.Size, true);
|
||||
var lightMapColorFormat = _hasGLFloatFramebuffers ? RenderTargetColorFormat.R11FG11FB10F : RenderTargetColorFormat.Rgba8;
|
||||
var lightMapSampleParameters = new TextureSampleParameters {Filter = true};
|
||||
var lightMapSampleParameters = new TextureSampleParameters { Filter = true };
|
||||
|
||||
viewport.LightRenderTarget?.Dispose();
|
||||
viewport.WallMaskRenderTarget?.Dispose();
|
||||
@@ -1000,25 +993,25 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private Vector2i GetLightMapSize(Vector2i screenSize, bool furtherDivide = false)
|
||||
{
|
||||
var divider = (float) _lightmapDivider;
|
||||
var divider = (float)_lightmapDivider;
|
||||
if (furtherDivide)
|
||||
{
|
||||
divider *= 2;
|
||||
}
|
||||
|
||||
var w = (int) Math.Ceiling(screenSize.X / divider);
|
||||
var h = (int) Math.Ceiling(screenSize.Y / divider);
|
||||
var w = (int)Math.Ceiling(screenSize.X / divider);
|
||||
var h = (int)Math.Ceiling(screenSize.Y / divider);
|
||||
|
||||
return (w, h);
|
||||
}
|
||||
|
||||
protected override void LightmapDividerChanged(int newValue)
|
||||
private void LightmapDividerChanged(int newValue)
|
||||
{
|
||||
_lightmapDivider = newValue;
|
||||
RegenAllLightRts();
|
||||
}
|
||||
|
||||
protected override void MaxLightsPerSceneChanged(int newValue)
|
||||
private void MaxLightsPerSceneChanged(int newValue)
|
||||
{
|
||||
_maxLightsPerScene = newValue;
|
||||
|
||||
@@ -1033,11 +1026,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Shadow FBO.
|
||||
_shadowRenderTarget = CreateRenderTarget((ShadowMapSize, _maxLightsPerScene),
|
||||
new RenderTargetFormatParameters(_hasGLFloatFramebuffers ? RenderTargetColorFormat.RG32F : RenderTargetColorFormat.Rgba8, true),
|
||||
new TextureSampleParameters {WrapMode = TextureWrapMode.Repeat, Filter = true},
|
||||
new TextureSampleParameters { WrapMode = TextureWrapMode.Repeat, Filter = true },
|
||||
nameof(_shadowRenderTarget));
|
||||
}
|
||||
|
||||
protected override void SoftShadowsChanged(bool newValue)
|
||||
private void SoftShadowsChanged(bool newValue)
|
||||
{
|
||||
_enableSoftShadows = newValue;
|
||||
}
|
||||
|
||||
@@ -112,6 +112,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return clydeTexture;
|
||||
}
|
||||
|
||||
public void RenderInRenderTarget(IRenderTarget target, Action a)
|
||||
{
|
||||
_clyde.RenderInRenderTarget((RenderTargetBase) target, a);
|
||||
}
|
||||
|
||||
public void SetScissor(UIBox2i? scissorBox)
|
||||
{
|
||||
_clyde.DrawSetScissor(scissorBox);
|
||||
|
||||
@@ -6,6 +6,7 @@ using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Log;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
// ReSharper disable once IdentifierTypo
|
||||
using RTCF = Robust.Client.Graphics.RenderTargetColorFormat;
|
||||
@@ -23,9 +24,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private readonly ConcurrentQueue<ClydeHandle> _renderTargetDisposeQueue
|
||||
= new();
|
||||
|
||||
IRenderWindow IClyde.MainWindowRenderTarget => _mainWindowRenderTarget;
|
||||
// Initialized in Clyde's constructor
|
||||
private readonly RenderWindow _mainWindowRenderTarget;
|
||||
private readonly RenderMainWindow _mainMainWindowRenderMainTarget;
|
||||
|
||||
// This is always kept up-to-date, except in CreateRenderTarget (because it restores the old value)
|
||||
// It is used for SRGB emulation.
|
||||
@@ -97,7 +97,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
case RTCF.RG32F:
|
||||
case RTCF.R11FG11FB10F:
|
||||
case RTCF.Rgba16F:
|
||||
Logger.WarningS("clyde.ogl", "The framebuffer {0} [{1}] is trying to be floating-point when that's not supported. Forcing Rgba8.", name == null ? "[unnamed]" : name, size);
|
||||
_sawmillOgl.Warning("The framebuffer {0} [{1}] is trying to be floating-point when that's not supported. Forcing Rgba8.", name == null ? "[unnamed]" : name, size);
|
||||
colorFormat = RTCF.Rgba8;
|
||||
break;
|
||||
}
|
||||
@@ -191,7 +191,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
FramebufferHandle = fbo,
|
||||
Size = size,
|
||||
TextureHandle = textureObject.TextureId,
|
||||
MemoryPressure = pressure
|
||||
MemoryPressure = pressure,
|
||||
ColorFormat = format.ColorFormat
|
||||
};
|
||||
|
||||
//GC.AddMemoryPressure(pressure);
|
||||
@@ -266,10 +267,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateWindowLoadedRtSize()
|
||||
private void UpdateMainWindowLoadedRtSize()
|
||||
{
|
||||
var loadedRt = RtToLoaded(_mainWindowRenderTarget);
|
||||
loadedRt.Size = _framebufferSize;
|
||||
var loadedRt = RtToLoaded(_mainMainWindowRenderMainTarget);
|
||||
loadedRt.Size = _windowing!.MainWindow!.FramebufferSize;
|
||||
}
|
||||
|
||||
private sealed class LoadedRenderTarget
|
||||
@@ -278,6 +279,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public Vector2i Size;
|
||||
public bool IsSrgb;
|
||||
|
||||
public RTCF ColorFormat;
|
||||
|
||||
// Remaining properties only apply if the render target is NOT a window.
|
||||
// Handle to the framebuffer object.
|
||||
public GLHandle FramebufferHandle;
|
||||
@@ -295,6 +298,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
protected readonly Clyde Clyde;
|
||||
private bool _disposed;
|
||||
|
||||
public bool MakeGLFence;
|
||||
public nint LastGLSync;
|
||||
|
||||
protected RenderTargetBase(Clyde clyde, ClydeHandle handle)
|
||||
{
|
||||
Clyde = clyde;
|
||||
@@ -302,6 +308,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
public abstract Vector2i Size { get; }
|
||||
|
||||
public void CopyPixelsToMemory<T>(CopyPixelsDelegate<T> callback, UIBox2i? subRegion = null) where T : unmanaged, IPixel<T>
|
||||
{
|
||||
Clyde.CopyRenderTargetPixels(Handle, subRegion, callback);
|
||||
}
|
||||
|
||||
public ClydeHandle Handle { get; }
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
@@ -320,6 +332,23 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void DisposeDeferred()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
DisposeDeferredImpl();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void DisposeDeferredImpl()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~RenderTargetBase()
|
||||
{
|
||||
Dispose(false);
|
||||
@@ -347,16 +376,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
else
|
||||
{
|
||||
Clyde._renderTargetDisposeQueue.Enqueue(Handle);
|
||||
DisposeDeferredImpl();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisposeDeferredImpl()
|
||||
{
|
||||
Clyde._renderTargetDisposeQueue.Enqueue(Handle);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RenderWindow : RenderTargetBase, IRenderWindow
|
||||
private sealed class RenderMainWindow : RenderTargetBase
|
||||
{
|
||||
public override Vector2i Size => Clyde._framebufferSize;
|
||||
public override Vector2i Size => Clyde._windowing!.MainWindow!.FramebufferSize;
|
||||
|
||||
public RenderWindow(Clyde clyde, ClydeHandle handle) : base(clyde, handle)
|
||||
public RenderMainWindow(Clyde clyde, ClydeHandle handle) : base(clyde, handle)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
|
||||
|
||||
@@ -120,9 +117,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
view = Matrix3.Identity;
|
||||
}
|
||||
|
||||
private static void CalcWorldMatrices(in Vector2i screenSize, IEye eye, out Matrix3 proj, out Matrix3 view)
|
||||
private static void CalcWorldMatrices(in Vector2i screenSize, in Vector2 renderScale, IEye eye,
|
||||
out Matrix3 proj, out Matrix3 view)
|
||||
{
|
||||
eye.GetViewMatrix(out view);
|
||||
eye.GetViewMatrix(out view, renderScale);
|
||||
|
||||
CalcWorldProjMatrix(screenSize, out proj);
|
||||
}
|
||||
@@ -266,8 +264,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void _drawQuad(Vector2 a, Vector2 b, in Matrix3 modelMatrix, GLShaderProgram program)
|
||||
{
|
||||
BindVertexArray(QuadVAO.Handle);
|
||||
DrawQuadWithVao(QuadVAO, a, b, modelMatrix, program);
|
||||
}
|
||||
|
||||
private void DrawQuadWithVao(GLHandle vao, Vector2 a, Vector2 b, in Matrix3 modelMatrix,
|
||||
GLShaderProgram program)
|
||||
{
|
||||
BindVertexArray(vao.Handle);
|
||||
CheckGlError();
|
||||
|
||||
var rectTransform = Matrix3.Identity;
|
||||
(rectTransform.R0C0, rectTransform.R1C1) = b - a;
|
||||
(rectTransform.R0C2, rectTransform.R1C2) = a;
|
||||
@@ -283,6 +288,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
/// Flushes the render handle, processing and re-pooling all the command lists.
|
||||
/// </summary>
|
||||
private void FlushRenderQueue()
|
||||
{
|
||||
FlushBatchQueue();
|
||||
|
||||
// Reset renderer state.
|
||||
_currentMatrixModel = Matrix3.Identity;
|
||||
_queuedShader = _defaultShader.Handle;
|
||||
SetScissorFull(null);
|
||||
}
|
||||
|
||||
private void FlushBatchQueue()
|
||||
{
|
||||
// Finish any batches that may have been WiP.
|
||||
BreakBatch();
|
||||
@@ -308,11 +323,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
ProcessRenderCommands();
|
||||
_queuedRenderCommands.Clear();
|
||||
|
||||
// Reset renderer state.
|
||||
_currentMatrixModel = Matrix3.Identity;
|
||||
_queuedShader = _defaultShader.Handle;
|
||||
SetScissorFull(null);
|
||||
}
|
||||
|
||||
private void SetScissorFull(UIBox2i? state)
|
||||
@@ -418,8 +428,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
//It's important to start at Texture6 here since DrawCommandBatch uses Texture0 and Texture1 immediately after calling this
|
||||
//function! If passing in textures as uniforms ever stops working it might be since someone made it use all the way up to Texture6 too.
|
||||
//Might change this in the future?
|
||||
TextureUnit cTarget = TextureUnit.Texture6+textureUnitVal;
|
||||
SetTexture(cTarget, ((ClydeTexture)clydeTexture).TextureId);
|
||||
TextureUnit cTarget = TextureUnit.Texture6 + textureUnitVal;
|
||||
SetTexture(cTarget, ((ClydeTexture) clydeTexture).TextureId);
|
||||
program.SetUniformTexture(name, cTarget);
|
||||
textureUnitVal++;
|
||||
break;
|
||||
@@ -500,6 +510,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private void DrawTexture(ClydeHandle texture, Vector2 bl, Vector2 br, Vector2 tl, Vector2 tr, in Color modulate,
|
||||
in Box2 texCoords)
|
||||
{
|
||||
EnsureBatchSpaceAvailable(4, GetQuadBatchIndexCount());
|
||||
EnsureBatchState(texture, in modulate, true, GetQuadBatchPrimitiveType(), _queuedShader);
|
||||
|
||||
bl = _currentMatrixModel.Transform(bl);
|
||||
@@ -525,6 +536,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
FinishBatch();
|
||||
_batchMetaData = null;
|
||||
|
||||
EnsureBatchSpaceAvailable(vertices.Length, indices.Length);
|
||||
|
||||
vertices.CopyTo(BatchVertexData.AsSpan(BatchVertexIndex));
|
||||
|
||||
// We are weaving this into the batch buffers for performance (and simplicity).
|
||||
@@ -566,6 +579,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
FinishBatch();
|
||||
_batchMetaData = null;
|
||||
|
||||
EnsureBatchSpaceAvailable(vertices.Length, 0);
|
||||
|
||||
vertices.CopyTo(BatchVertexData.AsSpan(BatchVertexIndex));
|
||||
|
||||
ref var command = ref AllocRenderCommand(RenderCommandType.DrawBatch);
|
||||
@@ -601,6 +616,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void DrawLine(Vector2 a, Vector2 b, Color color)
|
||||
{
|
||||
EnsureBatchSpaceAvailable(2, 0);
|
||||
EnsureBatchState(_stockTextureWhite.TextureId, color, false, BatchPrimitiveType.LineList, _queuedShader);
|
||||
|
||||
a = _currentMatrixModel.Transform(a);
|
||||
@@ -615,6 +631,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_debugStats.LastClydeDrawCalls += 1;
|
||||
}
|
||||
|
||||
private void EnsureBatchSpaceAvailable(int vtx, int idx)
|
||||
{
|
||||
if (BatchVertexIndex + vtx >= BatchVertexData.Length || BatchIndexIndex + idx > BatchIndexData.Length)
|
||||
{
|
||||
FlushBatchQueue();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSetScissor(UIBox2i? scissorBox)
|
||||
{
|
||||
BreakBatch();
|
||||
@@ -773,116 +797,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_batchMetaData = null;
|
||||
}
|
||||
|
||||
private unsafe void TakeScreenshot(ScreenshotType type)
|
||||
{
|
||||
if (_queuedScreenshots.Count == 0 || _queuedScreenshots.All(p => p.type != type))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var delegates = _queuedScreenshots.Where(p => p.type == type).ToList();
|
||||
|
||||
_queuedScreenshots.RemoveAll(p => p.type == type);
|
||||
|
||||
GL.PixelStore(PixelStoreParameter.PackAlignment, 1);
|
||||
CheckGlError();
|
||||
|
||||
var bufferLength = ScreenSize.X * ScreenSize.Y;
|
||||
if (!(_hasGLFenceSync && HasGLAnyMapBuffer && _hasGLPixelBufferObjects))
|
||||
{
|
||||
Logger.DebugS("clyde.ogl", "Necessary features for async screenshots not available, falling back to blocking path.");
|
||||
|
||||
// We need these 3 features to be able to do asynchronous screenshots, if we don't have them,
|
||||
// we'll have to fall back to a crappy synchronous stalling method of glReadPixels().
|
||||
|
||||
var buffer = new Rgba32[bufferLength];
|
||||
fixed (Rgba32* ptr = buffer)
|
||||
{
|
||||
var bufSize = sizeof(Rgba32) * bufferLength;
|
||||
GL.ReadnPixels(0, 0, ScreenSize.X, ScreenSize.Y, PixelFormat.Rgba, PixelType.UnsignedByte, bufSize,
|
||||
(IntPtr) ptr);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
var (w, h) = ScreenSize;
|
||||
|
||||
var image = new Image<Rgb24>(w, h);
|
||||
var imageSpan = image.GetPixelSpan();
|
||||
|
||||
FlipCopyScreenshot(buffer, imageSpan, w, h);
|
||||
|
||||
RunCallback(image);
|
||||
return;
|
||||
}
|
||||
|
||||
GL.GenBuffers(1, out uint pbo);
|
||||
CheckGlError();
|
||||
GL.BindBuffer(BufferTarget.PixelPackBuffer, pbo);
|
||||
CheckGlError();
|
||||
GL.BufferData(BufferTarget.PixelPackBuffer, bufferLength * sizeof(Rgba32), IntPtr.Zero,
|
||||
BufferUsageHint.StreamRead);
|
||||
CheckGlError();
|
||||
GL.ReadPixels(0, 0, ScreenSize.X, ScreenSize.Y, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
|
||||
CheckGlError();
|
||||
var fence = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
|
||||
CheckGlError();
|
||||
|
||||
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
|
||||
CheckGlError();
|
||||
|
||||
_transferringScreenshots.Add((pbo, fence, ScreenSize, RunCallback));
|
||||
|
||||
void RunCallback(Image<Rgb24> image) => delegates.ForEach(p => p.callback(image));
|
||||
}
|
||||
|
||||
private unsafe void CheckTransferringScreenshots()
|
||||
{
|
||||
if (_transferringScreenshots.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var screenshot in _transferringScreenshots.ToList())
|
||||
{
|
||||
var (pbo, fence, (width, height), callback) = screenshot;
|
||||
|
||||
int status;
|
||||
GL.GetSync(fence, SyncParameterName.SyncStatus, sizeof(int), null, &status);
|
||||
CheckGlError();
|
||||
|
||||
if (status == (int) All.Signaled)
|
||||
{
|
||||
var bufLen = width * height;
|
||||
var bufSize = sizeof(Rgba32) * bufLen;
|
||||
|
||||
GL.BindBuffer(BufferTarget.PixelPackBuffer, pbo);
|
||||
CheckGlError();
|
||||
var ptr = MapFullBuffer(BufferTarget.PixelPackBuffer, bufSize, BufferAccess.ReadOnly,
|
||||
BufferAccessMask.MapReadBit);
|
||||
|
||||
var packSpan = new ReadOnlySpan<Rgba32>((void*) ptr, width * height);
|
||||
|
||||
var image = new Image<Rgb24>(width, height);
|
||||
var imageSpan = image.GetPixelSpan();
|
||||
|
||||
FlipCopyScreenshot(packSpan, imageSpan, width, height);
|
||||
|
||||
UnmapBuffer(BufferTarget.PixelPackBuffer);
|
||||
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
|
||||
CheckGlError();
|
||||
GL.DeleteBuffer(pbo);
|
||||
CheckGlError();
|
||||
GL.DeleteSync(fence);
|
||||
CheckGlError();
|
||||
|
||||
_transferringScreenshots.Remove(screenshot);
|
||||
|
||||
// TODO: Don't do unnecessary copy here.
|
||||
callback(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private FullStoredRendererState PushRenderStateFull()
|
||||
{
|
||||
return new FullStoredRendererState(_currentMatrixProj, _currentMatrixView, _currentRenderTarget);
|
||||
@@ -913,7 +827,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_lightingReady = false;
|
||||
_currentMatrixModel = Matrix3.Identity;
|
||||
SetScissorFull(null);
|
||||
BindRenderTargetFull(_mainWindowRenderTarget);
|
||||
BindRenderTargetFull(_mainMainWindowRenderMainTarget);
|
||||
_batchMetaData = null;
|
||||
_queuedShader = _defaultShader.Handle;
|
||||
}
|
||||
@@ -927,6 +841,109 @@ namespace Robust.Client.Graphics.Clyde
|
||||
BlendingFactorDest.OneMinusSrcAlpha);
|
||||
}
|
||||
|
||||
private void BlitSecondaryWindows()
|
||||
{
|
||||
// Only got main window.
|
||||
if (_windowing!.AllWindows.Count == 1)
|
||||
return;
|
||||
|
||||
if (!_hasGLFenceSync && _cfg.GetCVar(CVars.DisplayForceSyncWindows))
|
||||
{
|
||||
GL.Finish();
|
||||
}
|
||||
|
||||
if (EffectiveThreadWindowBlit)
|
||||
{
|
||||
foreach (var window in _windowing.AllWindows)
|
||||
{
|
||||
if (window.IsMainWindow)
|
||||
continue;
|
||||
|
||||
window.BlitDoneEvent!.Reset();
|
||||
window.BlitStartEvent!.Set();
|
||||
window.BlitDoneEvent.Wait();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var window in _windowing.AllWindows)
|
||||
{
|
||||
if (window.IsMainWindow)
|
||||
continue;
|
||||
|
||||
_windowing.GLMakeContextCurrent(window);
|
||||
BlitThreadDoSecondaryWindowBlit(window);
|
||||
}
|
||||
|
||||
_windowing.GLMakeContextCurrent(_windowing.MainWindow!);
|
||||
}
|
||||
}
|
||||
|
||||
private void BlitThreadDoSecondaryWindowBlit(WindowReg window)
|
||||
{
|
||||
var rt = window.RenderTexture!;
|
||||
|
||||
if (_hasGLFenceSync)
|
||||
{
|
||||
// 0xFFFFFFFFFFFFFFFFUL is GL_TIMEOUT_IGNORED
|
||||
var sync = rt!.LastGLSync;
|
||||
GL.WaitSync(sync, WaitSyncFlags.None, unchecked((long) 0xFFFFFFFFFFFFFFFFUL));
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
GL.Viewport(0, 0, window.FramebufferSize.X, window.FramebufferSize.Y);
|
||||
CheckGlError();
|
||||
|
||||
SetTexture(TextureUnit.Texture0, window.RenderTexture!.Texture);
|
||||
|
||||
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
|
||||
CheckGlError();
|
||||
|
||||
window.BlitDoneEvent?.Set();
|
||||
_windowing!.WindowSwapBuffers(window);
|
||||
}
|
||||
|
||||
private void BlitThreadInit(WindowReg reg)
|
||||
{
|
||||
_windowing!.GLMakeContextCurrent(reg);
|
||||
_windowing.GLSwapInterval(0);
|
||||
|
||||
if (!_isGLES)
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
|
||||
var vao = GL.GenVertexArray();
|
||||
GL.BindVertexArray(vao);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, WindowVBO.ObjectHandle);
|
||||
// Vertex Coords
|
||||
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 0);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
// Texture Coords.
|
||||
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 2 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(1);
|
||||
|
||||
var program = _compileProgram(_winBlitShaderVert, _winBlitShaderFrag, new (string, uint)[]
|
||||
{
|
||||
("aPos", 0),
|
||||
("tCoord", 1),
|
||||
}, includeLib: false);
|
||||
|
||||
GL.UseProgram(program.Handle);
|
||||
var loc = GL.GetUniformLocation(program.Handle, "tex");
|
||||
SetTexture(TextureUnit.Texture0, reg.RenderTexture!.Texture);
|
||||
GL.Uniform1(loc, 0);
|
||||
}
|
||||
|
||||
private void FenceRenderTarget(RenderTargetBase rt)
|
||||
{
|
||||
if (!_hasGLFenceSync || !rt.MakeGLFence)
|
||||
return;
|
||||
|
||||
if (rt.LastGLSync != 0)
|
||||
GL.DeleteSync(rt.LastGLSync);
|
||||
|
||||
rt.LastGLSync = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct RenderCommand
|
||||
{
|
||||
|
||||
223
Robust.Client/Graphics/Clyde/Clyde.Screenshots.cs
Normal file
223
Robust.Client/Graphics/Clyde/Clyde.Screenshots.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using PF = OpenToolkit.Graphics.OpenGL4.PixelFormat;
|
||||
using PT = OpenToolkit.Graphics.OpenGL4.PixelType;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// Contains primary screenshot and pixel-copying logic.
|
||||
|
||||
internal sealed partial class Clyde
|
||||
{
|
||||
// Full-framebuffer screenshots undergo the following sequence of events:
|
||||
// 1. Screenshots are queued by content or whatever.
|
||||
// 2. When the rendering code reaches the screenshot type,
|
||||
// we instruct the GPU driver to copy the framebuffer and asynchronously transfer it to host memory.
|
||||
// 3. Transfer finished asynchronously, we invoke the callback.
|
||||
//
|
||||
// On RAW GLES2, we cannot do this asynchronously due to lacking GL features,
|
||||
// and the game will stutter as a result. This is sadly unavoidable.
|
||||
//
|
||||
// For CopyPixels on render targets, the copy and transfer is started immediately when the function is called.
|
||||
|
||||
private readonly List<QueuedScreenshot> _queuedScreenshots = new();
|
||||
private readonly List<TransferringPixelCopy> _transferringPixelCopies = new();
|
||||
|
||||
public void Screenshot(ScreenshotType type, CopyPixelsDelegate<Rgb24> callback, UIBox2i? subRegion = null)
|
||||
{
|
||||
_queuedScreenshots.Add(new QueuedScreenshot(type, callback, subRegion));
|
||||
}
|
||||
|
||||
private void TakeScreenshot(ScreenshotType type)
|
||||
{
|
||||
if (_queuedScreenshots.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GL.PixelStore(PixelStoreParameter.PackAlignment, 1);
|
||||
CheckGlError();
|
||||
|
||||
for (var i = 0; i < _queuedScreenshots.Count; i++)
|
||||
{
|
||||
var (qType, callback, subRegion) = _queuedScreenshots[i];
|
||||
if (qType != type)
|
||||
continue;
|
||||
|
||||
DoCopyPixels(ScreenSize, subRegion, callback);
|
||||
_queuedScreenshots.RemoveSwap(i--);
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyRenderTargetPixels<T>(
|
||||
ClydeHandle renderTarget,
|
||||
UIBox2i? subRegion,
|
||||
CopyPixelsDelegate<T> callback)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
var loaded = _renderTargets[renderTarget];
|
||||
|
||||
var original = GL.GetInteger(GetPName.ReadFramebufferBinding);
|
||||
|
||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, loaded.FramebufferHandle.Handle);
|
||||
|
||||
DoCopyPixels(loaded.Size, subRegion, callback);
|
||||
|
||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, original);
|
||||
}
|
||||
|
||||
private unsafe void DoCopyPixels<T>(
|
||||
Vector2i fbSize,
|
||||
UIBox2i? subRegion,
|
||||
CopyPixelsDelegate<T> callback)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
var (pf, pt) = default(T) switch
|
||||
{
|
||||
Rgba32 => (PF.Rgba, PT.UnsignedByte),
|
||||
Rgb24 => (PF.Rgb, PT.UnsignedByte),
|
||||
_ => throw new ArgumentException("Unsupported pixel type.")
|
||||
};
|
||||
|
||||
var size = ClydeBase.ClampSubRegion(fbSize, subRegion);
|
||||
|
||||
var bufferLength = size.X * size.Y;
|
||||
if (!(_hasGLFenceSync && HasGLAnyMapBuffer && _hasGLPixelBufferObjects))
|
||||
{
|
||||
_sawmillOgl.Debug("clyde.ogl",
|
||||
"Necessary features for async screenshots not available, falling back to blocking path.");
|
||||
|
||||
// We need these 3 features to be able to do asynchronous screenshots, if we don't have them,
|
||||
// we'll have to fall back to a crappy synchronous stalling method of glReadnPixels().
|
||||
|
||||
var buffer = new T[bufferLength];
|
||||
fixed (T* ptr = buffer)
|
||||
{
|
||||
var bufSize = sizeof(T) * bufferLength;
|
||||
GL.ReadnPixels(
|
||||
0, 0,
|
||||
size.X, size.Y,
|
||||
pf, pt,
|
||||
bufSize,
|
||||
(nint) ptr);
|
||||
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
var image = new Image<T>(size.X, size.Y);
|
||||
var imageSpan = image.GetPixelSpan();
|
||||
|
||||
FlipCopy(buffer, imageSpan, size.X, size.Y);
|
||||
|
||||
callback(image);
|
||||
return;
|
||||
}
|
||||
|
||||
GL.GenBuffers(1, out uint pbo);
|
||||
CheckGlError();
|
||||
|
||||
GL.BindBuffer(BufferTarget.PixelPackBuffer, pbo);
|
||||
CheckGlError();
|
||||
|
||||
GL.BufferData(
|
||||
BufferTarget.PixelPackBuffer,
|
||||
bufferLength * sizeof(Rgba32), IntPtr.Zero,
|
||||
BufferUsageHint.StreamRead);
|
||||
CheckGlError();
|
||||
|
||||
GL.ReadPixels(0, 0, size.X, size.Y, pf, pt, IntPtr.Zero);
|
||||
CheckGlError();
|
||||
|
||||
var fence = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
|
||||
CheckGlError();
|
||||
|
||||
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
|
||||
CheckGlError();
|
||||
|
||||
var transferring = new TransferringPixelCopy(pbo, fence, size, FinishPixelTransfer<T>, callback);
|
||||
_transferringPixelCopies.Add(transferring);
|
||||
}
|
||||
|
||||
private unsafe void CheckTransferringScreenshots()
|
||||
{
|
||||
if (_transferringPixelCopies.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _transferringPixelCopies.Count; i++)
|
||||
{
|
||||
var transferring = _transferringPixelCopies[i];
|
||||
|
||||
// Check if transfer done (sync signalled)
|
||||
int status;
|
||||
GL.GetSync(transferring.Sync, SyncParameterName.SyncStatus, sizeof(int), null, &status);
|
||||
CheckGlError();
|
||||
|
||||
if (status != (int) All.Signaled)
|
||||
continue;
|
||||
|
||||
transferring.TransferContinue(transferring);
|
||||
_transferringPixelCopies.RemoveSwap(i--);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void FinishPixelTransfer<T>(TransferringPixelCopy transferring) where T : unmanaged, IPixel<T>
|
||||
{
|
||||
var (pbo, fence, (width, height), _, callback) = transferring;
|
||||
|
||||
var bufLen = width * height;
|
||||
var bufSize = sizeof(T) * bufLen;
|
||||
|
||||
GL.BindBuffer(BufferTarget.PixelPackBuffer, pbo);
|
||||
CheckGlError();
|
||||
|
||||
var ptr = MapFullBuffer(
|
||||
BufferTarget.PixelPackBuffer,
|
||||
bufSize,
|
||||
BufferAccess.ReadOnly,
|
||||
BufferAccessMask.MapReadBit);
|
||||
|
||||
var packSpan = new ReadOnlySpan<T>(ptr, width * height);
|
||||
|
||||
var image = new Image<T>(width, height);
|
||||
var imageSpan = image.GetPixelSpan();
|
||||
|
||||
FlipCopy(packSpan, imageSpan, width, height);
|
||||
|
||||
UnmapBuffer(BufferTarget.PixelPackBuffer);
|
||||
|
||||
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
|
||||
CheckGlError();
|
||||
|
||||
GL.DeleteBuffer(pbo);
|
||||
CheckGlError();
|
||||
|
||||
GL.DeleteSync(fence);
|
||||
CheckGlError();
|
||||
|
||||
var castCallback = (CopyPixelsDelegate<T>) callback;
|
||||
castCallback(image);
|
||||
}
|
||||
|
||||
private sealed record QueuedScreenshot(
|
||||
ScreenshotType Type,
|
||||
CopyPixelsDelegate<Rgb24> Callback,
|
||||
UIBox2i? SubRegion);
|
||||
|
||||
private sealed record TransferringPixelCopy(
|
||||
uint Pbo,
|
||||
nint Sync,
|
||||
Vector2i Size,
|
||||
// Funny callback dance to handle the generics.
|
||||
Action<TransferringPixelCopy> TransferContinue,
|
||||
Delegate Callback);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private string _shaderWrapCodeRawFrag = default!;
|
||||
private string _shaderWrapCodeRawVert = default!;
|
||||
|
||||
private string _winBlitShaderVert = default!;
|
||||
private string _winBlitShaderFrag = default!;
|
||||
|
||||
private readonly Dictionary<ClydeHandle, LoadedShader> _loadedShaders =
|
||||
new();
|
||||
|
||||
@@ -117,6 +120,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_shaderWrapCodeRawVert = ReadEmbeddedShader("base-raw.vert");
|
||||
_shaderWrapCodeRawFrag = ReadEmbeddedShader("base-raw.frag");
|
||||
|
||||
_winBlitShaderVert = ReadEmbeddedShader("winblit.vert");
|
||||
_winBlitShaderFrag = ReadEmbeddedShader("winblit.frag");
|
||||
|
||||
var defaultLoadedShader = _resourceCache
|
||||
.GetResource<ShaderSourceResource>("/Shaders/Internal/default-sprite.swsl").ClydeHandle;
|
||||
|
||||
@@ -135,7 +141,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
private GLShaderProgram _compileProgram(string vertexSource, string fragmentSource,
|
||||
(string, uint)[] attribLocations, string? name = null)
|
||||
(string, uint)[] attribLocations, string? name = null, bool includeLib=true)
|
||||
{
|
||||
GLShader? vertexShader = null;
|
||||
GLShader? fragmentShader = null;
|
||||
@@ -172,8 +178,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
versionHeader += "#define HAS_UNIFORM_BUFFERS\n";
|
||||
}
|
||||
|
||||
vertexSource = versionHeader + "#define VERTEX_SHADER\n" + _shaderLibrary + vertexSource;
|
||||
fragmentSource = versionHeader + "#define FRAGMENT_SHADER\n" + _shaderLibrary + fragmentSource;
|
||||
var lib = includeLib ? _shaderLibrary : "";
|
||||
vertexSource = versionHeader + "#define VERTEX_SHADER\n" + lib + vertexSource;
|
||||
fragmentSource = versionHeader + "#define FRAGMENT_SHADER\n" + lib + fragmentSource;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
|
||||
TextureLoadParameters? loadParams = null)
|
||||
{
|
||||
DebugTools.Assert(_mainThread == Thread.CurrentThread);
|
||||
DebugTools.Assert(_gameThread == Thread.CurrentThread);
|
||||
|
||||
// Load using Rgba32.
|
||||
using var image = Image.Load<Rgba32>(stream);
|
||||
@@ -40,7 +40,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public OwnedTexture LoadTextureFromImage<T>(Image<T> image, string? name = null,
|
||||
TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel<T>
|
||||
{
|
||||
DebugTools.Assert(_mainThread == Thread.CurrentThread);
|
||||
DebugTools.Assert(_gameThread == Thread.CurrentThread);
|
||||
|
||||
var actualParams = loadParams ?? TextureLoadParameters.Default;
|
||||
var pixelType = typeof(T);
|
||||
@@ -452,24 +452,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private static void FlipCopyScreenshot(ReadOnlySpan<Rgba32> srcSpan, Span<Rgb24> dstSpan, int w, int h)
|
||||
{
|
||||
var dr = h - 1;
|
||||
for (var r = 0; r < h; r++, dr--)
|
||||
{
|
||||
var si = r * w;
|
||||
var di = dr * w;
|
||||
var srcRow = srcSpan[si..(si + w)];
|
||||
var dstRow = dstSpan[di..(di + w)];
|
||||
|
||||
for (var x = 0; x < w; x++)
|
||||
{
|
||||
var src = srcRow[x];
|
||||
dstRow[x] = new Rgb24(src.R, src.G, src.B);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Image<Rgba32> ApplyA8Swizzle(Image<A8> source)
|
||||
{
|
||||
var newImage = new Image<Rgba32>(source.Width, source.Height);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
@@ -10,7 +13,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private readonly Dictionary<ClydeHandle, WeakReference<Viewport>> _viewports =
|
||||
new();
|
||||
|
||||
private Viewport CreateViewport(Vector2i size, string? name = null)
|
||||
private Viewport CreateViewport(Vector2i size, TextureSampleParameters? sampleParameters = default, string? name = null)
|
||||
{
|
||||
var handle = AllocRid();
|
||||
var viewport = new Viewport(handle, name, this)
|
||||
@@ -18,6 +21,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Size = size,
|
||||
RenderTarget = CreateRenderTarget(size,
|
||||
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb, true),
|
||||
sampleParameters: sampleParameters,
|
||||
name: $"{name}-MainRenderTarget")
|
||||
};
|
||||
|
||||
@@ -28,9 +32,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return viewport;
|
||||
}
|
||||
|
||||
IClydeViewport IClyde.CreateViewport(Vector2i size, string? name)
|
||||
IClydeViewport IClyde.CreateViewport(Vector2i size, TextureSampleParameters? sampleParameters, string? name)
|
||||
{
|
||||
return CreateViewport(size, name);
|
||||
return CreateViewport(size, sampleParameters, name);
|
||||
}
|
||||
|
||||
private static Vector2 ScreenToMap(Vector2 point, Viewport vp)
|
||||
@@ -45,7 +49,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
point *= new Vector2(1, -1) / EyeManager.PixelsPerMeter;
|
||||
|
||||
// view matrix
|
||||
vp.Eye.GetViewMatrixInv(out var viewMatrixInv);
|
||||
vp.Eye.GetViewMatrixInv(out var viewMatrixInv, vp.RenderScale);
|
||||
point = viewMatrixInv * point;
|
||||
|
||||
return point;
|
||||
@@ -106,12 +110,66 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
public Vector2i Size { get; set; }
|
||||
public Vector2 RenderScale { get; set; } = Vector2.One;
|
||||
public bool AutomaticRender { get; set; }
|
||||
|
||||
void IClydeViewport.Render()
|
||||
{
|
||||
_clyde.RenderViewport(this);
|
||||
}
|
||||
|
||||
public MapCoordinates LocalToWorld(Vector2 point)
|
||||
{
|
||||
if (Eye == null)
|
||||
return default;
|
||||
|
||||
var newPoint = point;
|
||||
|
||||
// (inlined version of UiProjMatrix^-1)
|
||||
newPoint -= Size / 2f;
|
||||
newPoint *= new Vector2(1, -1) / EyeManager.PixelsPerMeter;
|
||||
|
||||
// view matrix
|
||||
Eye.GetViewMatrixInv(out var viewMatrixInv, RenderScale);
|
||||
newPoint = viewMatrixInv * newPoint;
|
||||
|
||||
return new MapCoordinates(newPoint, Eye.Position.MapId);
|
||||
}
|
||||
|
||||
public Vector2 WorldToLocal(Vector2 point)
|
||||
{
|
||||
if (Eye == null)
|
||||
return default;
|
||||
|
||||
var eye = (IEye) Eye;
|
||||
var newPoint = point;
|
||||
|
||||
eye.GetViewMatrix(out var viewMatrix, RenderScale);
|
||||
newPoint = viewMatrix * newPoint;
|
||||
|
||||
// (inlined version of UiProjMatrix)
|
||||
newPoint *= new Vector2(1, -1) * EyeManager.PixelsPerMeter;
|
||||
newPoint += Size / 2f;
|
||||
|
||||
return newPoint;
|
||||
}
|
||||
|
||||
public void RenderScreenOverlaysBelow(
|
||||
DrawingHandleScreen handle,
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds)
|
||||
{
|
||||
_clyde.RenderOverlaysDirect(this, control, handle, OverlaySpace.ScreenSpaceBelowWorld, viewportBounds);
|
||||
}
|
||||
|
||||
public void RenderScreenOverlaysAbove(
|
||||
DrawingHandleScreen handle,
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds)
|
||||
{
|
||||
_clyde.RenderOverlaysDirect(this, control, handle, OverlaySpace.ScreenSpace, viewportBounds);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
RenderTarget.Dispose();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user