mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
269 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2ee9a43f9 | ||
|
|
8d5ebd830a | ||
|
|
4d265b2210 | ||
|
|
e04caf7eb4 | ||
|
|
a4c54d3602 | ||
|
|
43fd6bc764 | ||
|
|
ff056552fe | ||
|
|
3014d9880e | ||
|
|
679c31199d | ||
|
|
0bc3c51707 | ||
|
|
140767c262 | ||
|
|
36a5b672e5 | ||
|
|
1eb63cb616 | ||
|
|
c14689f233 | ||
|
|
f03c006129 | ||
|
|
0d53c5e329 | ||
|
|
5cb1901870 | ||
|
|
d46885b96d | ||
|
|
0553600c9a | ||
|
|
580dd5f1a6 | ||
|
|
d47d488ce7 | ||
|
|
d584e51de6 | ||
|
|
2f85b94ea2 | ||
|
|
046f7a2e55 | ||
|
|
5f2881e3e4 | ||
|
|
cdb94748c8 | ||
|
|
53516d6389 | ||
|
|
efa3e010a6 | ||
|
|
4b12ff8574 | ||
|
|
59ed76c66f | ||
|
|
9781405f5e | ||
|
|
2178707937 | ||
|
|
0284eb0430 | ||
|
|
2c3cc070a6 | ||
|
|
6599f9565e | ||
|
|
85abcff5ea | ||
|
|
5b5894e2d5 | ||
|
|
7d778248ee | ||
|
|
672819d525 | ||
|
|
99e4910440 | ||
|
|
b503390837 | ||
|
|
87725f27c3 | ||
|
|
49c831b48d | ||
|
|
60a29933d8 | ||
|
|
5729e8eb19 | ||
|
|
42da4b1287 | ||
|
|
3342e1272f | ||
|
|
5c0ce43e6c | ||
|
|
0717b1fced | ||
|
|
68c03196e6 | ||
|
|
31292fe4b8 | ||
|
|
865348550f | ||
|
|
7372233782 | ||
|
|
7ebfc82dd6 | ||
|
|
807e7e888a | ||
|
|
39fefcb9c8 | ||
|
|
b6548c870c | ||
|
|
cf230b3454 | ||
|
|
16a93e86f6 | ||
|
|
2e4275a7f3 | ||
|
|
176ca6c578 | ||
|
|
2664061993 | ||
|
|
033699d7d6 | ||
|
|
f696edaa0c | ||
|
|
4920ecaa64 | ||
|
|
b8924a04cf | ||
|
|
be11cb4bca | ||
|
|
eafe395273 | ||
|
|
05cdb99252 | ||
|
|
d4c6b4a828 | ||
|
|
fc1cca4f48 | ||
|
|
3657b0a424 | ||
|
|
c3d8080a8e | ||
|
|
8e50924607 | ||
|
|
7fdd5c9d1c | ||
|
|
7fbcfeaa8f | ||
|
|
b82bc258db | ||
|
|
7ad2925f2c | ||
|
|
4091ad4837 | ||
|
|
35881d7a6a | ||
|
|
2d28ac35d8 | ||
|
|
8b5ad938d5 | ||
|
|
723f936a33 | ||
|
|
2636879860 | ||
|
|
dad1da507c | ||
|
|
145c190800 | ||
|
|
b7cc0ec629 | ||
|
|
ad329a6b58 | ||
|
|
4deba4b866 | ||
|
|
4c31083186 | ||
|
|
d31e7ccb55 | ||
|
|
a9aea7027f | ||
|
|
2a49c2d9b8 | ||
|
|
a0c069f1ea | ||
|
|
2c6fb95e53 | ||
|
|
afe337644e | ||
|
|
b8924f3ddf | ||
|
|
08970e745b | ||
|
|
0ba4a66787 | ||
|
|
75b3431ee6 | ||
|
|
c0ef976588 | ||
|
|
fe5cdf9e3c | ||
|
|
450349188b | ||
|
|
897ad998d9 | ||
|
|
635ae3c353 | ||
|
|
a4ea5a4620 | ||
|
|
90e87526d0 | ||
|
|
cd6576ddf9 | ||
|
|
e2cf4ee3db | ||
|
|
860c9af2bf | ||
|
|
87bb29408a | ||
|
|
738cfbe992 | ||
|
|
90edc02259 | ||
|
|
da5416a2da | ||
|
|
021845d956 | ||
|
|
7fab9f3b8d | ||
|
|
69c1161562 | ||
|
|
095fe9d60f | ||
|
|
14138fbcc2 | ||
|
|
48ce24e98b | ||
|
|
9cde21a7b3 | ||
|
|
ae1051e813 | ||
|
|
a3f80ac7dd | ||
|
|
f98ef78a21 | ||
|
|
bf8054b181 | ||
|
|
6b875e6676 | ||
|
|
a687c0a6c0 | ||
|
|
0580cf3ff7 | ||
|
|
590964d5bf | ||
|
|
ceda39813d | ||
|
|
a3a8912f42 | ||
|
|
b40973157d | ||
|
|
1de8731465 | ||
|
|
3a479cb5f4 | ||
|
|
76eeebf439 | ||
|
|
2fa83181e2 | ||
|
|
36f02b4a18 | ||
|
|
e842142dd7 | ||
|
|
2eb740cea8 | ||
|
|
a044f04e3b | ||
|
|
a4723d1f62 | ||
|
|
627c1eb054 | ||
|
|
836aec0b87 | ||
|
|
9116e64291 | ||
|
|
a6bfb5f557 | ||
|
|
5c83678c78 | ||
|
|
eac94b1032 | ||
|
|
efd870d070 | ||
|
|
94f98073b0 | ||
|
|
5aa9378de0 | ||
|
|
850e9ab695 | ||
|
|
7319f3a241 | ||
|
|
b6252c9e4f | ||
|
|
fcd507d1f9 | ||
|
|
1eb874f4c3 | ||
|
|
a628d31c4b | ||
|
|
2b0ecd7166 | ||
|
|
bde650689b | ||
|
|
87d8d74d8c | ||
|
|
dddf13a19a | ||
|
|
75626a86a3 | ||
|
|
3e3cd0e257 | ||
|
|
a3a90154a4 | ||
|
|
9240c94e59 | ||
|
|
a95ba9f181 | ||
|
|
074a4faa92 | ||
|
|
6b4d74f46e | ||
|
|
f648218756 | ||
|
|
b497efb0c0 | ||
|
|
d8f2b917b4 | ||
|
|
6dd6b79db6 | ||
|
|
ff4548f108 | ||
|
|
8f41405b31 | ||
|
|
f285c62674 | ||
|
|
56c30edf04 | ||
|
|
783d529ec4 | ||
|
|
895bfb8ec0 | ||
|
|
6ef67cf513 | ||
|
|
15a2f6702c | ||
|
|
c5c2c2022a | ||
|
|
3c378640dd | ||
|
|
0a149fa91c | ||
|
|
fe8d1d9422 | ||
|
|
c89c529ba4 | ||
|
|
dd56de70b7 | ||
|
|
710408c613 | ||
|
|
2461cd94dd | ||
|
|
721408bb37 | ||
|
|
8b42c1dd46 | ||
|
|
688b0b0458 | ||
|
|
5fa49b5689 | ||
|
|
1f7a9bdf0a | ||
|
|
796abe1230 | ||
|
|
6c2cf26250 | ||
|
|
ce262d5ff8 | ||
|
|
c250010dad | ||
|
|
709c7bc808 | ||
|
|
37918da73c | ||
|
|
6cc2083b09 | ||
|
|
6a6bfe33ca | ||
|
|
9737a4249c | ||
|
|
a48a353939 | ||
|
|
f0b45d95cb | ||
|
|
d69c5500f2 | ||
|
|
cf133ca341 | ||
|
|
b0922b8e0e | ||
|
|
512ebd8422 | ||
|
|
85f74c3ba3 | ||
|
|
da7abc6580 | ||
|
|
b1329d30bf | ||
|
|
12808d073e | ||
|
|
ec794ce4e4 | ||
|
|
6b13475842 | ||
|
|
b48ee22800 | ||
|
|
0b95a4edeb | ||
|
|
ed359481b4 | ||
|
|
1189613908 | ||
|
|
30907d8415 | ||
|
|
7f2da4d4f3 | ||
|
|
e30e963623 | ||
|
|
b056caeed7 | ||
|
|
fbc8086335 | ||
|
|
799702b814 | ||
|
|
63df90f86f | ||
|
|
51f0c60bd3 | ||
|
|
a9ed53f47b | ||
|
|
41c40f1a94 | ||
|
|
6e61c35d35 | ||
|
|
aae0a8bc51 | ||
|
|
cb543240c6 | ||
|
|
1654ab06f5 | ||
|
|
211245215e | ||
|
|
10aaaa65c5 | ||
|
|
d2a2afe82e | ||
|
|
025d90d281 | ||
|
|
c229f2e312 | ||
|
|
fe051a3577 | ||
|
|
51a0ef1e60 | ||
|
|
702dfef5fc | ||
|
|
a0c1ad246f | ||
|
|
1153888bd1 | ||
|
|
ccbb6ddec7 | ||
|
|
970da5f717 | ||
|
|
4d528dd577 | ||
|
|
c83720b163 | ||
|
|
bd87a805d4 | ||
|
|
fff42fb2b4 | ||
|
|
4500669f65 | ||
|
|
7d19ea9338 | ||
|
|
2dc610907d | ||
|
|
beb1c4b1fb | ||
|
|
7e331eaa75 | ||
|
|
caf9e45ad9 | ||
|
|
7cb3aeccc2 | ||
|
|
ae83e606d6 | ||
|
|
d9d5ef7471 | ||
|
|
0f97f366a6 | ||
|
|
35ab0b8cc8 | ||
|
|
5a14e939bf | ||
|
|
ccba6b5d1c | ||
|
|
254a5987c7 | ||
|
|
8550056e68 | ||
|
|
25211e3781 | ||
|
|
3500abfd47 | ||
|
|
7d1915096a | ||
|
|
4504731588 | ||
|
|
701fa95a82 | ||
|
|
40a9048704 | ||
|
|
cee8d42776 |
@@ -7,6 +7,18 @@ indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
|
||||
max_line_length = 120
|
||||
|
||||
# ReSharper properties
|
||||
resharper_csharp_max_line_length = 120
|
||||
resharper_csharp_wrap_after_declaration_lpar = true
|
||||
resharper_csharp_wrap_arguments_style = chop_if_long
|
||||
resharper_csharp_wrap_parameters_style = chop_if_long
|
||||
resharper_keep_existing_attribute_arrangement = true
|
||||
resharper_place_field_attribute_on_same_line = if_owner_is_single_line
|
||||
resharper_wrap_chained_binary_patterns = chop_if_long
|
||||
resharper_wrap_chained_method_calls = chop_if_long
|
||||
|
||||
[*.{csproj,xml,yml,dll.config,targets,props}]
|
||||
indent_size = 2
|
||||
|
||||
|
||||
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, macos-latest]
|
||||
os: [ubuntu-latest, windows-latest ] # , macos-latest] - temporarily disabled due to libfreetype.dll errors.
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
<PackageVersion Include="ILReader.Core" Version="1.0.0.4" />
|
||||
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
|
||||
<PackageVersion Include="JetBrains.Profiler.Api" Version="1.4.0" />
|
||||
<PackageVersion Include="Linguini.Bundle" Version="0.1.3" />
|
||||
<PackageVersion Include="Linguini.Bundle" Version="0.8.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1"/>
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit" Version="1.1.1"/>
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit" Version="1.1.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit" Version="1.1.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.8.0" />
|
||||
@@ -43,7 +44,7 @@
|
||||
<PackageVersion Include="NUnit.Analyzers" Version="3.10.0" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||
<PackageVersion Include="Nett" Version="0.15.0" />
|
||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
|
||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
|
||||
<PackageVersion Include="Pidgin" Version="3.2.2" />
|
||||
@@ -56,7 +57,7 @@
|
||||
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: 61a56c60bd...1d85b82e05
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
521
RELEASE-NOTES.md
521
RELEASE-NOTES.md
@@ -54,10 +54,525 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 220.2.2
|
||||
## 231.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* ViewSubscriber has been moved to shared; it doesn't actually do anything on the client but makes shared code easier.
|
||||
|
||||
### New features
|
||||
|
||||
* ContactEnumreator exists to iterate the contacts of a particular entity.
|
||||
* Add FixturesChangeComponent as a generic way to add and remove fixtures easily.
|
||||
* PointLightComponent enabling / disabling now has an attempt event if you wish to block it on content side.
|
||||
* There's an OpenScreenAt overload for screen-relative coordinates.
|
||||
* SpriteSystem has methods to get an entity's position in sprite terms.
|
||||
* EntityManager and ComponentFactory now have additional methods that interact with ComponentRegistry and ComponentRegistryEntry.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix PrototypeFlags Add not actually working.
|
||||
* Fix BUIs going BRRT opening and closing repeatedly upon prediction. The closing now gets deferred to the update loop if it's still closed at the end of prediction.
|
||||
|
||||
|
||||
## 220.2.1
|
||||
## 230.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add ProcessNow for IRobustJob as a convenience method where you may not want to run a job in the background sometimes.
|
||||
* Add Vector2i helpers to all 8 neighbouring directions.
|
||||
|
||||
### Other
|
||||
|
||||
* Remove IThreadPoolWorkItem interface from IRobustJob.
|
||||
|
||||
|
||||
## 230.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* You can now pass `bool[]` parameters to shaders.
|
||||
* Added `toolshed.nearby_entities_limit` CVar.
|
||||
* Fix `RichTextLabel.Text` to clear and reset the message properly in all cases.
|
||||
* `scene` command has tab completion now.
|
||||
* `devwindow` UI inspector property catches exceptions for read properties.
|
||||
* `SplitContainer.Flip()`
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix tile enlargement not being applied for some EntityLookup queries.
|
||||
* `LocalizedEntityCommands` are now not initialized inside `RobustUnitTest`, fixing guaranteed test failures.
|
||||
* Fixed issues with broadphase init breaking replays frequently.
|
||||
* Fix uploaded prototypes and resources for clients connecting to a server.
|
||||
|
||||
### Other
|
||||
|
||||
* Improved error reporting for DataField analyzer.
|
||||
|
||||
|
||||
## 230.0.1
|
||||
|
||||
|
||||
## 230.0.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added `InterpolatedStringHandlerArgumentAttribute` to the sandbox whitelist.
|
||||
* `IUserInterfaceManager.Popup()` popups now have a copy to clipboard button.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Security fixes
|
||||
* Fix exception in `TimedDespawnComponent` spawning another `TimedDespawnComponent`.
|
||||
* Fixed pool memory leak in physics `SolveIsland`.
|
||||
|
||||
|
||||
## 229.1.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed a bug where the client might not add entities to the broadphase/lookup components.
|
||||
|
||||
|
||||
## 229.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix some teleportation commands not working in singleplayer or replays
|
||||
|
||||
### Other
|
||||
|
||||
* Audio entity names now include the filepath of the audio being played if relevant for debugging.
|
||||
|
||||
|
||||
## 229.1.0
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix multithreading bug in ParallelTracker that caused the game to crash randomly.
|
||||
* Fixed IPv6-only hosts not working properly with built-in HTTP clients.
|
||||
|
||||
### Other
|
||||
|
||||
* Added obsoletion warning for `Control.Dispose()`. New code should not rely on it.
|
||||
* Reduced the default tickrate to 30 ticks.
|
||||
* Encryption of network messages is now done concurrently to avoid spending main thread time. In profiles, this added up to ~8% of main thread time on RMC-14.
|
||||
|
||||
|
||||
## 229.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Fixes large entities causing entity spawn menu to break.
|
||||
* Made PhysicsHull an internal ref struct for some PolygonShape speedup.
|
||||
|
||||
### New features
|
||||
|
||||
* Audio ticks-per-second is now capped at 30 by default and controlled via `audio.tick_rate` cvar.
|
||||
* Add CreateWindow and CreateDisposableControl helpers for BUIs.
|
||||
* Add OnProtoReload virtual method to BUIs that gets called on prototype reloads.
|
||||
* Add RemoveData to AppearanceSystem data.
|
||||
|
||||
|
||||
## 228.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The `Color` struct's equality methods now check for exact equality. Use `MathHelper.CloseToPercent(Color, Color)` for the previous functionality.
|
||||
* Added a toolshed.nearby_limit cvar to limit the maximum range of the nearby command. Defaults to 200.
|
||||
|
||||
### New features
|
||||
|
||||
* Added command usage with types to Toolshed command help.
|
||||
* Add Text property to RichTextLabel.
|
||||
* Whitelist System.Net.IPEndPoint.
|
||||
* Add event for mass & angular inertia changes.
|
||||
* Add SpriteSystem.IsVisible for layers.
|
||||
* Add TryQueueDeleteEntity that checks if the entity is already deleted / queuedeleted first.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Clients connecting to a server now always load prototype uploads after resource uploads, fixing ordering bugs that could cause various errors.
|
||||
|
||||
|
||||
## 227.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Add a `loop` arg to SpriteSystem.GetFrame in case you don't want to get a looping animation.
|
||||
* Remove obsolete VisibileSystem methods.
|
||||
|
||||
### New features
|
||||
|
||||
* Added `LocalizedEntityCommands`, which are console commands that have the ability to take entity system dependencies.
|
||||
* Added `BeginRegistrationRegion` to `IConsoleHost` to allow efficient bulk-registration of console commands.
|
||||
* Added `IConsoleHost.RegisterCommand` overload that takes an `IConsoleCommand`.
|
||||
* Added a `Finished` boolean to `AnimationCompletedEvent` which allows distinguishing if an animation was removed prematurely or completed naturally.
|
||||
* Add GetLocalTilesIntersecting for MapSystem.
|
||||
* Add an analyzer for methods that should call the base implementation and use it for EntitySystems.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix loading replays if string package is compressed inside a zip.
|
||||
|
||||
### Other
|
||||
|
||||
* Tab completions containing spaces are now properly quoted, so the command will actually work properly once entered.
|
||||
* Mark EntityCoordinates.Offset as Pure so it shows as warnings if the variable is unused.
|
||||
* Networked events will always be processed in order even if late.
|
||||
|
||||
|
||||
## 226.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* `System.Collections.IList` and `System.Collections.ICollection` are now sandbox safe, this fixes some collection expression cases.
|
||||
* The sandboxing system will now report the methods responsible for references to illegal items.
|
||||
|
||||
|
||||
## 226.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* `Control.VisibilityChanged()` virtual function.
|
||||
* Add some System.Random methods for NextFloat and NextPolarVector2.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixes ContainerSystem failing client-side debug asserts when an entity gets unanchored & inserted into a container on the same tick.
|
||||
* Remove potential race condition on server startup from invoking ThreadPool.SetMinThreads.
|
||||
|
||||
### Other
|
||||
|
||||
* Increase default value of res.rsi_atlas_size.
|
||||
* Fix internal networking logic.
|
||||
* Updates of `OutputPanel` contents caused by change in UI scale are now deferred until visible. Especially important to avoid updates from debug console.
|
||||
* Debug console is now limited to only keep `con.max_entries` entries.
|
||||
* Non-existent resources are cached by `IResourceCache.TryGetResource`. This avoids the game constantly trying to re-load non-existent resources in common patterns such as UI theme texture fallbacks.
|
||||
* Default IPv4 MTU has been lowered to 700.
|
||||
* Update Robust.LoaderApi.
|
||||
|
||||
### Internal
|
||||
|
||||
* Split out PVS serialization from compression and sending game states.
|
||||
* Turn broadphase contacts into an IParallelRobustJob and remove unnecessary GetMapEntityIds for every contact.
|
||||
|
||||
|
||||
## 226.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add some GetLocalEntitiesIntersecting methods for `Entity<T>`.
|
||||
|
||||
### Other
|
||||
|
||||
* Fix internal networking logic
|
||||
|
||||
|
||||
## 226.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `IEventBus.RaiseComponentEvent` now requires an EntityUid argument.
|
||||
* The `AddedComponentEventArgs` and `RemovedComponentEventArgs` constructors are now internal
|
||||
|
||||
### New features
|
||||
|
||||
* Allow RequestScreenTexture to be set in overlays.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix AnimationCompletedEvent not always going out.
|
||||
|
||||
|
||||
## 225.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `NetEntity.Parse` and `TryParse` will now fail to parse empty strings.
|
||||
* Try to prevent EventBus looping. This also caps the amount of directed component subscriptions for a particular component to 256.
|
||||
|
||||
### New features
|
||||
|
||||
* `IPrototypeManager.TryIndex` will now default to logging errors if passed an invalid prototype id struct (i,e., `EntProtoId` or `ProtoId<T>`). There is a new optional bool argument to disable logging errors.
|
||||
* `Eye` now allows its `Position` to be set directly. Please only do this with the `FixedEye` child type constructed manually.
|
||||
* Engine now respects the hub's `can_skip_build` parameter on info query, fixing an issue where the first hub advertisement fails due to ACZ taking too long.
|
||||
* Add GetSession & TryGetSession to ActorSystem.
|
||||
* Raise an event when an entity's name is changed.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* The `ent` toolshed command now takes `NetEntity` values, fixing parsing in practical uses.
|
||||
* Fix ComponentFactory test mocks.
|
||||
* Fix LookupFlags missing from a couple of EntityLookupSystem methods.
|
||||
|
||||
### Other
|
||||
|
||||
* Improved engine's Happy Eyeballs implementation, should result in more usage of IPv6 for HTTP APIs when available.
|
||||
* Remove CompIdx locks to improve performance inside Pvs at higher player counts.
|
||||
* Avoid a read lock in GetEntityQuery to also improve performance.
|
||||
* Mark `EntityManager.System<T>` as Pure.
|
||||
|
||||
|
||||
## 224.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed UserInterfaceSystem sometimes throwing a key-not-found exception when trying to close UIs.
|
||||
|
||||
|
||||
## 224.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* `ServerIntegrationInstance` has new methods for adding dummy player sessions for tests that require multiple players.
|
||||
* Linguini has been updated to v0.8.1. Errors will now be logged when a duplicate localization key is found.
|
||||
* Added `UserInterfaceSystem.SetUi()` for modifying the `InterfaceData` associated with some BUI.
|
||||
* Added the `EntityPrototypeView` control for spawning & rendering an entity prototype.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `UserInterfaceSystem` spamming client side errors when entities with UIs open are deleted while outside of PVS range.
|
||||
* Fix Toolshed's EnumTypeParse not working enum values with upercase characters.
|
||||
* Fixed `incmd` command not working due to an invalid cast.
|
||||
|
||||
### Other
|
||||
|
||||
* There have been various performance improvements to replay loading & playback.
|
||||
|
||||
### Internal
|
||||
|
||||
* Added `DummySession` and `DummyChannel` classes for use in integration tests and benchmarks to fool the server into thinking that there are multiple players connected.
|
||||
* Added `ICommonSessionInternal` and updated `CommonSession` so that the internal setters now go through that interface.
|
||||
|
||||
## 224.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixes PVS throwing exceptions when invalid entities are passed to `ExpandPvsEvent`. Now it just logs an error.
|
||||
* Fixes BUIs not properly closing, resulting in invalid entities in `UserInterfaceUserComponent.OpenInterfaces`
|
||||
* Fixes an unknown/invalid prototype exception sometimes being thrown when running ``IPrototypeManager.ResolveResults()`
|
||||
|
||||
|
||||
## 224.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `Matrix3` has been replaced with `System.Numerics.Matrix3x2`. Various Matrix related methods have been turned into extension methods in the `Matrix3Helpers` class.
|
||||
* Engine `EntityCategory` prototype IDs have been changed to use CamelCase. I.e., `hideSpawnMenu` -> `HideSpawnMenu`
|
||||
* Prototypes can now be implicitly cast `ProtoId<T>` or `EntProtoId` ID structs. The new implicit cast might cause previous function calls to be ambiguous.
|
||||
|
||||
### New features
|
||||
|
||||
* `Array.Clear(Array)` is now available in the sandbox.
|
||||
* BUIs now use `ExpandPvsEvent`. I.e., if a player has a UI open, then the entity associated with that UI will always get sent to the player by the PVS system.
|
||||
* Added `cvar_subs` command for listing all subscribers to cvar changes
|
||||
* Entity categories have been reworked
|
||||
* Each category now has a `HideSpawnMenu` field. The old `HideSpawnMenu` category is now just a normal category with that field set to true.
|
||||
* Reworked category inheritance. Inheritance can now be disabled per category using a `Inheritable` field.
|
||||
* Entity prototypes can now be automatically added to categories based on the components that they have, either by specifying components when defining the category in yml, or by adding the EntityCategoryAttribute to the component class.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed client-side BUI error log spam if an unknown entity has a UI open.
|
||||
* Fixed placement manager spawning entities with incorrect rotations.
|
||||
|
||||
### Other
|
||||
|
||||
* Added a try-catch block to BUI constructors, to avoid clients getting stuck in error loops while applying states.
|
||||
* Attempting to play sounds on terminating entities no longer logs an error.
|
||||
|
||||
|
||||
## 223.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* Better exception logging for IRobustJob.
|
||||
* Add SetGridAudio helper for SharedAudioSystem.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix placement manager not setting entity rotation correctly.
|
||||
* Fix grid-based audio not playing correctly.
|
||||
|
||||
|
||||
## 223.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added several new `FormattedMessage` methods for better exception tolerance when parsing markup. Several existing methods have been marked as obsolete, with new renamed methods taking their place.
|
||||
|
||||
|
||||
## 223.1.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `MapGridComponent.LastTileModifiedTick` is now actually networked to clients.
|
||||
|
||||
|
||||
## 223.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed an exception caused by enum cvars using integer type values instead of enum values
|
||||
|
||||
|
||||
## 223.1.0
|
||||
|
||||
### Other
|
||||
|
||||
* Various `ContainerSystem` methods have been obsoleted in favour of overrides that take in an `Entity` struct instead of `EntityUid`
|
||||
* Various `EntityCoordinates` methods have been obsoleted with replacements added to `SharedTransformSystem`
|
||||
|
||||
|
||||
## 223.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The `ComponentState` class is now abstract. Networked components that don't have state information now just return a null state.
|
||||
* The way that delta component states work has changed. It now expects there to be two different state classes, only one of which should implement `IComponentDeltaState<TFullState>`
|
||||
|
||||
### New features
|
||||
|
||||
* A new `replay.checkpoint_min_interval` cvar has been added. It can be used to limit the frequency at which checkpoints are generated when loading a replay.
|
||||
* Added `IConfigurationManager.OnCVarValueChanged`. This is a c# event that gets invoked whenever any cvar value changes.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `IEyeManager.GetWorldViewbounds()` and `IEyeManager.GetWorldViewbounds()` should now return the correct bounds if the main viewport does not take up the whole screen.
|
||||
|
||||
### Other
|
||||
|
||||
* The default values of various replay related cvars have been changed to try and reduce memory usage.
|
||||
|
||||
|
||||
## 222.4.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added the following types from `System.Numerics` to the sandbox: `Complex`, `Matrix3x2`, `Matrix4x4`, `Plane`, `Quaternion`, `Vector3`, `Vector4`.
|
||||
|
||||
|
||||
## 222.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* `ITileDefinition.EditorHidden` allows hiding a tile from the tile spawn panel.
|
||||
* Ordered event subscriptions now take child types into account, so ordering based on a shared type will work.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Cross-map BUI range checks now work.
|
||||
* Paused entities update on prototype reload.
|
||||
|
||||
### Other
|
||||
|
||||
* Fixed build compatibility with .NET 8.0.300 SDK, due to changes in how Central Package Management behaves.
|
||||
* Physics component has delta states to reduce network usage.
|
||||
|
||||
|
||||
## 222.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added `EntityQuery.Comp()` (abbreviation of `GetComponent()`)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `SerializationManager.TryGetVariableType` checking the wrong property.
|
||||
* Fixed GrammarSystem mispredicting a character's gender
|
||||
|
||||
### Other
|
||||
|
||||
* User interface system now performs range checks in parallel
|
||||
|
||||
|
||||
## 222.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed never setting BoundUserInterface.State.
|
||||
|
||||
### Other
|
||||
|
||||
* Add truncate for filesaving.
|
||||
* Add method for getting the type of a data field by name from ISerializationManager.
|
||||
|
||||
|
||||
## 222.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added `BoundKeyEventArgs.IsRepeat`.
|
||||
* Added `net.lidgren_log_warning` and `net.lidgren_log_error` CVars.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix assert trip when holding repeatable keybinds.
|
||||
|
||||
### Other
|
||||
|
||||
* Updated Lidgren to v0.3.1. This should provide performance improvements if warning/error logs are disabled.
|
||||
|
||||
|
||||
## 222.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Mark IComponentFactory argument in EntityPrototype as mandatory.
|
||||
|
||||
### New features
|
||||
|
||||
* Add `EntProtoId<T>` to check for components on the attached entity as well.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix PVS iterating duplicate chunks for multiple viewsubscriptions.
|
||||
|
||||
### Other
|
||||
|
||||
* Defer clientside BUI opens if it's the first state that comes in.
|
||||
|
||||
|
||||
## 221.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add SetMapAudio helper to SharedAudioSystem to setup map-wide audio entities.
|
||||
* Add SetWorldRotNoLerp method to SharedTransformSystem to avoid client lerping.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `SpriteComponent.CopyFrom` now copies `CopyToShaderParameters` configuration.
|
||||
|
||||
|
||||
## 221.1.0
|
||||
|
||||
|
||||
## 221.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `EntParentChangedMessage.OldMapId` is now an `EntityUid` instead of `MapId`
|
||||
* `TransformSystem.DetachParentToNull()` is being renamed to `DetachEntity`
|
||||
* The order in which `MoveEvent` handlers are invoked has been changed to prioritise engine subscriptions
|
||||
|
||||
### New features
|
||||
|
||||
* Added `UpdateHovered()` and `SetHovered()` to `IUserInterfaceManager`, for updating or modifying the currently hovered control.
|
||||
* Add SwapPositions to TransformSystem to swap two entity's transforms.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Improve client gamestate exception tolerance.
|
||||
|
||||
### Other
|
||||
|
||||
* If the currently hovered control is disposed, `UserInterfaceManager` will now look for a new control, rather than just setting the hovered control to null.
|
||||
|
||||
### Internal
|
||||
|
||||
* Use more `EntityQuery<T>` internally in EntityManager and PhysicsSystem.
|
||||
|
||||
|
||||
## 220.2.0
|
||||
@@ -85,7 +600,7 @@ END TEMPLATE-->
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Refactor UserInterfaceSystem.
|
||||
* Refactor UserInterfaceSystem.
|
||||
- The API has been significantly cleaned up and standardised, most noticeably callers don't need to worry about TryGetUi and can rely on either HasUi, SetUiState, CloseUi, or OpenUi to handle their code as appropriate.
|
||||
- Interface data is now stored via key rather than as a flat list which is a breaking change for YAML.
|
||||
- BoundUserInterfaces can now be completely handled via Shared code. Existing Server-side callers will behave similarly to before.
|
||||
|
||||
@@ -2,4 +2,7 @@
|
||||
id: Audio
|
||||
name: Audio
|
||||
description: Audio entity used by engine
|
||||
save: false
|
||||
save: false
|
||||
components:
|
||||
- type: Transform
|
||||
gridTraversal: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: entity
|
||||
id: debugRotation
|
||||
abstract: true
|
||||
categories: [ debug ]
|
||||
categories: [ Debug ]
|
||||
components:
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
# debug related entities
|
||||
- type: entityCategory
|
||||
id: debug
|
||||
id: Debug
|
||||
name: entity-category-name-debug
|
||||
description: entity-category-desc-debug
|
||||
suffix: entity-category-suffix-debug
|
||||
|
||||
# entities that spawn other entities
|
||||
- type: entityCategory
|
||||
id: spawner
|
||||
id: Spawner
|
||||
name: entity-category-name-spawner
|
||||
description: entity-category-desc-spawner
|
||||
|
||||
# entities that should be hidden from the spawn menu
|
||||
# simple category that just exists to hide prototypes in spawn menus
|
||||
- type: entityCategory
|
||||
id: hideSpawnMenu
|
||||
id: HideSpawnMenu
|
||||
name: entity-category-name-hide
|
||||
description: entity-category-desc-hide
|
||||
hideSpawnMenu: true
|
||||
inheritable: false
|
||||
|
||||
@@ -9,6 +9,7 @@ cmd-parse-failure-float = {$arg} is not a valid float.
|
||||
cmd-parse-failure-bool = {$arg} is not a valid bool.
|
||||
cmd-parse-failure-uid = {$arg} is not a valid entity UID.
|
||||
cmd-parse-failure-mapid = {$arg} is not a valid MapId.
|
||||
cmd-parse-failure-enum = {$arg} is not a {$enum} Enum.
|
||||
cmd-parse-failure-grid = {$arg} is not a valid grid.
|
||||
cmd-parse-failure-entity-exist = UID {$arg} does not correspond to an existing entity.
|
||||
cmd-parse-failure-session = There is no session with username: {$username}
|
||||
@@ -43,6 +44,13 @@ cmd-cvar-compl-list = List available CVars
|
||||
cmd-cvar-arg-name = <name | ?>
|
||||
cmd-cvar-value-hidden = <value hidden>
|
||||
|
||||
## 'cvar_subs' command
|
||||
cmd-cvar_subs-desc = Lists the OnValueChanged subscriptions for a CVar.
|
||||
cmd-cvar_subs-help = Usage: cvar_subs <name>
|
||||
|
||||
cmd-cvar_subs-invalid-args = Must provide exactly one argument.
|
||||
cmd-cvar_subs-arg-name = <name>
|
||||
|
||||
## 'list' command
|
||||
cmd-list-desc = Lists available commands, with optional search filter
|
||||
cmd-list-help = Usage: list [filter]
|
||||
@@ -245,9 +253,6 @@ cmd-bind-arg-command = <InputCommand>
|
||||
cmd-net-draw-interp-desc = Toggles the debug drawing of the network interpolation.
|
||||
cmd-net-draw-interp-help = Usage: net_draw_interp
|
||||
|
||||
cmd-net-draw-interp-desc = Toggles the debug drawing of the network interpolation.
|
||||
cmd-net-draw-interp-help = Usage: net_draw_interp
|
||||
|
||||
cmd-net-watch-ent-desc = Dumps all network updates for an EntityId to the console.
|
||||
cmd-net-watch-ent-help = Usage: net_watchent <0|EntityUid>
|
||||
|
||||
@@ -299,16 +304,9 @@ cmd-savegrid-help = savegrid <gridID> <Path>
|
||||
cmd-testbed-desc = Loads a physics testbed on the specified map.
|
||||
cmd-testbed-help = testbed <mapid> <test>
|
||||
|
||||
cmd-saveconfig-desc = Saves the client configuration to the config file.
|
||||
cmd-saveconfig-help = saveconfig
|
||||
|
||||
## 'flushcookies' command
|
||||
# Note: the flushcookies command is from Robust.Client.WebView, it's not in the main engine code.
|
||||
|
||||
cmd-flushcookies-desc = Flush CEF cookie storage to disk
|
||||
cmd-flushcookies-help = This ensure cookies are properly saved to disk in the event of unclean shutdowns.
|
||||
Note that the actual operation is asynchronous.
|
||||
|
||||
## 'addcomp' command
|
||||
cmd-addcomp-desc = Adds a component to an entity.
|
||||
cmd-addcomp-help = addcomp <uid> <componentName>
|
||||
@@ -384,9 +382,9 @@ cmd-tp-desc = Teleports a player to any location in the round.
|
||||
cmd-tp-help = tp <x> <y> [<mapID>]
|
||||
|
||||
cmd-tpto-desc = Teleports the current player or the specified players/entities to the location of the first player/entity.
|
||||
cmd-tpto-help = tpto <username|uid> [username|uid]...
|
||||
cmd-tpto-destination-hint = destination (uid or username)
|
||||
cmd-tpto-victim-hint = entity to teleport (uid or username)
|
||||
cmd-tpto-help = tpto <username|uid> [username|NetEntity]...
|
||||
cmd-tpto-destination-hint = destination (NetEntity or username)
|
||||
cmd-tpto-victim-hint = entity to teleport (NetEntity or username)
|
||||
cmd-tpto-parse-error = Cant resolve entity or player: {$str}
|
||||
|
||||
cmd-listplayers-desc = Lists all players currently connected.
|
||||
@@ -446,9 +444,6 @@ cmd-showanchored-help = Usage: showanchored
|
||||
cmd-dmetamem-desc = Dumps a type's members in a format suitable for the sandbox configuration file.
|
||||
cmd-dmetamem-help = Usage: dmetamem <type>
|
||||
|
||||
cmd-dmetamem-desc = Displays chunk bounds for the purposes of rendering.
|
||||
cmd-dmetamem-help = Usage: showchunkbb <type>
|
||||
|
||||
cmd-launchauth-desc = Load authentication tokens from launcher data to aid in testing of live servers.
|
||||
cmd-launchauth-help = Usage: launchauth <account name>
|
||||
|
||||
@@ -515,9 +510,6 @@ cmd-profsnap-help = Usage: profsnap
|
||||
cmd-devwindow-desc = Dev Window
|
||||
cmd-devwindow-help = Usage: devwindow
|
||||
|
||||
cmd-devwindow-desc = Open file
|
||||
cmd-devwindow-help = Usage: testopenfile
|
||||
|
||||
cmd-scene-desc = Immediately changes the UI scene/state.
|
||||
cmd-scene-help = Usage: scene <className>
|
||||
|
||||
@@ -528,14 +520,11 @@ cmd-hwid-desc = Returns the current HWID (HardWare ID).
|
||||
cmd-hwid-help = Usage: hwid
|
||||
|
||||
cmd-vvread-desc = Retrieve a path's value using VV (View Variables).
|
||||
cmd-vvread-desc = Usage: vvread <path>
|
||||
cmd-vvread-help = Usage: vvread <path>
|
||||
|
||||
cmd-vvwrite-desc = Modify a path's value using VV (View Variables).
|
||||
cmd-vvwrite-help = Usage: vvwrite <path>
|
||||
|
||||
cmd-vv-desc = Opens View Variables (VV).
|
||||
cmd-vv-help = Usage: vv <path|entity ID|guihover>
|
||||
|
||||
cmd-vvinvoke-desc = Invoke/Call a path with arguments using VV.
|
||||
cmd-vvinvoke-help = Usage: vvinvoke <path> [arguments...]
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
entity-category-name-debug = Debug
|
||||
entity-category-desc-debug = Entity prototypes intended for debugging & testing.
|
||||
entity-category-suffix-debug = Debug
|
||||
|
||||
entity-category-name-spawner = Spawner
|
||||
entity-category-desc-spawner = Entity prototypes that spawn other entities.
|
||||
|
||||
entity-category-name-hide = Hidden
|
||||
entity-category-desc-hide = Entity prototypes that should be hidden from the spawn menu
|
||||
entity-category-desc-hide = Entity prototypes that should be hidden from entity spawn menus
|
||||
|
||||
2
Resources/Locale/en-US/userinterface.ftl
Normal file
2
Resources/Locale/en-US/userinterface.ftl
Normal file
@@ -0,0 +1,2 @@
|
||||
popup-copy-button = Copy
|
||||
popup-title = Alert!
|
||||
91
Robust.Analyzers.Tests/DataDefinitionAnalyzerTest.cs
Normal file
91
Robust.Analyzers.Tests/DataDefinitionAnalyzerTest.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.DataDefinitionAnalyzer>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class DataDefinitionAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<DataDefinitionAnalyzer, NUnitVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
const string code = """
|
||||
using System;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.ViewVariables
|
||||
{
|
||||
public sealed class ViewVariablesAttribute : Attribute
|
||||
{
|
||||
public readonly VVAccess Access = VVAccess.ReadOnly;
|
||||
|
||||
public ViewVariablesAttribute() { }
|
||||
|
||||
public ViewVariablesAttribute(VVAccess access)
|
||||
{
|
||||
Access = access;
|
||||
}
|
||||
}
|
||||
public enum VVAccess : byte
|
||||
{
|
||||
ReadOnly = 0,
|
||||
ReadWrite = 1,
|
||||
}
|
||||
}
|
||||
|
||||
namespace Robust.Shared.Serialization.Manager.Attributes
|
||||
{
|
||||
public class DataFieldBaseAttribute : Attribute;
|
||||
public class DataFieldAttribute : DataFieldBaseAttribute;
|
||||
public sealed class DataDefinitionAttribute : Attribute;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class Foo
|
||||
{
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Bad;
|
||||
|
||||
[DataField]
|
||||
public int Good;
|
||||
|
||||
[DataField, ViewVariables]
|
||||
public int Good2;
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public int Good3;
|
||||
|
||||
[ViewVariables]
|
||||
public int Good4;
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(35,17): info RA0028: Data field Bad in data definition Foo has ViewVariables attribute with ReadWrite access, which is redundant
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldNoVVReadWriteRule).WithSpan(35, 17, 35, 50).WithArguments("Bad", "Foo")
|
||||
);
|
||||
}
|
||||
}
|
||||
92
Robust.Analyzers.Tests/MustCallBaseAnalyzerTest.cs
Normal file
92
Robust.Analyzers.Tests/MustCallBaseAnalyzerTest.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.MustCallBaseAnalyzer>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class MustCallBaseAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<MustCallBaseAnalyzer, NUnitVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.IoC.MustCallBaseAttribute.cs"
|
||||
);
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.Analyzers;
|
||||
|
||||
public class Foo
|
||||
{
|
||||
[MustCallBase]
|
||||
public virtual void Function()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[MustCallBase(true)]
|
||||
public virtual void Function2()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class Bar : Foo
|
||||
{
|
||||
public override void Function()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Function2()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class Baz : Foo
|
||||
{
|
||||
public override void Function()
|
||||
{
|
||||
base.Function();
|
||||
}
|
||||
}
|
||||
|
||||
public class Bal : Bar
|
||||
{
|
||||
public override void Function2()
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(20,26): warning RA0028: Overriders of this function must always call the base function
|
||||
VerifyCS.Diagnostic().WithSpan(20, 26, 20, 34),
|
||||
// /0/Test0.cs(41,26): warning RA0028: Overriders of this function must always call the base function
|
||||
VerifyCS.Diagnostic().WithSpan(41, 26, 41, 35));
|
||||
}
|
||||
}
|
||||
57
Robust.Analyzers.Tests/NoUncachedRegexAnalyzerTest.cs
Normal file
57
Robust.Analyzers.Tests/NoUncachedRegexAnalyzerTest.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.NoUncachedRegexAnalyzer>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class NoUncachedRegexAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<NoUncachedRegexAnalyzer, NUnitVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
const string code = """
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
public static class Foo
|
||||
{
|
||||
public static void Bad()
|
||||
{
|
||||
Regex.Replace("foo", "bar", "baz");
|
||||
}
|
||||
|
||||
public static void Good()
|
||||
{
|
||||
var r = new Regex("bar");
|
||||
r.Replace("foo", "baz");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(7,9): warning RA0026: Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.
|
||||
VerifyCS.Diagnostic().WithSpan(7, 9, 7, 43)
|
||||
);
|
||||
}
|
||||
}
|
||||
71
Robust.Analyzers.Tests/PreferNonGenericVariantForTest.cs
Normal file
71
Robust.Analyzers.Tests/PreferNonGenericVariantForTest.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.PreferNonGenericVariantForAnalyzer>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class PreferNonGenericVariantForTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<PreferNonGenericVariantForAnalyzer, NUnitVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code },
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute.cs"
|
||||
);
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.Analyzers;
|
||||
|
||||
public class Bar { };
|
||||
public class Baz { };
|
||||
public class Okay { };
|
||||
|
||||
public static class Foo
|
||||
{
|
||||
[PreferNonGenericVariantFor(typeof(Bar), typeof(Baz))]
|
||||
public static void DoFoo<T>() { }
|
||||
}
|
||||
|
||||
public class Test
|
||||
{
|
||||
public void DoBad()
|
||||
{
|
||||
Foo.DoFoo<Bar>();
|
||||
}
|
||||
|
||||
public void DoGood()
|
||||
{
|
||||
Foo.DoFoo<Okay>();
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(17,9): warning RA0029: Use the non-generic variant of this method for type Bar
|
||||
VerifyCS.Diagnostic().WithSpan(17, 9, 17, 25).WithArguments("Bar")
|
||||
);
|
||||
}
|
||||
}
|
||||
62
Robust.Analyzers.Tests/PreferOtherTypeAnalyzerTest.cs
Normal file
62
Robust.Analyzers.Tests/PreferOtherTypeAnalyzerTest.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.PreferOtherTypeAnalyzer>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class PreferOtherTypeAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<PreferOtherTypeAnalyzer, NUnitVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code },
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs"
|
||||
);
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.Analyzers;
|
||||
|
||||
public class EntityPrototype { };
|
||||
public class EntProtoId { };
|
||||
public class ReagentPrototype { };
|
||||
|
||||
[PreferOtherType(typeof(EntityPrototype), typeof(EntProtoId))]
|
||||
public class ProtoId<T> { };
|
||||
|
||||
public class Test
|
||||
{
|
||||
public ProtoId<EntityPrototype> Bad = new();
|
||||
|
||||
public ProtoId<ReagentPrototype> Good = new();
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(12,12): warning RA0031: Use the specific type EntProtoId instead of ProtoId when the type argument is EntityPrototype
|
||||
VerifyCS.Diagnostic().WithSpan(12, 12, 12, 48).WithArguments("EntProtoId", "ProtoId", "EntityPrototype")
|
||||
);
|
||||
}
|
||||
}
|
||||
81
Robust.Analyzers.Tests/PreferOtherTypeFixerTest.cs
Normal file
81
Robust.Analyzers.Tests/PreferOtherTypeFixerTest.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.PreferOtherTypeAnalyzer>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
public sealed class PreferOtherTypeFixerTest
|
||||
{
|
||||
private static Task Verifier(string code, string fixedCode, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpCodeFixTest<PreferOtherTypeAnalyzer, PreferOtherTypeFixer, NUnitVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code },
|
||||
},
|
||||
FixedState =
|
||||
{
|
||||
Sources = { fixedCode },
|
||||
}
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs"
|
||||
);
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.FixedState,
|
||||
"Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs"
|
||||
);
|
||||
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.Analyzers;
|
||||
|
||||
public class EntityPrototype { };
|
||||
public class EntProtoId { };
|
||||
public class ReagentPrototype { };
|
||||
|
||||
[PreferOtherType(typeof(EntityPrototype), typeof(EntProtoId))]
|
||||
public class ProtoId<T> { };
|
||||
|
||||
public class Test
|
||||
{
|
||||
public ProtoId<EntityPrototype> Foo = new();
|
||||
}
|
||||
""";
|
||||
|
||||
const string fixedCode = """
|
||||
using Robust.Shared.Analyzers;
|
||||
|
||||
public class EntityPrototype { };
|
||||
public class EntProtoId { };
|
||||
public class ReagentPrototype { };
|
||||
|
||||
[PreferOtherType(typeof(EntityPrototype), typeof(EntProtoId))]
|
||||
public class ProtoId<T> { };
|
||||
|
||||
public class Test
|
||||
{
|
||||
public EntProtoId Foo = new();
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code, fixedCode,
|
||||
// /0/Test0.cs(12,12): error RA0031: Use the specific type EntProtoId instead of ProtoId when the type argument is EntityPrototype
|
||||
VerifyCS.Diagnostic().WithSpan(12, 12, 12, 48).WithArguments("EntProtoId", "ProtoId", "EntityPrototype"));
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" LogicalName="Robust.Shared.Analyzers.AccessAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" LogicalName="Robust.Shared.Analyzers.AccessPermissions.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\MustCallBaseAttribute.cs" LogicalName="Robust.Shared.IoC.MustCallBaseAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferNonGenericVariantForAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferOtherTypeAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -25,6 +28,7 @@
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzer.Testing"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces"/>
|
||||
<PackageReference Include="NUnit"/>
|
||||
<PackageReference Include="NUnit3TestAdapter"/>
|
||||
|
||||
@@ -6,6 +6,8 @@ using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
using Robust.Shared.Serialization.Manager.Definition;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
@@ -15,6 +17,9 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
private const string DataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataDefinitionAttribute";
|
||||
private const string ImplicitDataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.ImplicitDataDefinitionForInheritorsAttribute";
|
||||
private const string DataFieldBaseNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataFieldBaseAttribute";
|
||||
private const string ViewVariablesNamespace = "Robust.Shared.ViewVariables.ViewVariablesAttribute";
|
||||
private const string DataFieldAttributeName = "DataField";
|
||||
private const string ViewVariablesAttributeName = "ViewVariables";
|
||||
|
||||
private static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
|
||||
Diagnostics.IdDataDefinitionPartial,
|
||||
@@ -56,8 +61,29 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
"Make sure to add a setter."
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor DataFieldRedundantTagRule = new(
|
||||
Diagnostics.IdDataFieldRedundantTag,
|
||||
"Data field has redundant tag specified",
|
||||
"Data field {0} in data definition {1} has an explicitly set tag that matches autogenerated tag",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Info,
|
||||
true,
|
||||
"Make sure to remove the tag string from the data field attribute."
|
||||
);
|
||||
|
||||
public static readonly DiagnosticDescriptor DataFieldNoVVReadWriteRule = new(
|
||||
Diagnostics.IdDataFieldNoVVReadWrite,
|
||||
"Data field has VV ReadWrite",
|
||||
"Data field {0} in data definition {1} has ViewVariables attribute with ReadWrite access, which is redundant",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Info,
|
||||
true,
|
||||
"Make sure to remove the ViewVariables attribute."
|
||||
);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
|
||||
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule
|
||||
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule,
|
||||
DataFieldRedundantTagRule, DataFieldNoVVReadWriteRule
|
||||
);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
@@ -125,6 +151,18 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasRedundantTag(fieldSymbol))
|
||||
{
|
||||
TryGetAttributeLocation(field, DataFieldAttributeName, out var location);
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, location, fieldSymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasVVReadWrite(fieldSymbol))
|
||||
{
|
||||
TryGetAttributeLocation(field, ViewVariablesAttributeName, out var location);
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldNoVVReadWriteRule, location, fieldSymbol.Name, type.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +187,18 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasRedundantTag(propertySymbol))
|
||||
{
|
||||
TryGetAttributeLocation(property, DataFieldAttributeName, out var location);
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, location, propertySymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasVVReadWrite(propertySymbol))
|
||||
{
|
||||
TryGetAttributeLocation(property, ViewVariablesAttributeName, out var location);
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldNoVVReadWriteRule, location, propertySymbol.Name, type.Name));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsReadOnlyDataField(ITypeSymbol type, ISymbol field)
|
||||
@@ -217,6 +267,24 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetAttributeLocation(MemberDeclarationSyntax syntax, string attributeName, out Location location)
|
||||
{
|
||||
foreach (var attributeList in syntax.AttributeLists)
|
||||
{
|
||||
foreach (var attribute in attributeList.Attributes)
|
||||
{
|
||||
if (attribute.Name.ToString() != attributeName)
|
||||
continue;
|
||||
|
||||
location = attribute.GetLocation();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Default to the declaration syntax's location
|
||||
location = syntax.GetLocation();
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsReadOnlyMember(ITypeSymbol type, ISymbol member)
|
||||
{
|
||||
if (member is IFieldSymbol field)
|
||||
@@ -248,6 +316,57 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool HasRedundantTag(ISymbol symbol)
|
||||
{
|
||||
if (!IsDataField(symbol, out var _, out var attribute))
|
||||
return false;
|
||||
|
||||
// No args, no problem
|
||||
if (attribute.ConstructorArguments.Length == 0)
|
||||
return false;
|
||||
|
||||
// If a tag is explicitly specified, it will be the first argument...
|
||||
var tagArgument = attribute.ConstructorArguments[0];
|
||||
// ...but the first arg could also something else, since tag is optional
|
||||
// so we make sure that it's a string
|
||||
if (tagArgument.Value is not string explicitName)
|
||||
return false;
|
||||
|
||||
// Get the name that sourcegen would provide
|
||||
var automaticName = DataDefinitionUtility.AutoGenerateTag(symbol.Name);
|
||||
|
||||
// If the explicit name matches the sourcegen name, we have a redundancy
|
||||
return explicitName == automaticName;
|
||||
}
|
||||
|
||||
private static bool HasVVReadWrite(ISymbol symbol)
|
||||
{
|
||||
if (!IsDataField(symbol, out _, out _))
|
||||
return false;
|
||||
|
||||
// Make sure it has ViewVariablesAttribute
|
||||
AttributeData? viewVariablesAttribute = null;
|
||||
foreach (var attr in symbol.GetAttributes())
|
||||
{
|
||||
if (attr.AttributeClass?.ToDisplayString() == ViewVariablesNamespace)
|
||||
{
|
||||
viewVariablesAttribute = attr;
|
||||
}
|
||||
}
|
||||
if (viewVariablesAttribute == null)
|
||||
return false;
|
||||
|
||||
// Default is ReadOnly, which is fine
|
||||
if (viewVariablesAttribute.ConstructorArguments.Length == 0)
|
||||
return false;
|
||||
|
||||
var accessArgument = viewVariablesAttribute.ConstructorArguments[0];
|
||||
if (accessArgument.Value is not byte accessByte)
|
||||
return false;
|
||||
|
||||
return (VVAccess)accessByte == VVAccess.ReadWrite;
|
||||
}
|
||||
|
||||
private static bool IsImplicitDataDefinition(ITypeSymbol type)
|
||||
{
|
||||
if (HasAttribute(type, ImplicitDataDefinitionNamespace))
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#nullable enable
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CodeActions;
|
||||
using Microsoft.CodeAnalysis.CodeFixes;
|
||||
@@ -16,8 +13,13 @@ namespace Robust.Analyzers;
|
||||
[ExportCodeFixProvider(LanguageNames.CSharp)]
|
||||
public sealed class DefinitionFixer : CodeFixProvider
|
||||
{
|
||||
private const string DataFieldAttributeName = "DataField";
|
||||
|
||||
private const string ViewVariablesAttributeName = "ViewVariables";
|
||||
|
||||
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
|
||||
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable
|
||||
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable,
|
||||
IdDataFieldRedundantTag, IdDataFieldNoVVReadWrite
|
||||
);
|
||||
|
||||
public override Task RegisterCodeFixesAsync(CodeFixContext context)
|
||||
@@ -34,6 +36,10 @@ public sealed class DefinitionFixer : CodeFixProvider
|
||||
return RegisterDataFieldFix(context, diagnostic);
|
||||
case IdDataFieldPropertyWritable:
|
||||
return RegisterDataFieldPropertyFix(context, diagnostic);
|
||||
case IdDataFieldRedundantTag:
|
||||
return RegisterRedundantTagFix(context, diagnostic);
|
||||
case IdDataFieldNoVVReadWrite:
|
||||
return RegisterVVReadWriteFix(context, diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +78,110 @@ public sealed class DefinitionFixer : CodeFixProvider
|
||||
return document.WithSyntaxRoot(root);
|
||||
}
|
||||
|
||||
private static async Task RegisterRedundantTagFix(CodeFixContext context, Diagnostic diagnostic)
|
||||
{
|
||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
|
||||
var span = diagnostic.Location.SourceSpan;
|
||||
var token = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<MemberDeclarationSyntax>().First();
|
||||
|
||||
if (token == null)
|
||||
return;
|
||||
|
||||
// Find the DataField attribute
|
||||
AttributeSyntax? dataFieldAttribute = null;
|
||||
foreach (var attributeList in token.AttributeLists)
|
||||
{
|
||||
foreach (var attribute in attributeList.Attributes)
|
||||
{
|
||||
if (attribute.Name.ToString() == DataFieldAttributeName)
|
||||
{
|
||||
dataFieldAttribute = attribute;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dataFieldAttribute != null)
|
||||
break;
|
||||
}
|
||||
|
||||
if (dataFieldAttribute == null)
|
||||
return;
|
||||
|
||||
context.RegisterCodeFix(CodeAction.Create(
|
||||
"Remove explicitly set tag",
|
||||
c => RemoveRedundantTag(context.Document, dataFieldAttribute, c),
|
||||
"Remove explicitly set tag"
|
||||
), diagnostic);
|
||||
}
|
||||
|
||||
private static async Task<Document> RemoveRedundantTag(Document document, AttributeSyntax syntax, CancellationToken cancellation)
|
||||
{
|
||||
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
|
||||
|
||||
if (syntax.ArgumentList == null)
|
||||
return document;
|
||||
|
||||
AttributeSyntax? newSyntax;
|
||||
if (syntax.ArgumentList.Arguments.Count == 1)
|
||||
{
|
||||
// If this is the only argument, delete the ArgumentList so we don't leave empty parentheses
|
||||
newSyntax = syntax.RemoveNode(syntax.ArgumentList, SyntaxRemoveOptions.KeepNoTrivia);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove the first argument, which is the tag
|
||||
var newArgs = syntax.ArgumentList.Arguments.RemoveAt(0);
|
||||
var newArgList = syntax.ArgumentList.WithArguments(newArgs);
|
||||
// Construct a new attribute with the tag removed
|
||||
newSyntax = syntax.WithArgumentList(newArgList);
|
||||
}
|
||||
|
||||
root = root!.ReplaceNode(syntax, newSyntax!);
|
||||
|
||||
return document.WithSyntaxRoot(root);
|
||||
}
|
||||
|
||||
private static async Task RegisterVVReadWriteFix(CodeFixContext context, Diagnostic diagnostic)
|
||||
{
|
||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
|
||||
var span = diagnostic.Location.SourceSpan;
|
||||
var token = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<MemberDeclarationSyntax>().First();
|
||||
|
||||
if (token == null)
|
||||
return;
|
||||
|
||||
context.RegisterCodeFix(CodeAction.Create(
|
||||
"Remove ViewVariables attribute",
|
||||
c => RemoveVVAttribute(context.Document, token, c),
|
||||
"Remove ViewVariables attribute"
|
||||
), diagnostic);
|
||||
}
|
||||
|
||||
private static async Task<Document> RemoveVVAttribute(Document document, MemberDeclarationSyntax syntax, CancellationToken cancellation)
|
||||
{
|
||||
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
|
||||
|
||||
var newLists = new SyntaxList<AttributeListSyntax>();
|
||||
foreach (var attributeList in syntax.AttributeLists)
|
||||
{
|
||||
var attributes = new SeparatedSyntaxList<AttributeSyntax>();
|
||||
foreach (var attribute in attributeList.Attributes)
|
||||
{
|
||||
if (attribute.Name.ToString() != ViewVariablesAttributeName)
|
||||
{
|
||||
attributes = attributes.Add(attribute);
|
||||
}
|
||||
}
|
||||
// Don't add empty lists []
|
||||
if (attributes.Count > 0)
|
||||
newLists = newLists.Add(attributeList.WithAttributes(attributes));
|
||||
}
|
||||
var newSyntax = syntax.WithAttributeLists(newLists);
|
||||
|
||||
root = root!.ReplaceNode(syntax, newSyntax);
|
||||
|
||||
return document.WithSyntaxRoot(root);
|
||||
}
|
||||
|
||||
private static async Task RegisterDataFieldFix(CodeFixContext context, Diagnostic diagnostic)
|
||||
{
|
||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
|
||||
|
||||
111
Robust.Analyzers/MustCallBaseAnalyzer.cs
Normal file
111
Robust.Analyzers/MustCallBaseAnalyzer.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Enforces <c>MustCallBaseAttribute</c>.
|
||||
/// </summary>
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class MustCallBaseAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string Attribute = "Robust.Shared.Analyzers.MustCallBaseAttribute";
|
||||
|
||||
private static readonly DiagnosticDescriptor Rule = new(
|
||||
Diagnostics.IdMustCallBase,
|
||||
"No base call in overriden function",
|
||||
"Overriders of this function must always call the base function",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Method);
|
||||
}
|
||||
|
||||
private static void AnalyzeSymbol(SymbolAnalysisContext context)
|
||||
{
|
||||
if (context.Symbol is not IMethodSymbol { IsOverride: true } method)
|
||||
return;
|
||||
|
||||
var attrSymbol = context.Compilation.GetTypeByMetadataName(Attribute);
|
||||
if (attrSymbol == null)
|
||||
return;
|
||||
|
||||
if (DoesMethodOverriderHaveAttribute(method, attrSymbol) is not { } data)
|
||||
return;
|
||||
|
||||
if (data is { onlyOverrides: true, depth: < 2 })
|
||||
return;
|
||||
|
||||
var syntax = (MethodDeclarationSyntax) method.DeclaringSyntaxReferences[0].GetSyntax();
|
||||
if (HasBaseCall(syntax))
|
||||
return;
|
||||
|
||||
var diag = Diagnostic.Create(Rule, syntax.Identifier.GetLocation());
|
||||
context.ReportDiagnostic(diag);
|
||||
}
|
||||
|
||||
private static (int depth, bool onlyOverrides)? DoesMethodOverriderHaveAttribute(
|
||||
IMethodSymbol method,
|
||||
INamedTypeSymbol attributeSymbol)
|
||||
{
|
||||
var depth = 0;
|
||||
while (method.OverriddenMethod != null)
|
||||
{
|
||||
depth += 1;
|
||||
method = method.OverriddenMethod;
|
||||
if (GetAttribute(method, attributeSymbol) is not { } attribute)
|
||||
continue;
|
||||
|
||||
var onlyOverrides = attribute.ConstructorArguments is [{Kind: TypedConstantKind.Primitive, Value: true}];
|
||||
return (depth, onlyOverrides);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool HasBaseCall(MethodDeclarationSyntax syntax)
|
||||
{
|
||||
return syntax.Accept(new BaseCallLocator());
|
||||
}
|
||||
|
||||
private static AttributeData? GetAttribute(ISymbol namedTypeSymbol, INamedTypeSymbol attrSymbol)
|
||||
{
|
||||
return namedTypeSymbol.GetAttributes()
|
||||
.SingleOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol));
|
||||
}
|
||||
|
||||
private sealed class BaseCallLocator : CSharpSyntaxVisitor<bool>
|
||||
{
|
||||
public override bool VisitBaseExpression(BaseExpressionSyntax node)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool DefaultVisit(SyntaxNode node)
|
||||
{
|
||||
foreach (var childNode in node.ChildNodes())
|
||||
{
|
||||
if (childNode is not CSharpSyntaxNode cSharpSyntax)
|
||||
continue;
|
||||
|
||||
if (cSharpSyntax.Accept(this))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
66
Robust.Analyzers/NoUncachedRegexAnalyzer.cs
Normal file
66
Robust.Analyzers/NoUncachedRegexAnalyzer.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class NoUncachedRegexAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string RegexTypeName = "Regex";
|
||||
private const string RegexType = $"System.Text.RegularExpressions.{RegexTypeName}";
|
||||
|
||||
private static readonly DiagnosticDescriptor Rule = new (
|
||||
Diagnostics.IdUncachedRegex,
|
||||
"Use of uncached static Regex function",
|
||||
"Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public static readonly HashSet<string> BadFunctions =
|
||||
[
|
||||
"Count",
|
||||
"EnumerateMatches",
|
||||
"IsMatch",
|
||||
"Match",
|
||||
"Matches",
|
||||
"Replace",
|
||||
"Split"
|
||||
];
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.EnableConcurrentExecution();
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.RegisterOperationAction(CheckInvocation, OperationKind.Invocation);
|
||||
}
|
||||
|
||||
private static void CheckInvocation(OperationAnalysisContext context)
|
||||
{
|
||||
if (context.Operation is not IInvocationOperation invocation)
|
||||
return;
|
||||
|
||||
// All Regex functions we care about are static.
|
||||
var targetMethod = invocation.TargetMethod;
|
||||
if (!targetMethod.IsStatic)
|
||||
return;
|
||||
|
||||
// Bail early.
|
||||
if (targetMethod.ContainingType.Name != "Regex")
|
||||
return;
|
||||
|
||||
var regexType = context.Compilation.GetTypeByMetadataName(RegexType);
|
||||
if (!SymbolEqualityComparer.Default.Equals(regexType, targetMethod.ContainingType))
|
||||
return;
|
||||
|
||||
if (!BadFunctions.Contains(targetMethod.Name))
|
||||
return;
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.Syntax.GetLocation()));
|
||||
}
|
||||
}
|
||||
65
Robust.Analyzers/PreferNonGenericVariantForAnalyzer.cs
Normal file
65
Robust.Analyzers/PreferNonGenericVariantForAnalyzer.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class PreferNonGenericVariantForAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string AttributeType = "Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute";
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
|
||||
UseNonGenericVariantDescriptor
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor UseNonGenericVariantDescriptor = new(
|
||||
Diagnostics.IdUseNonGenericVariant,
|
||||
"Consider using the non-generic variant of this method",
|
||||
"Use the non-generic variant of this method for type {0}",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true,
|
||||
"Use the generic variant of this method.");
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics | GeneratedCodeAnalysisFlags.Analyze);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterOperationAction(CheckForNonGenericVariant, OperationKind.Invocation);
|
||||
}
|
||||
|
||||
private void CheckForNonGenericVariant(OperationAnalysisContext obj)
|
||||
{
|
||||
if (obj.Operation is not IInvocationOperation invocationOperation) return;
|
||||
|
||||
var preferNonGenericAttribute = obj.Compilation.GetTypeByMetadataName(AttributeType);
|
||||
|
||||
HashSet<ITypeSymbol> forTypes = [];
|
||||
foreach (var attribute in invocationOperation.TargetMethod.GetAttributes())
|
||||
{
|
||||
if (!SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, preferNonGenericAttribute))
|
||||
continue;
|
||||
|
||||
foreach (var type in attribute.ConstructorArguments[0].Values)
|
||||
forTypes.Add((ITypeSymbol)type.Value);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (forTypes == null)
|
||||
return;
|
||||
|
||||
foreach (var typeArg in invocationOperation.TargetMethod.TypeArguments)
|
||||
{
|
||||
if (forTypes.Contains(typeArg))
|
||||
{
|
||||
obj.ReportDiagnostic(
|
||||
Diagnostic.Create(UseNonGenericVariantDescriptor,
|
||||
invocationOperation.Syntax.GetLocation(), typeArg.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
75
Robust.Analyzers/PreferOtherTypeAnalyzer.cs
Normal file
75
Robust.Analyzers/PreferOtherTypeAnalyzer.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
#nullable enable
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class PreferOtherTypeAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string AttributeType = "Robust.Shared.Analyzers.PreferOtherTypeAttribute";
|
||||
|
||||
private static readonly DiagnosticDescriptor PreferOtherTypeDescriptor = new(
|
||||
Diagnostics.IdPreferOtherType,
|
||||
"Use the specific type",
|
||||
"Use the specific type {0} instead of {1} when the type argument is {2}",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
"Use the specific type.");
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
|
||||
PreferOtherTypeDescriptor
|
||||
);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics | GeneratedCodeAnalysisFlags.Analyze);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterSyntaxNodeAction(AnalyzeField, SyntaxKind.VariableDeclaration);
|
||||
}
|
||||
|
||||
private void AnalyzeField(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not VariableDeclarationSyntax node)
|
||||
return;
|
||||
|
||||
// Get the type of the generic being used
|
||||
if (node.Type is not GenericNameSyntax genericName)
|
||||
return;
|
||||
var genericSyntax = genericName.TypeArgumentList.Arguments[0];
|
||||
if (context.SemanticModel.GetSymbolInfo(genericSyntax).Symbol is not { } genericType)
|
||||
return;
|
||||
|
||||
// Look for the PreferOtherTypeAttribute
|
||||
var symbolInfo = context.SemanticModel.GetSymbolInfo(node.Type);
|
||||
if (symbolInfo.Symbol?.GetAttributes() is not { } attributes)
|
||||
return;
|
||||
|
||||
var preferOtherTypeAttribute = context.Compilation.GetTypeByMetadataName(AttributeType);
|
||||
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
if (!SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, preferOtherTypeAttribute))
|
||||
continue;
|
||||
|
||||
// See if the generic type argument matches the type the attribute specifies
|
||||
if (attribute.ConstructorArguments[0].Value is not ITypeSymbol checkedType)
|
||||
return;
|
||||
if (!SymbolEqualityComparer.Default.Equals(checkedType, genericType))
|
||||
continue;
|
||||
|
||||
if (attribute.ConstructorArguments[1].Value is not ITypeSymbol replacementType)
|
||||
continue;
|
||||
context.ReportDiagnostic(Diagnostic.Create(PreferOtherTypeDescriptor,
|
||||
context.Node.GetLocation(),
|
||||
replacementType.Name,
|
||||
symbolInfo.Symbol.Name,
|
||||
genericType.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
97
Robust.Analyzers/PreferOtherTypeFixer.cs
Normal file
97
Robust.Analyzers/PreferOtherTypeFixer.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
#nullable enable
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CodeActions;
|
||||
using Microsoft.CodeAnalysis.CodeFixes;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using static Robust.Roslyn.Shared.Diagnostics;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[ExportCodeFixProvider(LanguageNames.CSharp)]
|
||||
public sealed class PreferOtherTypeFixer : CodeFixProvider
|
||||
{
|
||||
private const string PreferOtherTypeAttributeName = "PreferOtherTypeAttribute";
|
||||
|
||||
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
|
||||
IdPreferOtherType
|
||||
);
|
||||
|
||||
public override FixAllProvider GetFixAllProvider()
|
||||
{
|
||||
return WellKnownFixAllProviders.BatchFixer;
|
||||
}
|
||||
|
||||
public override Task RegisterCodeFixesAsync(CodeFixContext context)
|
||||
{
|
||||
foreach (var diagnostic in context.Diagnostics)
|
||||
{
|
||||
switch (diagnostic.Id)
|
||||
{
|
||||
case IdPreferOtherType:
|
||||
return RegisterReplaceType(context, diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static async Task RegisterReplaceType(CodeFixContext context, Diagnostic diagnostic)
|
||||
{
|
||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
|
||||
var span = diagnostic.Location.SourceSpan;
|
||||
var token = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<VariableDeclarationSyntax>().First();
|
||||
|
||||
if (token == null)
|
||||
return;
|
||||
|
||||
context.RegisterCodeFix(CodeAction.Create(
|
||||
"Replace type",
|
||||
c => ReplaceType(context.Document, token, c),
|
||||
"Replace type"
|
||||
), diagnostic);
|
||||
}
|
||||
|
||||
private static async Task<Document> ReplaceType(Document document, VariableDeclarationSyntax syntax, CancellationToken cancellation)
|
||||
{
|
||||
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
|
||||
var model = await document.GetSemanticModelAsync(cancellation);
|
||||
|
||||
if (model == null)
|
||||
return document;
|
||||
|
||||
if (syntax.Type is not GenericNameSyntax genericNameSyntax)
|
||||
return document;
|
||||
var genericTypeSyntax = genericNameSyntax.TypeArgumentList.Arguments[0];
|
||||
if (model.GetSymbolInfo(genericTypeSyntax).Symbol is not {} genericTypeSymbol)
|
||||
return document;
|
||||
|
||||
var symbolInfo = model.GetSymbolInfo(syntax.Type);
|
||||
if (symbolInfo.Symbol?.GetAttributes() is not { } attributes)
|
||||
return document;
|
||||
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
if (attribute.AttributeClass?.Name != PreferOtherTypeAttributeName)
|
||||
continue;
|
||||
|
||||
if (attribute.ConstructorArguments[0].Value is not ITypeSymbol checkedTypeSymbol)
|
||||
continue;
|
||||
|
||||
if (!SymbolEqualityComparer.Default.Equals(checkedTypeSymbol, genericTypeSymbol))
|
||||
continue;
|
||||
|
||||
if (attribute.ConstructorArguments[1].Value is not ITypeSymbol replacementTypeSymbol)
|
||||
continue;
|
||||
|
||||
var replacementIdentifier = SyntaxFactory.IdentifierName(replacementTypeSymbol.Name);
|
||||
var replacementSyntax = syntax.WithType(replacementIdentifier);
|
||||
|
||||
root = root!.ReplaceNode(syntax, replacementSyntax);
|
||||
return document.WithSyntaxRoot(root);
|
||||
}
|
||||
|
||||
return document;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,23 @@
|
||||
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Needed for PreferNonGenericVariantAnalyzer. -->
|
||||
<Compile Include="..\Robust.Shared\Analyzers\PreferNonGenericVariantForAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Needed for PreferOtherTypeAnalyzer. -->
|
||||
<Compile Include="..\Robust.Shared\Analyzers\PreferOtherTypeAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Needed for DataDefinitionAnalyzer. -->
|
||||
<Compile Include="..\Robust.Shared\Serialization\Manager\Definition\DataDefinitionUtility.cs" LinkBase="Implementations" />
|
||||
<Compile Include="..\Robust.Shared\ViewVariables\ViewVariablesAttribute.cs" LinkBase="Implementations" />
|
||||
<Compile Include="..\Robust.Shared\Serialization\NetSerializableAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -26,7 +26,8 @@ public sealed class DefaultSQLConfig : IConfig
|
||||
|
||||
public IEnumerable<IExporter> GetExporters()
|
||||
{
|
||||
yield return SQLExporter.Default;
|
||||
//yield return SQLExporter.Default;
|
||||
yield break;
|
||||
}
|
||||
|
||||
public IEnumerable<IColumnProvider> GetColumnProviders() => DefaultConfig.Instance.GetColumnProviders();
|
||||
|
||||
@@ -15,11 +15,10 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Npgsql;
|
||||
using Npgsql.Internal;
|
||||
using Npgsql.Internal.TypeHandlers;
|
||||
using Npgsql.Internal.TypeHandling;
|
||||
|
||||
namespace Robust.Benchmarks.Exporters;
|
||||
|
||||
/*
|
||||
public sealed class SQLExporter : IExporter
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions
|
||||
@@ -98,7 +97,9 @@ public sealed class SQLExporter : IExporter
|
||||
|
||||
public string Name => "sql";
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
// https://github.com/npgsql/efcore.pg/issues/1107#issuecomment-945126627
|
||||
class JsonOverrideTypeHandlerResolverFactory : TypeHandlerResolverFactory
|
||||
{
|
||||
@@ -138,6 +139,7 @@ class JsonOverrideTypeHandlerResolverFactory : TypeHandlerResolverFactory
|
||||
=> null; // Let the built-in resolver do this
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public sealed class DesignTimeContextFactoryPostgres : IDesignTimeDbContextFactory<BenchmarkContext>
|
||||
{
|
||||
|
||||
@@ -239,6 +239,7 @@ namespace Robust.Build.Tasks
|
||||
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
|
||||
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
|
||||
}
|
||||
res.Remove();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -302,7 +302,7 @@ internal partial class AudioManager
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IBufferedAudioSource? IAudioInternal.CreateBufferedAudioSource(int buffers, bool floatAudio=false)
|
||||
IBufferedAudioSource? IAudioInternal.CreateBufferedAudioSource(int buffers, bool floatAudio)
|
||||
{
|
||||
var source = AL.GenSource();
|
||||
|
||||
|
||||
@@ -143,12 +143,11 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
/// <summary>
|
||||
/// Like _checkAlError but allows custom data to be passed in as relevant.
|
||||
/// </summary>
|
||||
internal void LogALError(string message, [CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1)
|
||||
internal void LogALError(ALErrorInterpolatedStringHandler message, [CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = AL.GetError();
|
||||
if (error != ALError.NoError)
|
||||
if (message.Error != ALError.NoError)
|
||||
{
|
||||
OpenALSawmill.Error("[{0}:{1}] AL error: {2}, {3}. Stacktrace is {4}", callerMember, callerLineNumber, error, message, Environment.StackTrace);
|
||||
OpenALSawmill.Error("[{0}:{1}] AL error: {2}, {3}. Stacktrace is {4}", callerMember, callerLineNumber, message.Error, message.ToStringAndClear(), Environment.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,4 +169,32 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
BufferHandle = bufferHandle;
|
||||
}
|
||||
}
|
||||
|
||||
[InterpolatedStringHandler]
|
||||
internal ref struct ALErrorInterpolatedStringHandler
|
||||
{
|
||||
private DefaultInterpolatedStringHandler _handler;
|
||||
public ALError Error;
|
||||
|
||||
public ALErrorInterpolatedStringHandler(int literalLength, int formattedCount, out bool shouldAppend)
|
||||
{
|
||||
Error = AL.GetError();
|
||||
if (Error == ALError.NoError)
|
||||
{
|
||||
shouldAppend = false;
|
||||
_handler = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldAppend = true;
|
||||
_handler = new DefaultInterpolatedStringHandler(literalLength, formattedCount);
|
||||
}
|
||||
}
|
||||
|
||||
public string ToStringAndClear() => _handler.ToStringAndClear();
|
||||
public override string ToString() => _handler.ToString();
|
||||
public void AppendLiteral(string value) => _handler.AppendLiteral(value);
|
||||
public void AppendFormatted<T>(T value) => _handler.AppendFormatted(value);
|
||||
public void AppendFormatted<T>(T value, string? format) => _handler.AppendFormatted(value, format);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public sealed class AudioOverlay : Overlay
|
||||
|
||||
var screenHandle = args.ScreenHandle;
|
||||
var output = new StringBuilder();
|
||||
var listenerPos = _entManager.GetComponent<TransformComponent>(localPlayer.Value).MapPosition;
|
||||
var listenerPos = _transform.GetMapCoordinates(_entManager.GetComponent<TransformComponent>(localPlayer.Value));
|
||||
|
||||
if (listenerPos.MapId != args.MapId)
|
||||
return;
|
||||
|
||||
@@ -37,11 +37,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IParallelManager _parMan = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
[Dependency] private readonly IAudioInternal _audio = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
[Dependency] private readonly SharedMapSystem _maps = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
|
||||
@@ -49,9 +48,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// Per-tick cache of relevant streams.
|
||||
/// </summary>
|
||||
private readonly List<(EntityUid Entity, AudioComponent Component, TransformComponent Xform)> _streams = new();
|
||||
private EntityUid? _listenerGrid;
|
||||
private UpdateAudioJob _updateAudioJob;
|
||||
|
||||
private float _audioFrameTime;
|
||||
private float _audioFrameTimeRemaining;
|
||||
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
|
||||
@@ -110,9 +110,16 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
Subs.CVar(CfgManager, CVars.AudioAttenuation, OnAudioAttenuation, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioTickRate, OnAudioTickRate, true);
|
||||
InitializeLimit();
|
||||
}
|
||||
|
||||
private void OnAudioTickRate(int obj)
|
||||
{
|
||||
_audioFrameTime = 1f / obj;
|
||||
_audioFrameTimeRemaining = MathF.Min(_audioFrameTimeRemaining, _audioFrameTime);
|
||||
}
|
||||
|
||||
private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
ApplyAudioParams(component.Params, component);
|
||||
@@ -216,11 +223,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
}
|
||||
|
||||
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
|
||||
{
|
||||
_metadata.SetFlag(entity.Owner, MetaDataFlags.Undetachable, true);
|
||||
}
|
||||
|
||||
// Need to set all initial data for first frame.
|
||||
ApplyAudioParams(component.Params, component);
|
||||
component.Source.Global = component.Global;
|
||||
@@ -259,6 +261,13 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
_audioFrameTimeRemaining -= frameTime;
|
||||
|
||||
if (_audioFrameTimeRemaining > 0f)
|
||||
return;
|
||||
|
||||
// Clamp to 0 in case we have a really long frame.
|
||||
_audioFrameTimeRemaining = MathF.Max(0f, _audioFrameTime + _audioFrameTimeRemaining);
|
||||
var eye = _eyeManager.CurrentEye;
|
||||
var localEntity = _playerManager.LocalEntity;
|
||||
Vector2 listenerVelocity;
|
||||
@@ -282,9 +291,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
_streams.Add((uid, comp, xform));
|
||||
}
|
||||
|
||||
_mapManager.TryFindGridAt(ourPos, out var gridUid, out _);
|
||||
_listenerGrid = gridUid == EntityUid.Invalid ? null : gridUid;
|
||||
|
||||
try
|
||||
{
|
||||
_updateAudioJob.OurPosition = ourPos;
|
||||
@@ -336,52 +342,18 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
Vector2 worldPos;
|
||||
var gridUid = xform.ParentUid;
|
||||
component.Volume = component.Params.Volume;
|
||||
|
||||
// Handle grid audio differently by using nearest-edge instead of entity centre.
|
||||
// Handle grid audio differently by using grid position.
|
||||
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
|
||||
{
|
||||
// It's our grid so max volume.
|
||||
if (_listenerGrid == gridUid)
|
||||
{
|
||||
component.Volume = component.Params.Volume;
|
||||
component.Occlusion = 0f;
|
||||
component.Position = listener.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Need a grid-optimised version because this is gonna be expensive.
|
||||
// Just to avoid clipping on and off grid or nearestPoint changing we'll
|
||||
// always set the sound to listener's pos, we'll just manually do gain ourselves.
|
||||
if (_physics.TryGetNearest(gridUid, listener, out _, out var gridDistance))
|
||||
{
|
||||
// Out of range
|
||||
if (gridDistance > component.MaxDistance)
|
||||
{
|
||||
component.Gain = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
var paramsGain = VolumeToGain(component.Params.Volume);
|
||||
|
||||
// Thought I'd never have to manually calculate gain again but this is the least
|
||||
// unpleasant audio I could get at the moment.
|
||||
component.Gain = paramsGain * _audio.GetAttenuationGain(
|
||||
gridDistance,
|
||||
component.Params.RolloffFactor,
|
||||
component.Params.ReferenceDistance,
|
||||
component.Params.MaxDistance);
|
||||
component.Position = listener.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
// Can't get nearest point so don't play anymore.
|
||||
component.Gain = 0f;
|
||||
return;
|
||||
var parentUid = xform.ParentUid;
|
||||
worldPos = _maps.GetGridPosition(parentUid);
|
||||
}
|
||||
else
|
||||
{
|
||||
worldPos = _xformSys.GetWorldPosition(entity);
|
||||
}
|
||||
|
||||
worldPos = _xformSys.GetWorldPosition(entity);
|
||||
component.Volume = component.Params.Volume;
|
||||
|
||||
// Max distance check
|
||||
var delta = worldPos - listener.Position;
|
||||
@@ -403,8 +375,15 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
// Update audio occlusion
|
||||
var occlusion = GetOcclusion(listener, delta, distance, entity);
|
||||
component.Occlusion = occlusion;
|
||||
if ((component.Flags & AudioFlags.NoOcclusion) == AudioFlags.NoOcclusion)
|
||||
{
|
||||
component.Occlusion = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
var occlusion = GetOcclusion(listener, delta, distance, entity);
|
||||
component.Occlusion = occlusion;
|
||||
}
|
||||
|
||||
// Update audio positions.
|
||||
component.Position = worldPos;
|
||||
@@ -671,10 +650,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
private (EntityUid Entity, AudioComponent Component) CreateAndStartPlayingStream(AudioParams? audioParams, AudioStream stream)
|
||||
{
|
||||
var audioP = audioParams ?? AudioParams.Default;
|
||||
var entity = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace);
|
||||
var comp = SetupAudio(entity, null, audioP, stream.Length);
|
||||
LoadStream((entity, comp), stream);
|
||||
var entity = SetupAudio(null, audioP, initialize: false, length: stream.Length);
|
||||
LoadStream(entity, stream);
|
||||
EntityManager.InitializeAndStartEntity(entity);
|
||||
var comp = entity.Comp;
|
||||
var source = comp.Source;
|
||||
|
||||
// TODO clamp the offset inside of SetPlaybackPosition() itself.
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Audio.Sources;
|
||||
|
||||
internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSource
|
||||
{
|
||||
private int? SourceHandle = null;
|
||||
private int[] BufferHandles;
|
||||
private Dictionary<int, int> BufferMap = new();
|
||||
private readonly AudioManager _master;
|
||||
private bool _mono = true;
|
||||
private bool _float = false;
|
||||
private int FilterHandle;
|
||||
|
||||
public int SampleRate { get; set; } = 44100;
|
||||
|
||||
@@ -43,7 +37,7 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
var state = AL.GetSourceState(SourceHandle!.Value);
|
||||
var state = AL.GetSourceState(SourceHandle);
|
||||
_master._checkAlError();
|
||||
return state == ALSourceState.Playing;
|
||||
}
|
||||
@@ -53,7 +47,7 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
{
|
||||
_checkDisposed();
|
||||
// IDK why this stackallocs but gonna leave it for now.
|
||||
AL.SourcePlay(stackalloc int[] {SourceHandle!.Value});
|
||||
AL.SourcePlay(stackalloc int[] {SourceHandle});
|
||||
_master._checkAlError();
|
||||
}
|
||||
else
|
||||
@@ -61,7 +55,7 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
if (_isDisposed())
|
||||
return;
|
||||
|
||||
AL.SourceStop(SourceHandle!.Value);
|
||||
AL.SourceStop(SourceHandle);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -74,13 +68,13 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (SourceHandle == null)
|
||||
if (SourceHandle == -1)
|
||||
return;
|
||||
|
||||
if (!_master.IsMainThread())
|
||||
{
|
||||
// We can't run this code inside another thread so tell Clyde to clear it up later.
|
||||
_master.DeleteBufferedSourceOnMainThread(SourceHandle.Value, FilterHandle);
|
||||
_master.DeleteBufferedSourceOnMainThread(SourceHandle, FilterHandle);
|
||||
|
||||
foreach (var handle in BufferHandles)
|
||||
{
|
||||
@@ -92,21 +86,21 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
if (FilterHandle != 0)
|
||||
EFX.DeleteFilter(FilterHandle);
|
||||
|
||||
AL.DeleteSource(SourceHandle.Value);
|
||||
AL.DeleteSource(SourceHandle);
|
||||
AL.DeleteBuffers(BufferHandles);
|
||||
_master.RemoveBufferedAudioSource(SourceHandle.Value);
|
||||
_master.RemoveBufferedAudioSource(SourceHandle);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
FilterHandle = 0;
|
||||
SourceHandle = null;
|
||||
SourceHandle = -1;
|
||||
}
|
||||
|
||||
public int GetNumberOfBuffersProcessed()
|
||||
{
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.GetSource(SourceHandle!.Value, ALGetSourcei.BuffersProcessed, out var buffersProcessed);
|
||||
AL.GetSource(SourceHandle, ALGetSourcei.BuffersProcessed, out var buffersProcessed);
|
||||
return buffersProcessed;
|
||||
}
|
||||
|
||||
@@ -116,7 +110,7 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
var entries = Math.Min(Math.Min(handles.Length, BufferHandles.Length), GetNumberOfBuffersProcessed());
|
||||
fixed (int* ptr = handles)
|
||||
{
|
||||
AL.SourceUnqueueBuffers(SourceHandle!.Value, entries, ptr);
|
||||
AL.SourceUnqueueBuffers(SourceHandle, entries, ptr);
|
||||
}
|
||||
|
||||
for (var i = 0; i < entries; i++)
|
||||
@@ -183,7 +177,7 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
fixed (int* ptr = realHandles)
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
{
|
||||
AL.SourceQueueBuffers(SourceHandle!.Value, handles.Length, ptr);
|
||||
AL.SourceQueueBuffers(SourceHandle, handles.Length, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -291,9 +291,9 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SnapGridGetCell : LocalizedCommands
|
||||
internal sealed class SnapGridGetCell : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
public override string Command => "sggcell";
|
||||
|
||||
@@ -319,9 +319,10 @@ namespace Robust.Client.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (_entManager.TryGetComponent<MapGridComponent>(_entManager.GetEntity(gridNet), out var grid))
|
||||
var gridEnt = EntityManager.GetEntity(gridNet);
|
||||
if (EntityManager.TryGetComponent<MapGridComponent>(gridEnt, out var grid))
|
||||
{
|
||||
foreach (var entity in grid.GetAnchoredEntities(new Vector2i(
|
||||
foreach (var entity in _map.GetAnchoredEntities(gridEnt, grid, new Vector2i(
|
||||
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
|
||||
int.Parse(indices.Split(',')[1], CultureInfo.InvariantCulture))))
|
||||
{
|
||||
@@ -425,9 +426,9 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class GridTileCount : LocalizedCommands
|
||||
internal sealed class GridTileCount : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
public override string Command => "gridtc";
|
||||
|
||||
@@ -440,15 +441,15 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
|
||||
if (!NetEntity.TryParse(args[0], out var gridUidNet) ||
|
||||
!_entManager.TryGetEntity(gridUidNet, out var gridUid))
|
||||
!EntityManager.TryGetEntity(gridUidNet, out var gridUid))
|
||||
{
|
||||
shell.WriteLine($"{args[0]} is not a valid entity UID.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_entManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
|
||||
if (EntityManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
|
||||
{
|
||||
shell.WriteLine(grid.GetAllTiles().Count().ToString());
|
||||
shell.WriteLine(_map.GetAllTiles(gridUid.Value, grid).Count().ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -578,7 +579,20 @@ namespace Robust.Client.Console.Commands
|
||||
private static string GetMemberValue(MemberInfo? member, Control control, string separator, string
|
||||
wrap = "{0}")
|
||||
{
|
||||
var value = member?.GetValue(control);
|
||||
object? value = null;
|
||||
try
|
||||
{
|
||||
value = member?.GetValue(control);
|
||||
}
|
||||
catch (TargetInvocationException exception)
|
||||
{
|
||||
var exceptionToPrint = exception.InnerException ?? exception;
|
||||
value = $"{exceptionToPrint.GetType()}: {exceptionToPrint.Message}";
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
value = $"{exception.GetType()}: {exception.Message}";
|
||||
}
|
||||
var o = value switch
|
||||
{
|
||||
ICollection<Control> controls => string.Join(separator,
|
||||
@@ -680,12 +694,12 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ChunkInfoCommand : LocalizedCommands
|
||||
internal sealed class ChunkInfoCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IEyeManager _eye = default!;
|
||||
[Dependency] private readonly IInputManager _input = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
|
||||
public override string Command => "chunkinfo";
|
||||
|
||||
@@ -699,8 +713,8 @@ namespace Robust.Client.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var mapSystem = _entManager.System<SharedMapSystem>();
|
||||
var chunkIndex = mapSystem.LocalToChunkIndices(gridUid, grid, grid.MapToGrid(mousePos));
|
||||
var mapSystem = EntityManager.System<SharedMapSystem>();
|
||||
var chunkIndex = mapSystem.LocalToChunkIndices(gridUid, grid, _mapSystem.MapToGrid(gridUid, mousePos));
|
||||
var chunk = mapSystem.GetOrAddChunk(gridUid, grid, chunkIndex);
|
||||
|
||||
shell.WriteLine($"worldBounds: {mapSystem.CalcWorldAABB(gridUid, grid, chunk)} localBounds: {chunk.CachedBounds}");
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public sealed class GridChunkBBCommand : LocalizedCommands
|
||||
public sealed class GridChunkBBCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly GridChunkBoundsDebugSystem _system = default!;
|
||||
|
||||
public override string Command => "showchunkbb";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<GridChunkBoundsDebugSystem>().Enabled ^= true;
|
||||
_system.Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
|
||||
private Control TabRichText()
|
||||
{
|
||||
var label = new RichTextLabel();
|
||||
label.SetMessage(FormattedMessage.FromMarkup(Lipsum));
|
||||
label.SetMessage(FormattedMessage.FromMarkupOrThrow(Lipsum));
|
||||
|
||||
TabContainer.SetTabTitle(label, "RichText");
|
||||
return label;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -14,6 +15,8 @@ namespace Robust.Client.Debugging
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
|
||||
private bool _debugPositions;
|
||||
private bool _debugRotations;
|
||||
@@ -35,7 +38,7 @@ namespace Robust.Client.Debugging
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, EntityManager));
|
||||
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, EntityManager, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -74,13 +77,15 @@ namespace Robust.Client.Debugging
|
||||
{
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly SharedTransformSystem _transform;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityPositionOverlay(EntityLookupSystem lookup, IEntityManager entityManager)
|
||||
public EntityPositionOverlay(EntityLookupSystem lookup, IEntityManager entityManager, SharedTransformSystem transform)
|
||||
{
|
||||
_lookup = lookup;
|
||||
_entityManager = entityManager;
|
||||
_transform = transform;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
@@ -88,11 +93,10 @@ namespace Robust.Client.Debugging
|
||||
const float stubLength = 0.25f;
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var entity in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
var (center, worldRotation) = xformQuery.GetComponent(entity).GetWorldPositionRotation();
|
||||
var (center, worldRotation) = _transform.GetWorldPositionRotation(entity);
|
||||
|
||||
var xLine = worldRotation.RotateVec(Vector2.UnitX);
|
||||
var yLine = worldRotation.RotateVec(Vector2.UnitY);
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
@@ -78,6 +79,14 @@ namespace Robust.Client.Debugging
|
||||
internal int PointCount;
|
||||
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly IEyeManager _eye = default!;
|
||||
[Dependency] private readonly IInputManager _input = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
internal ContactPoint[] Points = new ContactPoint[MaxContactPoints];
|
||||
|
||||
@@ -89,20 +98,21 @@ namespace Robust.Client.Debugging
|
||||
if (value == _flags) return;
|
||||
|
||||
if (_flags == PhysicsDebugFlags.None)
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(
|
||||
_overlay.AddOverlay(
|
||||
new PhysicsDebugOverlay(
|
||||
EntityManager,
|
||||
IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IInputManager>(),
|
||||
IoCManager.Resolve<IMapManager>(),
|
||||
IoCManager.Resolve<IPlayerManager>(),
|
||||
IoCManager.Resolve<IResourceCache>(),
|
||||
_eye,
|
||||
_input,
|
||||
_map,
|
||||
_player,
|
||||
_resourceCache,
|
||||
this,
|
||||
Get<EntityLookupSystem>(),
|
||||
Get<SharedPhysicsSystem>()));
|
||||
_entityLookup,
|
||||
_physics,
|
||||
_transform));
|
||||
|
||||
if (value == PhysicsDebugFlags.None)
|
||||
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsDebugOverlay));
|
||||
_overlay.RemoveOverlay(typeof(PhysicsDebugOverlay));
|
||||
|
||||
_flags = value;
|
||||
}
|
||||
@@ -198,6 +208,7 @@ namespace Robust.Client.Debugging
|
||||
private readonly DebugPhysicsSystem _debugPhysicsSystem;
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly SharedPhysicsSystem _physicsSystem;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
|
||||
|
||||
@@ -208,7 +219,7 @@ namespace Robust.Client.Debugging
|
||||
private HashSet<Joint> _drawnJoints = new();
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IMapManager mapManager, IPlayerManager playerManager, IResourceCache cache, DebugPhysicsSystem system, EntityLookupSystem lookup, SharedPhysicsSystem physicsSystem)
|
||||
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IMapManager mapManager, IPlayerManager playerManager, IResourceCache cache, DebugPhysicsSystem system, EntityLookupSystem lookup, SharedPhysicsSystem physicsSystem, SharedTransformSystem transformSystem)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
@@ -218,6 +229,7 @@ namespace Robust.Client.Debugging
|
||||
_debugPhysicsSystem = system;
|
||||
_lookup = lookup;
|
||||
_physicsSystem = physicsSystem;
|
||||
_transformSystem = transformSystem;
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
@@ -225,7 +237,7 @@ namespace Robust.Client.Debugging
|
||||
{
|
||||
var viewBounds = args.WorldBounds;
|
||||
var viewAABB = args.WorldAABB;
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var mapId = args.MapId;
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Shapes) != 0)
|
||||
{
|
||||
@@ -327,7 +339,7 @@ namespace Robust.Client.Debugging
|
||||
{
|
||||
if (jointComponent.JointCount == 0 ||
|
||||
!_entityManager.TryGetComponent(uid, out TransformComponent? xf1) ||
|
||||
!viewAABB.Contains(xf1.WorldPosition)) continue;
|
||||
!viewAABB.Contains(_transformSystem.GetWorldPosition(xf1))) continue;
|
||||
|
||||
foreach (var (_, joint) in jointComponent.Joints)
|
||||
{
|
||||
@@ -368,12 +380,12 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
|
||||
worldHandle.UseShader(null);
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
private void DrawScreen(DrawingHandleScreen screenHandle, OverlayDrawArgs args)
|
||||
{
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var mapId = args.MapId;
|
||||
var mousePos = _inputManager.MouseScreenPosition;
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0)
|
||||
@@ -443,7 +455,7 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
|
||||
screenHandle.UseShader(null);
|
||||
screenHandle.SetTransform(Matrix3.Identity);
|
||||
screenHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
@@ -517,17 +529,17 @@ namespace Robust.Client.Debugging
|
||||
if (!_entityManager.TryGetComponent(joint.BodyAUid, out TransformComponent? xform1) ||
|
||||
!_entityManager.TryGetComponent(joint.BodyBUid, out TransformComponent? xform2)) return;
|
||||
|
||||
var matrix1 = xform1.WorldMatrix;
|
||||
var matrix2 = xform2.WorldMatrix;
|
||||
var matrix1 = _transformSystem.GetWorldMatrix(xform1);
|
||||
var matrix2 = _transformSystem.GetWorldMatrix(xform2);
|
||||
|
||||
var xf1 = new Vector2(matrix1.R0C2, matrix1.R1C2);
|
||||
var xf2 = new Vector2(matrix2.R0C2, matrix2.R1C2);
|
||||
var xf1 = new Vector2(matrix1.M31, matrix1.M32);
|
||||
var xf2 = new Vector2(matrix2.M31, matrix2.M32);
|
||||
|
||||
var p1 = matrix1.Transform(joint.LocalAnchorA);
|
||||
var p2 = matrix2.Transform(joint.LocalAnchorB);
|
||||
var p1 = Vector2.Transform(joint.LocalAnchorA, matrix1);
|
||||
var p2 = Vector2.Transform(joint.LocalAnchorB, matrix2);
|
||||
|
||||
var xfa = new Transform(xf1, xform1.WorldRotation);
|
||||
var xfb = new Transform(xf2, xform2.WorldRotation);
|
||||
var xfa = new Transform(xf1, _transformSystem.GetWorldRotation(xform1));
|
||||
var xfb = new Transform(xf2, _transformSystem.GetWorldRotation(xform2));
|
||||
|
||||
switch (joint)
|
||||
{
|
||||
|
||||
@@ -26,6 +26,9 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
||||
|
||||
internal event Action? AfterStartup;
|
||||
internal event Action? AfterShutdown;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetupNetworking();
|
||||
@@ -34,6 +37,20 @@ namespace Robust.Client.GameObjects
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
public override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
AfterStartup?.Invoke();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
AfterShutdown?.Invoke();
|
||||
}
|
||||
|
||||
public override void FlushEntities()
|
||||
{
|
||||
// Server doesn't network deletions on client shutdown so we need to
|
||||
@@ -48,16 +65,6 @@ namespace Robust.Client.GameObjects
|
||||
return base.CreateEntity(prototypeName, out metadata);
|
||||
}
|
||||
|
||||
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity, MetaDataComponent? meta)
|
||||
{
|
||||
base.InitializeEntity(entity, meta);
|
||||
}
|
||||
|
||||
void IClientEntityManagerInternal.StartEntity(EntityUid entity)
|
||||
{
|
||||
base.StartEntity(entity);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void DirtyEntity(EntityUid uid, MetaDataComponent? meta = null)
|
||||
{
|
||||
@@ -128,7 +135,10 @@ namespace Robust.Client.GameObjects
|
||||
var sequence = _stateMan.SystemMessageDispatched(msg);
|
||||
EntityNetManager?.SendSystemNetworkMessage(msg, sequence);
|
||||
|
||||
DebugTools.Assert(!_stateMan.IsPredictionEnabled || _gameTiming.InPrediction && _gameTiming.IsFirstTimePredicted || _client.RunLevel != ClientRunLevel.Connected);
|
||||
if (!_stateMan.IsPredictionEnabled && _client.RunLevel != ClientRunLevel.SinglePlayerGame)
|
||||
return;
|
||||
|
||||
DebugTools.Assert(_gameTiming.InPrediction && _gameTiming.IsFirstTimePredicted || _client.RunLevel == ClientRunLevel.SinglePlayerGame);
|
||||
|
||||
var eventArgs = new EntitySessionEventArgs(session!);
|
||||
EventBus.RaiseEvent(EventSource.Local, msg);
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace Robust.Client.GameObjects
|
||||
[DataField("color")]
|
||||
private Color color = Color.White;
|
||||
|
||||
public Matrix3 LocalMatrix = Matrix3.Identity;
|
||||
public Matrix3x2 LocalMatrix = Matrix3x2.Identity;
|
||||
|
||||
[Animatable]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
@@ -389,10 +389,10 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
internal void UpdateLocalMatrix()
|
||||
{
|
||||
LocalMatrix = Matrix3.CreateTransform(in offset, in rotation, in scale);
|
||||
LocalMatrix = Matrix3Helpers.CreateTransform(in offset, in rotation, in scale);
|
||||
}
|
||||
|
||||
public Matrix3 GetLocalMatrix()
|
||||
public Matrix3x2 GetLocalMatrix()
|
||||
{
|
||||
return LocalMatrix;
|
||||
}
|
||||
@@ -1304,22 +1304,21 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
// worldRotation + eyeRotation should be the angle of the entity on-screen. If no-rot is enabled this is just set to zero.
|
||||
// However, at some point later the eye-matrix is applied separately, so we subtract -eye rotation for now:
|
||||
var entityMatrix = Matrix3.CreateTransform(worldPosition, NoRotation ? -eyeRotation : worldRotation - cardinal);
|
||||
var entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, NoRotation ? -eyeRotation : worldRotation - cardinal);
|
||||
|
||||
Matrix3.Multiply(in LocalMatrix, in entityMatrix, out var transformSprite);
|
||||
var transformSprite = Matrix3x2.Multiply(LocalMatrix, entityMatrix);
|
||||
|
||||
if (GranularLayersRendering)
|
||||
{
|
||||
//Default rendering
|
||||
entityMatrix = Matrix3.CreateTransform(worldPosition, worldRotation);
|
||||
Matrix3.Multiply(in LocalMatrix, in entityMatrix, out var transformDefault);
|
||||
entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, worldRotation);
|
||||
var transformDefault = Matrix3x2.Multiply(LocalMatrix, entityMatrix);
|
||||
//Snap to cardinals
|
||||
entityMatrix = Matrix3.CreateTransform(worldPosition, worldRotation - angle.GetCardinalDir().ToAngle());
|
||||
Matrix3.Multiply(in LocalMatrix, in entityMatrix, out var transformSnap);
|
||||
entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, worldRotation - angle.GetCardinalDir().ToAngle());
|
||||
var transformSnap = Matrix3x2.Multiply(LocalMatrix, entityMatrix);
|
||||
//No rotation
|
||||
entityMatrix = Matrix3.CreateTransform(worldPosition, -eyeRotation);
|
||||
Matrix3.Multiply(in LocalMatrix, in entityMatrix, out var transformNoRot);
|
||||
|
||||
entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, -eyeRotation);
|
||||
var transformNoRot = Matrix3x2.Multiply(LocalMatrix, entityMatrix);
|
||||
|
||||
foreach (var layer in Layers) {
|
||||
switch (layer.RenderingStrategy)
|
||||
@@ -1380,7 +1379,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
// TODO whenever sprite comp gets ECS'd , just make this a direct method call.
|
||||
var ev = new QueueSpriteTreeUpdateEvent(entities.GetComponent<TransformComponent>(Owner));
|
||||
entities.EventBus.RaiseComponentEvent(this, ref ev);
|
||||
entities.EventBus.RaiseComponentEvent(Owner, this, ref ev);
|
||||
}
|
||||
|
||||
private void QueueUpdateIsInert()
|
||||
@@ -1390,7 +1389,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
// TODO whenever sprite comp gets ECS'd , just make this a direct method call.
|
||||
var ev = new SpriteUpdateInertEvent();
|
||||
entities.EventBus.RaiseComponentEvent(this, ref ev);
|
||||
entities.EventBus.RaiseComponentEvent(Owner, this, ref ev);
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem instead.")]
|
||||
@@ -1546,7 +1545,7 @@ namespace Robust.Client.GameObjects
|
||||
private RSI.State? _actualState;
|
||||
[ViewVariables] public RSI.State? ActualState => _actualState;
|
||||
|
||||
public Matrix3 LocalMatrix = Matrix3.Identity;
|
||||
public Matrix3x2 LocalMatrix = Matrix3x2.Identity;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 Scale
|
||||
@@ -1681,6 +1680,8 @@ namespace Robust.Client.GameObjects
|
||||
DirOffset = toClone.DirOffset;
|
||||
_autoAnimated = toClone._autoAnimated;
|
||||
RenderingStrategy = toClone.RenderingStrategy;
|
||||
if (toClone.CopyToShaderParameters is { } copyToShaderParameters)
|
||||
CopyToShaderParameters = new CopyToShaderParameters(copyToShaderParameters);
|
||||
}
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
@@ -1690,7 +1691,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
internal void UpdateLocalMatrix()
|
||||
{
|
||||
LocalMatrix = Matrix3.CreateTransform(in _offset, in _rotation, in _scale);
|
||||
LocalMatrix = Matrix3Helpers.CreateTransform(in _offset, in _rotation, in _scale);
|
||||
}
|
||||
|
||||
RSI? ISpriteLayer.Rsi { get => RSI; set => SetRsi(value); }
|
||||
@@ -1962,27 +1963,27 @@ namespace Robust.Client.GameObjects
|
||||
/// Given the apparent rotation of an entity on screen (world + eye rotation), get layer's matrix for drawing &
|
||||
/// relevant RSI direction.
|
||||
/// </summary>
|
||||
public void GetLayerDrawMatrix(RsiDirection dir, out Matrix3 layerDrawMatrix)
|
||||
public void GetLayerDrawMatrix(RsiDirection dir, out Matrix3x2 layerDrawMatrix)
|
||||
{
|
||||
if (_parent.NoRotation || dir == RsiDirection.South)
|
||||
layerDrawMatrix = LocalMatrix;
|
||||
else
|
||||
{
|
||||
Matrix3.Multiply(in _rsiDirectionMatrices[(int)dir], in LocalMatrix, out layerDrawMatrix);
|
||||
layerDrawMatrix = Matrix3x2.Multiply(_rsiDirectionMatrices[(int)dir], LocalMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
private static Matrix3[] _rsiDirectionMatrices = new Matrix3[]
|
||||
private static Matrix3x2[] _rsiDirectionMatrices = new Matrix3x2[]
|
||||
{
|
||||
// array order chosen such that this array can be indexed by casing an RSI direction to an int
|
||||
Matrix3.Identity, // should probably just avoid matrix multiplication altogether if the direction is south.
|
||||
Matrix3.CreateRotation(-Direction.North.ToAngle()),
|
||||
Matrix3.CreateRotation(-Direction.East.ToAngle()),
|
||||
Matrix3.CreateRotation(-Direction.West.ToAngle()),
|
||||
Matrix3.CreateRotation(-Direction.SouthEast.ToAngle()),
|
||||
Matrix3.CreateRotation(-Direction.SouthWest.ToAngle()),
|
||||
Matrix3.CreateRotation(-Direction.NorthEast.ToAngle()),
|
||||
Matrix3.CreateRotation(-Direction.NorthWest.ToAngle())
|
||||
Matrix3x2.Identity, // should probably just avoid matrix multiplication altogether if the direction is south.
|
||||
Matrix3Helpers.CreateRotation(-Direction.North.ToAngle()),
|
||||
Matrix3Helpers.CreateRotation(-Direction.East.ToAngle()),
|
||||
Matrix3Helpers.CreateRotation(-Direction.West.ToAngle()),
|
||||
Matrix3Helpers.CreateRotation(-Direction.SouthEast.ToAngle()),
|
||||
Matrix3Helpers.CreateRotation(-Direction.SouthWest.ToAngle()),
|
||||
Matrix3Helpers.CreateRotation(-Direction.NorthEast.ToAngle()),
|
||||
Matrix3Helpers.CreateRotation(-Direction.NorthWest.ToAngle())
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -2016,7 +2017,7 @@ namespace Robust.Client.GameObjects
|
||||
/// <summary>
|
||||
/// Render a layer. This assumes that the input angle is between 0 and 2pi.
|
||||
/// </summary>
|
||||
internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3 spriteMatrix, Angle angle, Direction? overrideDirection)
|
||||
internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3x2 spriteMatrix, Angle angle, Direction? overrideDirection)
|
||||
{
|
||||
if (!Visible || Blank)
|
||||
return;
|
||||
@@ -2038,7 +2039,7 @@ namespace Robust.Client.GameObjects
|
||||
if (CopyToShaderParameters == null)
|
||||
{
|
||||
// Set the drawing transform for this layer
|
||||
Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix);
|
||||
var transformMatrix = Matrix3x2.Multiply(layerMatrix, spriteMatrix);
|
||||
drawingHandle.SetTransform(in transformMatrix);
|
||||
|
||||
RenderTexture(drawingHandle, texture);
|
||||
@@ -2155,6 +2156,12 @@ namespace Robust.Client.GameObjects
|
||||
public object LayerKey = layerKey;
|
||||
public string? ParameterTexture;
|
||||
public string? ParameterUV;
|
||||
|
||||
public CopyToShaderParameters(CopyToShaderParameters toClone) : this(toClone.LayerKey)
|
||||
{
|
||||
ParameterTexture = toClone.ParameterTexture;
|
||||
ParameterUV = toClone.ParameterUV;
|
||||
}
|
||||
}
|
||||
|
||||
void IAnimationProperties.SetAnimatableProperty(string name, object value)
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
private readonly List<Entity<AnimationPlayerComponent>> _activeAnimations = new();
|
||||
|
||||
private EntityQuery<AnimationPlayerComponent> _playerQuery;
|
||||
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||
|
||||
[Dependency] private readonly IComponentFactory _compFact = default!;
|
||||
@@ -18,6 +19,7 @@ namespace Robust.Client.GameObjects
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_playerQuery = GetEntityQuery<AnimationPlayerComponent>();
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
}
|
||||
|
||||
@@ -74,7 +76,8 @@ namespace Robust.Client.GameObjects
|
||||
foreach (var key in remie)
|
||||
{
|
||||
component.PlayingAnimations.Remove(key);
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, new AnimationCompletedEvent {Uid = uid, Key = key}, true);
|
||||
var completedEvent = new AnimationCompletedEvent {Uid = uid, Key = key, Finished = true};
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, completedEvent, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -171,31 +174,42 @@ namespace Robust.Client.GameObjects
|
||||
return component.PlayingAnimations.ContainsKey(key);
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public void Stop(AnimationPlayerComponent component, string key)
|
||||
{
|
||||
component.PlayingAnimations.Remove(key);
|
||||
Stop((component.Owner, component), key);
|
||||
}
|
||||
|
||||
public void Stop(EntityUid uid, string key)
|
||||
public void Stop(Entity<AnimationPlayerComponent?> entity, string key)
|
||||
{
|
||||
if (!TryComp<AnimationPlayerComponent>(uid, out var player))
|
||||
if (!_playerQuery.Resolve(entity.Owner, ref entity.Comp, false) ||
|
||||
!entity.Comp.PlayingAnimations.Remove(key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
player.PlayingAnimations.Remove(key);
|
||||
var completedEvent = new AnimationCompletedEvent {Uid = entity.Owner, Key = key, Finished = false};
|
||||
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, completedEvent, true);
|
||||
}
|
||||
|
||||
public void Stop(EntityUid uid, AnimationPlayerComponent? component, string key)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return;
|
||||
|
||||
component.PlayingAnimations.Remove(key);
|
||||
Stop((uid, component), key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised whenever an animation stops, either due to running its course or being stopped manually.
|
||||
/// </summary>
|
||||
public sealed class AnimationCompletedEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Uid { get; init; }
|
||||
public string Key { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the animation finished by getting to its natural end.
|
||||
/// If false, it was removed prematurely via <see cref="AnimationPlayerSystem.Stop(Robust.Client.GameObjects.AnimationPlayerComponent,string)"/> or similar overloads.
|
||||
/// </summary>
|
||||
public bool Finished { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -11,6 +14,7 @@ namespace Robust.Client.GameObjects
|
||||
public sealed class AppearanceSystem : SharedAppearanceSystem
|
||||
{
|
||||
private readonly Queue<(EntityUid uid, AppearanceComponent)> _queuedUpdates = new();
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -74,10 +78,13 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var (key, value) in data)
|
||||
{
|
||||
object? serializationObject;
|
||||
if (value.GetType().IsValueType)
|
||||
newDict[key] = value;
|
||||
else if (value is ICloneable cloneable)
|
||||
newDict[key] = cloneable.Clone();
|
||||
else if ((serializationObject = _serialization.CreateCopy(value)) != null)
|
||||
newDict[key] = serializationObject;
|
||||
else
|
||||
throw new NotSupportedException("Invalid object in appearance data dictionary. Appearance data must be cloneable");
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.IoC;
|
||||
using static Robust.Shared.GameObjects.OccluderComponent;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
@@ -20,6 +21,7 @@ namespace Robust.Client.GameObjects;
|
||||
internal sealed class ClientOccluderSystem : OccluderSystem
|
||||
{
|
||||
private readonly HashSet<EntityUid> _dirtyEntities = new();
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
@@ -102,7 +104,8 @@ internal sealed class ClientOccluderSystem : OccluderSystem
|
||||
|
||||
if (occluder.Enabled && xform.Anchored && TryComp(xform.GridUid, out grid))
|
||||
{
|
||||
pos = grid.TileIndicesFor(xform.Coordinates);
|
||||
gridId = xform.GridUid.Value;
|
||||
pos = _mapSystem.TileIndicesFor(gridId, grid, xform.Coordinates);
|
||||
_dirtyEntities.Add(sender);
|
||||
}
|
||||
else if (occluder.LastPosition != null)
|
||||
@@ -117,10 +120,10 @@ internal sealed class ClientOccluderSystem : OccluderSystem
|
||||
return;
|
||||
}
|
||||
|
||||
DirtyNeighbours(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(0, 1)), query);
|
||||
DirtyNeighbours(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(0, -1)), query);
|
||||
DirtyNeighbours(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(1, 0)), query);
|
||||
DirtyNeighbours(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(-1, 0)), query);
|
||||
DirtyNeighbours(_mapSystem.GetAnchoredEntitiesEnumerator(gridId, grid, pos + new Vector2i(0, 1)), query);
|
||||
DirtyNeighbours(_mapSystem.GetAnchoredEntitiesEnumerator(gridId, grid, pos + new Vector2i(0, -1)), query);
|
||||
DirtyNeighbours(_mapSystem.GetAnchoredEntitiesEnumerator(gridId, grid, pos + new Vector2i(1, 0)), query);
|
||||
DirtyNeighbours(_mapSystem.GetAnchoredEntitiesEnumerator(gridId, grid, pos + new Vector2i(-1, 0)), query);
|
||||
}
|
||||
|
||||
private void DirtyNeighbours(AnchoredEntitiesEnumerator enumerator, EntityQuery<OccluderComponent> occluderQuery)
|
||||
@@ -166,7 +169,7 @@ internal sealed class ClientOccluderSystem : OccluderSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var tile = grid.TileIndicesFor(xform.Coordinates);
|
||||
var tile = _mapSystem.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
|
||||
|
||||
// TODO: Sub to parent changes instead or something.
|
||||
// DebugTools.Assert(occluder.LastPosition == null
|
||||
@@ -175,16 +178,16 @@ internal sealed class ClientOccluderSystem : OccluderSystem
|
||||
|
||||
// dir starts at the relative effective south direction;
|
||||
var dir = xform.LocalRotation.GetCardinalDir();
|
||||
CheckDir(dir, OccluderDir.South, tile, occluder, grid, occluders, xforms);
|
||||
CheckDir(dir, OccluderDir.South, tile, occluder, xform.GridUid.Value, grid, occluders, xforms);
|
||||
|
||||
dir = dir.GetClockwise90Degrees();
|
||||
CheckDir(dir, OccluderDir.West, tile, occluder, grid, occluders, xforms);
|
||||
CheckDir(dir, OccluderDir.West, tile, occluder, xform.GridUid.Value, grid, occluders, xforms);
|
||||
|
||||
dir = dir.GetClockwise90Degrees();
|
||||
CheckDir(dir, OccluderDir.North, tile, occluder, grid, occluders, xforms);
|
||||
CheckDir(dir, OccluderDir.North, tile, occluder, xform.GridUid.Value, grid, occluders, xforms);
|
||||
|
||||
dir = dir.GetClockwise90Degrees();
|
||||
CheckDir(dir, OccluderDir.East, tile, occluder, grid, occluders, xforms);
|
||||
CheckDir(dir, OccluderDir.East, tile, occluder, xform.GridUid.Value, grid, occluders, xforms);
|
||||
}
|
||||
|
||||
private void CheckDir(
|
||||
@@ -192,6 +195,7 @@ internal sealed class ClientOccluderSystem : OccluderSystem
|
||||
OccluderDir occDir,
|
||||
Vector2i tile,
|
||||
OccluderComponent occluder,
|
||||
EntityUid gridUid,
|
||||
MapGridComponent grid,
|
||||
EntityQuery<OccluderComponent> query,
|
||||
EntityQuery<TransformComponent> xforms)
|
||||
@@ -199,7 +203,7 @@ internal sealed class ClientOccluderSystem : OccluderSystem
|
||||
if ((occluder.Occluding & occDir) != 0)
|
||||
return;
|
||||
|
||||
foreach (var neighbor in grid.GetAnchoredEntities(tile.Offset(dir)))
|
||||
foreach (var neighbor in _mapSystem.GetAnchoredEntities(gridUid, grid, tile.Offset(dir)))
|
||||
{
|
||||
if (!query.TryGetComponent(neighbor, out var otherOccluder) || !otherOccluder.Enabled)
|
||||
continue;
|
||||
|
||||
@@ -120,7 +120,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
var type = _serializer.FindSerializedType(typeof(BaseContainer), data.ContainerType);
|
||||
container = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type!, inject:false);
|
||||
InitContainer(container, (uid, component), id);
|
||||
container.Init(this, id, (uid, component));
|
||||
component.Containers.Add(id, container);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -118,7 +119,7 @@ public sealed class EntityLookupOverlay : Overlay
|
||||
//DebugTools.Assert(!ent.IsInContainer(_entityManager));
|
||||
var (entPos, entRot) = _transform.GetWorldPositionRotation(ent);
|
||||
|
||||
var lookupPos = invMatrix.Transform(entPos);
|
||||
var lookupPos = Vector2.Transform(entPos, invMatrix);
|
||||
var lookupRot = entRot - rotation;
|
||||
|
||||
var aabb = _lookup.GetAABB(ent, lookupPos, lookupRot, xform, _xformQuery);
|
||||
@@ -127,6 +128,6 @@ public sealed class EntityLookupOverlay : Overlay
|
||||
}
|
||||
});
|
||||
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var map = _eyeManager.CurrentMap;
|
||||
var map = args.MapId;
|
||||
if (map == MapId.Nullspace) return;
|
||||
|
||||
foreach (var (_, treeComp) in _trees.GetIntersectingTrees(map, args.WorldBounds))
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
private GridChunkBoundsOverlay? _overlay;
|
||||
|
||||
@@ -36,7 +38,9 @@ namespace Robust.Client.GameObjects
|
||||
_overlay = new GridChunkBoundsOverlay(
|
||||
EntityManager,
|
||||
_eyeManager,
|
||||
_mapManager);
|
||||
_mapManager,
|
||||
_transform,
|
||||
_map);
|
||||
|
||||
_overlayManager.AddOverlay(_overlay);
|
||||
}
|
||||
@@ -56,21 +60,25 @@ namespace Robust.Client.GameObjects
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
public GridChunkBoundsOverlay(IEntityManager entManager, IEyeManager eyeManager, IMapManager mapManager)
|
||||
public GridChunkBoundsOverlay(IEntityManager entManager, IEyeManager eyeManager, IMapManager mapManager, SharedTransformSystem transformSystem, SharedMapSystem mapSystem)
|
||||
{
|
||||
_entityManager = entManager;
|
||||
_eyeManager = eyeManager;
|
||||
_mapManager = mapManager;
|
||||
_transformSystem = transformSystem;
|
||||
_mapSystem = mapSystem;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var currentMap = _eyeManager.CurrentMap;
|
||||
var currentMap = args.MapId;
|
||||
var viewport = args.WorldBounds;
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
@@ -78,11 +86,11 @@ namespace Robust.Client.GameObjects
|
||||
_mapManager.FindGridsIntersecting(currentMap, viewport, ref _grids);
|
||||
foreach (var grid in _grids)
|
||||
{
|
||||
var worldMatrix = _entityManager.GetComponent<TransformComponent>(grid).WorldMatrix;
|
||||
var worldMatrix = _transformSystem.GetWorldMatrix(grid);
|
||||
worldHandle.SetTransform(worldMatrix);
|
||||
var transform = new Transform(Vector2.Zero, Angle.Zero);
|
||||
|
||||
var chunkEnumerator = grid.Comp.GetMapChunks(viewport);
|
||||
var chunkEnumerator = _mapSystem.GetMapChunks(grid.Owner, grid.Comp, viewport);
|
||||
|
||||
while (chunkEnumerator.MoveNext(out var chunk))
|
||||
{
|
||||
@@ -109,7 +117,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
public sealed class InputSystem : SharedInputSystem, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _stateManager = default!;
|
||||
@@ -82,18 +83,35 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
// send it off to the server
|
||||
var clientMsg = (ClientFullInputCmdMessage)message;
|
||||
var fullMsg = new FullInputCmdMessage(
|
||||
clientMsg.Tick,
|
||||
clientMsg.SubTick,
|
||||
(int)clientMsg.InputSequence,
|
||||
clientMsg.InputFunctionId,
|
||||
clientMsg.State,
|
||||
GetNetCoordinates(clientMsg.Coordinates),
|
||||
clientMsg.ScreenCoordinates)
|
||||
var clientMsg = message switch
|
||||
{
|
||||
Uid = GetNetEntity(clientMsg.Uid)
|
||||
ClientFullInputCmdMessage clientInput => clientInput,
|
||||
FullInputCmdMessage fullInput => new ClientFullInputCmdMessage(
|
||||
fullInput.Tick,
|
||||
fullInput.SubTick,
|
||||
fullInput.InputFunctionId,
|
||||
GetCoordinates(fullInput.Coordinates),
|
||||
fullInput.ScreenCoordinates,
|
||||
fullInput.State,
|
||||
GetEntity(fullInput.Uid)),
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
var fullMsg = message switch
|
||||
{
|
||||
FullInputCmdMessage fullInput => fullInput,
|
||||
ClientFullInputCmdMessage client => new FullInputCmdMessage(
|
||||
client.Tick,
|
||||
client.SubTick,
|
||||
client.InputFunctionId,
|
||||
clientMsg.State,
|
||||
GetNetCoordinates(client.Coordinates),
|
||||
clientMsg.ScreenCoordinates,
|
||||
GetNetEntity(clientMsg.Uid)
|
||||
),
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
DispatchInputCommand(clientMsg, fullMsg);
|
||||
@@ -131,7 +149,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
_conHost.RegisterCommand("incmd",
|
||||
"Inserts an input command into the simulation",
|
||||
"incmd <KeyFunction> <d|u KeyState> <wxPos> <wyPos>",
|
||||
"incmd <KeyFunction> <KeyState> [wxPos] [wyPos]",
|
||||
GenerateInputCommand);
|
||||
}
|
||||
|
||||
@@ -147,17 +165,47 @@ namespace Robust.Client.GameObjects
|
||||
if (_playerManager.LocalEntity is not { } pent)
|
||||
return;
|
||||
|
||||
BoundKeyFunction keyFunction = new BoundKeyFunction(args[0]);
|
||||
BoundKeyState state = args[1] == "u" ? BoundKeyState.Up: BoundKeyState.Down;
|
||||
if (args.Length is not (2 or 4))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString($"cmd-invalid-arg-number-error"));
|
||||
return;
|
||||
}
|
||||
|
||||
var pxform = Transform(pent);
|
||||
var wPos = pxform.WorldPosition + new Vector2(float.Parse(args[2]), float.Parse(args[3]));
|
||||
var coords = EntityCoordinates.FromMap(pent, new MapCoordinates(wPos, pxform.MapID), _transform, EntityManager);
|
||||
var keyFunction = new BoundKeyFunction(args[0]);
|
||||
if (!Enum.TryParse<BoundKeyState>(args[1], out var state))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-parse-failure-enum", ("arg", args[1]), ("enum", nameof(BoundKeyState))));
|
||||
return;
|
||||
}
|
||||
|
||||
var wOffset = Vector2.Zero;
|
||||
if (args.Length == 4)
|
||||
{
|
||||
if (!float.TryParse(args[2], out var wX))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[2])));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!float.TryParse(args[3], out var wY))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[3])));
|
||||
return;
|
||||
}
|
||||
|
||||
wOffset = new Vector2(wX, wY);
|
||||
}
|
||||
|
||||
var coords = _transform.ToCoordinates(pent, _transform.GetMapCoordinates(pent).Offset(wOffset));
|
||||
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
|
||||
|
||||
var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state,
|
||||
GetNetCoordinates(coords), new ScreenCoordinates(0, 0, default), NetEntity.Invalid);
|
||||
var message = new ClientFullInputCmdMessage(_timing.CurTick,
|
||||
_timing.TickFraction,
|
||||
funcId,
|
||||
coords,
|
||||
new ScreenCoordinates(0, 0, default),
|
||||
state,
|
||||
EntityUid.Invalid);
|
||||
|
||||
HandleInputCommand(_playerManager.LocalSession, keyFunction, message);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed partial class SpriteSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an entity's sprite position in world terms.
|
||||
/// </summary>
|
||||
public Vector2 GetSpriteWorldPosition(Entity<SpriteComponent?, TransformComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp2))
|
||||
return Vector2.Zero;
|
||||
|
||||
var (worldPos, worldRot) = _xforms.GetWorldPositionRotation(entity.Owner);
|
||||
|
||||
if (!Resolve(entity, ref entity.Comp1, false))
|
||||
{
|
||||
return worldPos;
|
||||
}
|
||||
|
||||
if (entity.Comp1.NoRotation)
|
||||
{
|
||||
return worldPos + entity.Comp1.Offset;
|
||||
}
|
||||
|
||||
return worldPos + worldRot.RotateVec(entity.Comp1.Rotation.RotateVec(entity.Comp1.Offset));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an entity's sprite position in screen coordinates.
|
||||
/// </summary>
|
||||
public ScreenCoordinates GetSpriteScreenCoordinates(Entity<SpriteComponent?, TransformComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp2))
|
||||
return ScreenCoordinates.Invalid;
|
||||
|
||||
var spriteCoords = GetSpriteWorldPosition(entity);
|
||||
return _eye.MapToScreen(new MapCoordinates(spriteCoords, entity.Comp2.MapID));
|
||||
}
|
||||
}
|
||||
@@ -30,10 +30,12 @@ namespace Robust.Client.GameObjects
|
||||
public sealed partial class SpriteSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IEyeManager _eye = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xforms = default!;
|
||||
|
||||
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
|
||||
|
||||
@@ -66,6 +68,11 @@ namespace Robust.Client.GameObjects
|
||||
_sawmill = _logManager.GetSawmill("sprite");
|
||||
}
|
||||
|
||||
public bool IsVisible(Layer layer)
|
||||
{
|
||||
return layer.Visible && layer.CopyToShaderParameters == null;
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, SpriteComponent component, ComponentInit args)
|
||||
{
|
||||
// I'm not 100% this is needed, but I CBF with this ATM. Somebody kill server sprite component please.
|
||||
@@ -184,7 +191,8 @@ namespace Robust.Client.GameObjects
|
||||
/// <summary>
|
||||
/// Gets the specified frame for this sprite at the specified time.
|
||||
/// </summary>
|
||||
public Texture GetFrame(SpriteSpecifier spriteSpec, TimeSpan curTime)
|
||||
/// <param name="loop">Should we clamp on the last frame and not loop</param>
|
||||
public Texture GetFrame(SpriteSpecifier spriteSpec, TimeSpan curTime, bool loop = true)
|
||||
{
|
||||
Texture? sprite = null;
|
||||
|
||||
@@ -196,19 +204,29 @@ namespace Robust.Client.GameObjects
|
||||
var frames = state!.GetFrames(RsiDirection.South);
|
||||
var delays = state.GetDelays();
|
||||
var totalDelay = delays.Sum();
|
||||
var time = curTime.TotalSeconds % totalDelay;
|
||||
var delaySum = 0f;
|
||||
|
||||
for (var i = 0; i < delays.Length; i++)
|
||||
// No looping
|
||||
if (!loop && curTime.TotalSeconds >= totalDelay)
|
||||
{
|
||||
var delay = delays[i];
|
||||
delaySum += delay;
|
||||
sprite = frames[^1];
|
||||
}
|
||||
// Loopable
|
||||
else
|
||||
{
|
||||
var time = curTime.TotalSeconds % totalDelay;
|
||||
var delaySum = 0f;
|
||||
|
||||
if (time > delaySum)
|
||||
continue;
|
||||
for (var i = 0; i < delays.Length; i++)
|
||||
{
|
||||
var delay = delays[i];
|
||||
delaySum += delay;
|
||||
|
||||
sprite = frames[i];
|
||||
break;
|
||||
if (time > delaySum)
|
||||
continue;
|
||||
|
||||
sprite = frames[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sprite ??= Frame0(spriteSpec);
|
||||
|
||||
@@ -1,8 +1,38 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
ProtoManager.PrototypesReloaded += OnProtoReload;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
ProtoManager.PrototypesReloaded -= OnProtoReload;
|
||||
}
|
||||
|
||||
private void OnProtoReload(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
var player = Player.LocalEntity;
|
||||
|
||||
if (!UserQuery.TryComp(player, out var userComp))
|
||||
return;
|
||||
|
||||
foreach (var uid in userComp.OpenInterfaces.Keys)
|
||||
{
|
||||
if (!UIQuery.TryComp(uid, out var uiComp))
|
||||
continue;
|
||||
|
||||
foreach (var bui in uiComp.ClientOpenInterfaces.Values)
|
||||
{
|
||||
bui.OnProtoReload(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
internal bool Enabled { get; set; }
|
||||
|
||||
@@ -43,7 +44,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
var screenPos = _eyeManager.WorldToScreen(EntityManager.GetComponent<TransformComponent>(player.Value).WorldPosition);
|
||||
var screenPos = _eyeManager.WorldToScreen(_transform.GetWorldPosition(Transform(player.Value)));
|
||||
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
|
||||
_label.Visible = true;
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class ViewSubscriberSystem : SharedViewSubscriberSystem;
|
||||
@@ -7,9 +7,5 @@ namespace Robust.Client.GameObjects
|
||||
// These methods are used by the Game State Manager.
|
||||
|
||||
EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata);
|
||||
|
||||
void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null);
|
||||
|
||||
void StartEntity(EntityUid entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,11 +54,11 @@ namespace Robust.Client.GameStates
|
||||
private readonly HashSet<NetEntity> _stateEnts = new();
|
||||
private readonly List<EntityUid> _toDelete = new();
|
||||
private readonly List<IComponent> _toRemove = new();
|
||||
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState>> _outputData = new();
|
||||
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> _outputData = new();
|
||||
private readonly List<(EntityUid, TransformComponent)> _queuedBroadphaseUpdates = new();
|
||||
|
||||
private readonly ObjectPool<Dictionary<ushort, IComponentState>> _compDataPool =
|
||||
new DefaultObjectPool<Dictionary<ushort, IComponentState>>(new DictPolicy<ushort, IComponentState>(), 256);
|
||||
private readonly ObjectPool<Dictionary<ushort, IComponentState?>> _compDataPool =
|
||||
new DefaultObjectPool<Dictionary<ushort, IComponentState?>>(new DictPolicy<ushort, IComponentState?>(), 256);
|
||||
|
||||
private uint _metaCompNetId;
|
||||
|
||||
@@ -125,6 +125,8 @@ namespace Robust.Client.GameStates
|
||||
#endif
|
||||
|
||||
private bool _resettingPredictedEntities;
|
||||
private readonly List<EntityUid> _brokenEnts = new();
|
||||
private readonly List<(EntityUid, NetEntity)> _toStart = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
@@ -257,7 +259,7 @@ namespace Robust.Client.GameStates
|
||||
public void UpdateFullRep(GameState state, bool cloneDelta = false)
|
||||
=> _processor.UpdateFullRep(state, cloneDelta);
|
||||
|
||||
public Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep()
|
||||
public Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep()
|
||||
=> _processor.GetFullRep();
|
||||
|
||||
private void HandlePvsLeaveMessage(MsgStateLeavePvs message)
|
||||
@@ -598,8 +600,12 @@ namespace Robust.Client.GameStates
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A component was dirtied: {comp.GetType()}");
|
||||
|
||||
var handleState = new ComponentHandleState(compState, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
|
||||
if (compState != null)
|
||||
{
|
||||
var handleState = new ComponentHandleState(compState, null);
|
||||
_entities.EventBus.RaiseComponentEvent(entity, comp, ref handleState);
|
||||
}
|
||||
|
||||
comp.LastModifiedTick = _timing.LastRealTick;
|
||||
}
|
||||
}
|
||||
@@ -631,8 +637,12 @@ namespace Robust.Client.GameStates
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A component was removed: {comp.GetType()}");
|
||||
|
||||
var stateEv = new ComponentHandleState(state, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
|
||||
if (state != null)
|
||||
{
|
||||
var stateEv = new ComponentHandleState(state, null);
|
||||
_entities.EventBus.RaiseComponentEvent(entity, comp, ref stateEv);
|
||||
}
|
||||
|
||||
comp.ClearCreationTick(); // don't undo the re-adding.
|
||||
comp.LastModifiedTick = _timing.LastRealTick;
|
||||
}
|
||||
@@ -667,7 +677,16 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var netEntity in createdEntities)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
if (!_entityManager.TryGetEntityData(netEntity, out _, out var meta))
|
||||
{
|
||||
_sawmill.Error($"Encountered deleted entity while merging implicit data! NetEntity: {netEntity}");
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
var (_, meta) = _entityManager.GetEntityData(netEntity);
|
||||
#endif
|
||||
|
||||
var compData = _compDataPool.Get();
|
||||
_outputData.Add(netEntity, compData);
|
||||
|
||||
@@ -676,7 +695,7 @@ namespace Robust.Client.GameStates
|
||||
DebugTools.Assert(component.NetSyncEnabled);
|
||||
|
||||
var state = _entityManager.GetComponentState(bus, component, null, GameTick.Zero);
|
||||
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(state is not IComponentDeltaState);
|
||||
compData.Add(netId, state);
|
||||
}
|
||||
}
|
||||
@@ -876,9 +895,22 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
foreach (var (entity, data) in _toApply)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
HandleEntityState(entity, data.NetEntity, data.Meta, _entities.EventBus, data.curState,
|
||||
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
|
||||
|
||||
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Caught exception while applying entity state. Entity: {_entities.ToPrettyString(entity)}. Exception: {e}");
|
||||
_entityManager.DeleteEntity(entity);
|
||||
RequestFullState();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (!data.EnteringPvs)
|
||||
continue;
|
||||
|
||||
@@ -917,7 +949,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessDeletions(delSpan, xforms, xformSys);
|
||||
ProcessDeletions(delSpan, xforms, metas, xformSys);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -962,6 +994,7 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
var xforms = _entities.GetEntityQuery<TransformComponent>();
|
||||
var metas = _entities.GetEntityQuery<MetaDataComponent>();
|
||||
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
|
||||
|
||||
_toDelete.Clear();
|
||||
@@ -990,12 +1023,12 @@ namespace Robust.Client.GameStates
|
||||
|
||||
// This entity is going to get deleted, but maybe some if its children won't be, so lets detach them to
|
||||
// null. First we will detach the parent in order to reduce the number of broadphase/lookup updates.
|
||||
xformSys.DetachParentToNull(ent, xform);
|
||||
xformSys.DetachEntity(ent, xform);
|
||||
|
||||
// Then detach all children.
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
|
||||
xformSys.DetachEntity(child, xforms.Get(child), metas.Get(child), xform);
|
||||
|
||||
if (deleteClientChildren
|
||||
&& !deleteClientEntities // don't add duplicates
|
||||
@@ -1014,9 +1047,9 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDeletions(
|
||||
ReadOnlySpan<NetEntity> delSpan,
|
||||
private void ProcessDeletions(ReadOnlySpan<NetEntity> delSpan,
|
||||
EntityQuery<TransformComponent> xforms,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
SharedTransformSystem xformSys)
|
||||
{
|
||||
// Processing deletions is non-trivial, because by default deletions will also delete all child entities.
|
||||
@@ -1043,13 +1076,13 @@ namespace Robust.Client.GameStates
|
||||
continue; // Already deleted? or never sent to us?
|
||||
|
||||
// First, a single recursive map change
|
||||
xformSys.DetachParentToNull(id.Value, xform);
|
||||
xformSys.DetachEntity(id.Value, xform);
|
||||
|
||||
// Then detach all children.
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
|
||||
xformSys.DetachEntity(child, xforms.Get(child), metas.Get(child), xform);
|
||||
}
|
||||
|
||||
// Finally, delete the entity.
|
||||
@@ -1144,7 +1177,7 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
meta._flags |= MetaDataFlags.Detached;
|
||||
xformSys.DetachParentToNull(ent.Value, xform);
|
||||
xformSys.DetachEntity(ent.Value, xform);
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
|
||||
|
||||
if (container != null)
|
||||
@@ -1157,63 +1190,58 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private void InitializeAndStart(Dictionary<NetEntity, EntityState> toCreate)
|
||||
{
|
||||
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
|
||||
_toStart.Clear();
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
var brokenEnts = new List<EntityUid>();
|
||||
#endif
|
||||
using (_prof.Group("Initialize Entity"))
|
||||
{
|
||||
EntityUid entity = default;
|
||||
foreach (var netEntity in toCreate.Keys)
|
||||
{
|
||||
var entity = _entityManager.GetEntity(netEntity);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
_entities.InitializeEntity(entity, metaQuery.GetComponent(entity));
|
||||
#if EXCEPTION_TOLERANCE
|
||||
(entity, var meta) = _entityManager.GetEntityData(netEntity);
|
||||
_entities.InitializeEntity(entity, meta);
|
||||
_toStart.Add((entity, netEntity));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Server entity threw in Init: ent={_entities.ToPrettyString(entity)}");
|
||||
_sawmill.Error($"Server entity threw in Init: nent={netEntity}, ent={_entities.ToPrettyString(entity)}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
|
||||
brokenEnts.Add(entity);
|
||||
toCreate.Remove(netEntity);
|
||||
}
|
||||
_toCreate.Remove(netEntity);
|
||||
_brokenEnts.Add(entity);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (_prof.Group("Start Entity"))
|
||||
{
|
||||
foreach (var netEntity in toCreate.Keys)
|
||||
foreach (var (entity, netEntity) in _toStart)
|
||||
{
|
||||
var entity = _entityManager.GetEntity(netEntity);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
_entities.StartEntity(entity);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
_entities.StartEntity(entity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Server entity threw in Start: ent={_entityManager.ToPrettyString(entity)}");
|
||||
_sawmill.Error($"Server entity threw in Start: nent={netEntity}, ent={_entityManager.ToPrettyString(entity)}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
|
||||
brokenEnts.Add(entity);
|
||||
toCreate.Remove(netEntity);
|
||||
}
|
||||
_toCreate.Remove(netEntity);
|
||||
_brokenEnts.Add(entity);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
foreach (var entity in brokenEnts)
|
||||
foreach (var entity in _brokenEnts)
|
||||
{
|
||||
_entityManager.DeleteEntity(entity);
|
||||
}
|
||||
#endif
|
||||
_brokenEnts.Clear();
|
||||
}
|
||||
|
||||
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
|
||||
@@ -1329,8 +1357,11 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var (comp, cur, next) in _compStateWork.Values)
|
||||
{
|
||||
if (cur == null && next == null)
|
||||
continue;
|
||||
|
||||
var handleState = new ComponentHandleState(cur, next);
|
||||
bus.RaiseComponentEvent(comp, ref handleState);
|
||||
bus.RaiseComponentEvent(uid, comp, ref handleState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1402,7 +1433,7 @@ namespace Robust.Client.GameStates
|
||||
containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container);
|
||||
}
|
||||
|
||||
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachParentToNull(uid, xform);
|
||||
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachEntity(uid, xform);
|
||||
|
||||
if (container != null)
|
||||
containerSys.AddExpectedEntity(_entities.GetNetEntity(uid), container);
|
||||
@@ -1481,8 +1512,11 @@ namespace Robust.Client.GameStates
|
||||
_entityManager.AddComponent(uid, comp, true, meta);
|
||||
}
|
||||
|
||||
if (state == null)
|
||||
continue;
|
||||
|
||||
var handleState = new ComponentHandleState(state, null);
|
||||
_entityManager.EventBus.RaiseComponentEvent(comp, ref handleState);
|
||||
_entityManager.EventBus.RaiseComponentEvent(uid, comp, ref handleState);
|
||||
}
|
||||
|
||||
// ensure we don't have any extra components
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Client.GameStates
|
||||
/// <summary>
|
||||
/// This dictionary stores the full most recently received server state of any entity. This is used whenever predicted entities get reset.
|
||||
/// </summary>
|
||||
internal readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState>> _lastStateFullRep
|
||||
internal readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> _lastStateFullRep
|
||||
= new();
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -212,7 +212,7 @@ Had full state: {LastFullState != null}"
|
||||
{
|
||||
if (!_lastStateFullRep.TryGetValue(entityState.NetEntity, out var compData))
|
||||
{
|
||||
compData = new Dictionary<ushort, IComponentState>();
|
||||
compData = new();
|
||||
_lastStateFullRep.Add(entityState.NetEntity, compData);
|
||||
}
|
||||
|
||||
@@ -221,21 +221,20 @@ Had full state: {LastFullState != null}"
|
||||
var compState = change.State;
|
||||
|
||||
if (compState is IComponentDeltaState delta
|
||||
&& !delta.FullState
|
||||
&& compData.TryGetValue(change.NetID, out var old)) // May fail if relying on implicit data
|
||||
{
|
||||
DebugTools.Assert(old is IComponentDeltaState oldDelta && oldDelta.FullState, "last state is not a full state");
|
||||
DebugTools.Assert(old is not IComponentDeltaState, "last state is not a full state");
|
||||
|
||||
if (cloneDelta)
|
||||
{
|
||||
compState = delta.CreateNewFullState(old);
|
||||
compState = delta.CreateNewFullState(old!);
|
||||
}
|
||||
else
|
||||
{
|
||||
delta.ApplyToFullState(old);
|
||||
delta.ApplyToFullState(old!);
|
||||
compState = old;
|
||||
}
|
||||
DebugTools.Assert(compState is IComponentDeltaState newState && newState.FullState, "newly constructed state is not a full state");
|
||||
DebugTools.Assert(compState is not IComponentDeltaState, "newly constructed state is not a full state");
|
||||
}
|
||||
|
||||
compData[change.NetID] = compState;
|
||||
@@ -391,7 +390,7 @@ Had full state: {LastFullState != null}"
|
||||
LastFullStateRequested = null;
|
||||
}
|
||||
|
||||
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState>> implicitData)
|
||||
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> implicitData)
|
||||
{
|
||||
foreach (var (netEntity, implicitEntState) in implicitData)
|
||||
{
|
||||
@@ -399,6 +398,7 @@ Had full state: {LastFullState != null}"
|
||||
|
||||
foreach (var (netId, implicitCompState) in implicitEntState)
|
||||
{
|
||||
DebugTools.Assert(implicitCompState is not IComponentDeltaState);
|
||||
ref var serverState = ref CollectionsMarshal.GetValueRefOrAddDefault(fullRep, netId, out var exists);
|
||||
|
||||
if (!exists)
|
||||
@@ -407,36 +407,32 @@ Had full state: {LastFullState != null}"
|
||||
continue;
|
||||
}
|
||||
|
||||
if (serverState is not IComponentDeltaState serverDelta || serverDelta.FullState)
|
||||
if (serverState is not IComponentDeltaState serverDelta)
|
||||
continue;
|
||||
|
||||
DebugTools.AssertNotNull(implicitCompState);
|
||||
|
||||
// Server sent an initial delta state. This is fine as long as the client can infer an initial full
|
||||
// state from the entity prototype.
|
||||
if (implicitCompState is not IComponentDeltaState implicitDelta || !implicitDelta.FullState)
|
||||
{
|
||||
_logger.Error($"Server sent delta state and client failed to construct an implicit full state for entity {netEntity}");
|
||||
continue;
|
||||
}
|
||||
|
||||
serverDelta.ApplyToFullState(implicitCompState);
|
||||
serverDelta.ApplyToFullState(implicitCompState!);
|
||||
serverState = implicitCompState;
|
||||
DebugTools.Assert(implicitCompState is IComponentDeltaState d && d.FullState);
|
||||
DebugTools.Assert(serverState is not IComponentDeltaState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<ushort, IComponentState> GetLastServerStates(NetEntity netEntity)
|
||||
public Dictionary<ushort, IComponentState?> GetLastServerStates(NetEntity netEntity)
|
||||
{
|
||||
return _lastStateFullRep[netEntity];
|
||||
}
|
||||
|
||||
public Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep()
|
||||
public Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep()
|
||||
{
|
||||
return _lastStateFullRep;
|
||||
}
|
||||
|
||||
public bool TryGetLastServerStates(NetEntity entity,
|
||||
[NotNullWhen(true)] out Dictionary<ushort, IComponentState>? dictionary)
|
||||
[NotNullWhen(true)] out Dictionary<ushort, IComponentState?>? dictionary)
|
||||
{
|
||||
return _lastStateFullRep.TryGetValue(entity, out dictionary);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace Robust.Client.GameStates
|
||||
/// <summary>
|
||||
/// Returns the full collection of cached game states that are used to reset predicted entities.
|
||||
/// </summary>
|
||||
Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep();
|
||||
Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep();
|
||||
|
||||
/// <summary>
|
||||
/// This will perform some setup in order to reset the game to an earlier state. To fully reset the state
|
||||
|
||||
@@ -83,13 +83,13 @@ namespace Robust.Client.GameStates
|
||||
/// The data to merge.
|
||||
/// It's a dictionary of entity ID -> (component net ID -> ComponentState)
|
||||
/// </param>
|
||||
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState>> data);
|
||||
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> data);
|
||||
|
||||
/// <summary>
|
||||
/// Get the last state data from the server for an entity.
|
||||
/// </summary>
|
||||
/// <returns>Dictionary (net ID -> ComponentState)</returns>
|
||||
Dictionary<ushort, IComponentState> GetLastServerStates(NetEntity entity);
|
||||
Dictionary<ushort, IComponentState?> GetLastServerStates(NetEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the number of applicable states in the game state buffer from a given tick.
|
||||
@@ -99,6 +99,6 @@ namespace Robust.Client.GameStates
|
||||
int GetApplicableStateCount(GameTick? fromTick);
|
||||
|
||||
bool TryGetLastServerStates(NetEntity entity,
|
||||
[NotNullWhen(true)] out Dictionary<ushort, IComponentState>? dictionary);
|
||||
[NotNullWhen(true)] out Dictionary<ushort, IComponentState?>? dictionary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Robust.Client.GameStates
|
||||
while (query.MoveNext(out var uid, out var transform))
|
||||
{
|
||||
// if not on the same map, continue
|
||||
if (transform.MapID != _eyeManager.CurrentMap || _container.IsEntityInContainer(uid))
|
||||
if (transform.MapID != args.MapId || _container.IsEntityInContainer(uid))
|
||||
continue;
|
||||
|
||||
if (transform.GridUid == uid)
|
||||
|
||||
@@ -64,29 +64,22 @@ namespace Robust.Client.Graphics
|
||||
/// <inheritdoc />
|
||||
public Box2 GetWorldViewport()
|
||||
{
|
||||
var vpSize = _displayManager.ScreenSize;
|
||||
|
||||
var topLeft = ScreenToMap(Vector2.Zero);
|
||||
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);
|
||||
var top = MathHelper.Max(topLeft.Y, topRight.Y, bottomRight.Y, bottomLeft.Y);
|
||||
|
||||
return new Box2(left, bottom, right, top);
|
||||
return GetWorldViewbounds().CalcBoundingBox();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2Rotated GetWorldViewbounds()
|
||||
{
|
||||
var vpSize = _displayManager.ScreenSize;
|
||||
// This is an inefficient and roundabout way of geting the viewport.
|
||||
// But its a method that shouldn't get used much.
|
||||
|
||||
var vp = MainViewport as Control;
|
||||
var vpSize = vp?.PixelSize ?? _displayManager.ScreenSize;
|
||||
|
||||
var topRight = ScreenToMap(new Vector2(vpSize.X, 0)).Position;
|
||||
var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y)).Position;
|
||||
|
||||
// This assumes the main viewports eye and the main eye are the same.
|
||||
var rotation = new Angle(CurrentEye.Rotation);
|
||||
var center = (bottomLeft + topRight) / 2;
|
||||
|
||||
@@ -108,18 +101,16 @@ namespace Robust.Client.Graphics
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void GetScreenProjectionMatrix(out Matrix3 projMatrix)
|
||||
public void GetScreenProjectionMatrix(out Matrix3x2 projMatrix)
|
||||
{
|
||||
Matrix3 result = default;
|
||||
Matrix3x2 result = default;
|
||||
|
||||
result.R0C0 = PixelsPerMeter;
|
||||
result.R1C1 = -PixelsPerMeter;
|
||||
result.M11 = PixelsPerMeter;
|
||||
result.M22 = -PixelsPerMeter;
|
||||
|
||||
var screenSize = _displayManager.ScreenSize;
|
||||
result.R0C2 = screenSize.X / 2f;
|
||||
result.R1C2 = screenSize.Y / 2f;
|
||||
|
||||
result.R2C2 = 1;
|
||||
result.M31 = screenSize.X / 2f;
|
||||
result.M32 = screenSize.Y / 2f;
|
||||
|
||||
/* column major
|
||||
Sx 0 Tx
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
@@ -13,26 +14,29 @@ namespace Robust.Client.Graphics
|
||||
public interface IEyeManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The current eye that is being used to render the game.
|
||||
/// The primary eye, which is usually the eye associated with the main viewport.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Generally, you should avoid using this whenever possible. E.g., when rendering overlays should use the
|
||||
/// eye & viewbounds that gets passed to the draw method.
|
||||
/// Setting this property to null will use the default eye.
|
||||
/// </remarks>
|
||||
IEye CurrentEye { get; set; }
|
||||
|
||||
IViewportControl MainViewport { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the map on which the current eye is "placed".
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
MapId CurrentMap { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A world-space box that is at LEAST the area covered by the viewport.
|
||||
/// A world-space box that is at LEAST the area covered by the main viewport.
|
||||
/// May be larger due to say rotation.
|
||||
/// </summary>
|
||||
Box2 GetWorldViewport();
|
||||
|
||||
/// <summary>
|
||||
/// A world-space box of the area visible in the main viewport.
|
||||
/// </summary>
|
||||
Box2Rotated GetWorldViewbounds();
|
||||
|
||||
/// <summary>
|
||||
@@ -40,10 +44,10 @@ namespace Robust.Client.Graphics
|
||||
/// to UI screen space.
|
||||
/// </summary>
|
||||
/// <param name="projMatrix"></param>
|
||||
void GetScreenProjectionMatrix(out Matrix3 projMatrix);
|
||||
void GetScreenProjectionMatrix(out Matrix3x2 projMatrix);
|
||||
|
||||
/// <summary>
|
||||
/// Projects a point from world space to UI screen space using the current camera.
|
||||
/// Projects a point from world space to UI screen space using the main viewport.
|
||||
/// </summary>
|
||||
/// <param name="point">Point in world to transform.</param>
|
||||
/// <returns>Corresponding point in UI screen space.</returns>
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
#if EXCEPTION_TOLERANCE_LOCAL
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("clyde.win", $"Error dispatching window event {ev.GetType().Name}:\n{e}");
|
||||
_sawmillWin.Error($"Error dispatching window event {ev.GetType().Name}:\n{e}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -15,8 +14,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private readonly Dictionary<EntityUid, Dictionary<Vector2i, MapChunkData>> _mapChunkData =
|
||||
new();
|
||||
|
||||
@@ -67,7 +64,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
var transform = _entityManager.GetComponent<TransformComponent>(mapGrid);
|
||||
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
|
||||
gridProgram.SetUniform(UniIModelMatrix, _transformSystem.GetWorldMatrix(transform));
|
||||
var enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
|
||||
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -367,7 +368,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_renderHandle.UseShader(entry.Sprite.PostShader);
|
||||
CalcScreenMatrices(viewport.Size, out var proj, out var view);
|
||||
_renderHandle.SetProjView(proj, view);
|
||||
_renderHandle.SetModelTransform(Matrix3.Identity);
|
||||
_renderHandle.SetModelTransform(Matrix3x2.Identity);
|
||||
|
||||
var rounded = roundedPos - entityPostRenderTarget.Size / 2;
|
||||
|
||||
@@ -480,7 +481,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var worldBounds = CalcWorldBounds(viewport);
|
||||
var worldAABB = worldBounds.CalcBoundingBox();
|
||||
|
||||
if (_eyeManager.CurrentMap != MapId.Nullspace)
|
||||
if (eye.Position.MapId != MapId.Nullspace)
|
||||
{
|
||||
using (DebugGroup("Lights"))
|
||||
using (_prof.Group("Lights"))
|
||||
@@ -513,7 +514,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawLight && eye.DrawFov)
|
||||
{
|
||||
var mapUid = _mapManager.GetMapEntityId(eye.Position.MapId);
|
||||
var mapUid = _mapSystem.GetMap(eye.Position.MapId);
|
||||
if (_entityManager.GetComponent<MapComponent>(mapUid).LightingEnabled)
|
||||
ApplyFovToBuffer(viewport, eye);
|
||||
}
|
||||
@@ -530,7 +531,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Because the math is wrong.
|
||||
// So there are distortions from incorrect projection.
|
||||
_renderHandle.UseShader(_fovDebugShaderInstance);
|
||||
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3.Identity);
|
||||
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3x2.Identity);
|
||||
var pos = UIBox2.FromDimensions(viewport.Size / 2 - new Vector2(200, 200), new Vector2(400, 400));
|
||||
_renderHandle.DrawingHandleScreen.DrawTextureRect(FovTexture, pos);
|
||||
}
|
||||
@@ -538,15 +539,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (DebugLayers == ClydeDebugLayers.Light)
|
||||
{
|
||||
_renderHandle.UseShader(null);
|
||||
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3.Identity);
|
||||
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3x2.Identity);
|
||||
_renderHandle.DrawingHandleScreen.DrawTextureRect(
|
||||
viewport.WallBleedIntermediateRenderTarget2.Texture,
|
||||
UIBox2.FromDimensions(Vector2.Zero, viewport.Size), new Color(1, 1, 1, 0.5f));
|
||||
}
|
||||
|
||||
using (_prof.Group("Overlays WS"))
|
||||
if (eye.Position.MapId != MapId.Nullspace)
|
||||
{
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB, worldBounds);
|
||||
using (_prof.Group("Overlays WS"))
|
||||
{
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB, worldBounds);
|
||||
}
|
||||
}
|
||||
|
||||
_currentViewport = oldVp;
|
||||
|
||||
@@ -98,28 +98,34 @@ namespace Robust.Client.Graphics.Clyde
|
||||
[FieldOffset(20 * sizeof(float))] public Vector3 ViewMatrixC2;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ProjViewMatrices(in Matrix3 projMatrix, in Matrix3 viewMatrix)
|
||||
public ProjViewMatrices(in Matrix3x2 projMatrix, in Matrix3x2 viewMatrix)
|
||||
{
|
||||
ProjMatrixC0 = new Vector3(projMatrix.R0C0, projMatrix.R1C0, projMatrix.R2C0);
|
||||
ProjMatrixC1 = new Vector3(projMatrix.R0C1, projMatrix.R1C1, projMatrix.R2C1);
|
||||
ProjMatrixC2 = new Vector3(projMatrix.R0C2, projMatrix.R1C2, projMatrix.R2C2);
|
||||
// We put the rows of the input matrix into the columns of our GPU matrices
|
||||
// this transpose is required, as in C#, we premultiply vectors with matrices
|
||||
// (vM) while GL postmultiplies vectors with matrices (Mv); however, since
|
||||
// the Matrix3x2 data is stored row-major, and GL uses column-major, the
|
||||
// memory layout is the same (or would be, if Matrix3x2 didn't have an
|
||||
// implicit column)
|
||||
ProjMatrixC0 = new Vector3(projMatrix.M11, projMatrix.M12, 0);
|
||||
ProjMatrixC1 = new Vector3(projMatrix.M21, projMatrix.M22, 0);
|
||||
ProjMatrixC2 = new Vector3(projMatrix.M31, projMatrix.M32, 1);
|
||||
|
||||
ViewMatrixC0 = new Vector3(viewMatrix.R0C0, viewMatrix.R1C0, viewMatrix.R2C0);
|
||||
ViewMatrixC1 = new Vector3(viewMatrix.R0C1, viewMatrix.R1C1, viewMatrix.R2C1);
|
||||
ViewMatrixC2 = new Vector3(viewMatrix.R0C2, viewMatrix.R1C2, viewMatrix.R2C2);
|
||||
ViewMatrixC0 = new Vector3(viewMatrix.M11, viewMatrix.M12, 0);
|
||||
ViewMatrixC1 = new Vector3(viewMatrix.M21, viewMatrix.M22, 0);
|
||||
ViewMatrixC2 = new Vector3(viewMatrix.M31, viewMatrix.M32, 1);
|
||||
}
|
||||
|
||||
public void Apply(Clyde clyde, GLShaderProgram program)
|
||||
{
|
||||
program.SetUniformMaybe("projectionMatrix", new Matrix3(
|
||||
ProjMatrixC0.X, ProjMatrixC1.X, ProjMatrixC2.X,
|
||||
ProjMatrixC0.Y, ProjMatrixC1.Y, ProjMatrixC2.Y,
|
||||
ProjMatrixC0.Z, ProjMatrixC1.Z, ProjMatrixC2.Z
|
||||
program.SetUniformMaybe("projectionMatrix", new Matrix3x2(
|
||||
ProjMatrixC0.X, ProjMatrixC0.Y, // Implicit 0
|
||||
ProjMatrixC1.X, ProjMatrixC1.Y, // Implicit 0
|
||||
ProjMatrixC2.X, ProjMatrixC2.Y // Implicit 1
|
||||
));
|
||||
program.SetUniformMaybe("viewMatrix", new Matrix3(
|
||||
ViewMatrixC0.X, ViewMatrixC1.X, ViewMatrixC2.X,
|
||||
ViewMatrixC0.Y, ViewMatrixC1.Y, ViewMatrixC2.Y,
|
||||
ViewMatrixC0.Z, ViewMatrixC1.Z, ViewMatrixC2.Z
|
||||
program.SetUniformMaybe("viewMatrix", new Matrix3x2(
|
||||
ViewMatrixC0.X, ViewMatrixC0.Y, // Implicit 0
|
||||
ViewMatrixC1.X, ViewMatrixC1.Y, // Implicit 0
|
||||
ViewMatrixC2.X, ViewMatrixC2.Y // Implicit 1
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return resource.ClydeHandle;
|
||||
}
|
||||
|
||||
Logger.Warning($"Can't load shader {path}\n");
|
||||
_clydeSawmill.Warning($"Can't load shader {path}\n");
|
||||
return default;
|
||||
}
|
||||
|
||||
@@ -345,13 +345,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return;
|
||||
|
||||
// If this map has lighting disabled, return
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
var mapUid = _mapSystem.GetMapOrInvalid(mapId);
|
||||
if (!_entityManager.TryGetComponent<MapComponent>(mapUid, out var map) || !map.LightingEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
(PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot)[] lights;
|
||||
int count;
|
||||
Box2 expandedBounds;
|
||||
using (_prof.Group("LightsToRender"))
|
||||
@@ -492,18 +491,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var offset = new Vector2(component.Radius, component.Radius);
|
||||
|
||||
Matrix3 matrix;
|
||||
Matrix3x2 matrix;
|
||||
if (mask == null)
|
||||
{
|
||||
matrix = Matrix3.Identity;
|
||||
matrix = Matrix3x2.Identity;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only apply rotation if a mask is said, because else it doesn't matter.
|
||||
matrix = Matrix3.CreateRotation(rotation);
|
||||
matrix = Matrix3Helpers.CreateRotation(rotation);
|
||||
}
|
||||
|
||||
(matrix.R0C2, matrix.R1C2) = lightPos;
|
||||
(matrix.M31, matrix.M32) = lightPos;
|
||||
|
||||
_drawQuad(-offset, offset, matrix, lightShader);
|
||||
}
|
||||
@@ -541,7 +540,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Clyde clyde,
|
||||
int count,
|
||||
int shadowCastingCount,
|
||||
TransformSystem xformSystem,
|
||||
EntityQuery<TransformComponent> xforms,
|
||||
Box2 worldAABB) state,
|
||||
in ComponentTreeEntry<PointLightComponent> value)
|
||||
@@ -554,7 +552,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return false;
|
||||
|
||||
var (light, transform) = value;
|
||||
var (lightPos, rot) = state.xformSystem.GetWorldPositionRotation(transform, state.xforms);
|
||||
var (lightPos, rot) = state.clyde._transformSystem.GetWorldPositionRotation(transform, state.xforms);
|
||||
lightPos += rot.RotateVec(light.Offset);
|
||||
var circle = new Circle(lightPos, light.Radius);
|
||||
|
||||
@@ -600,16 +598,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
in Box2Rotated worldBounds,
|
||||
in Box2 worldAABB)
|
||||
{
|
||||
var lightTreeSys = _entitySystemManager.GetEntitySystem<LightTreeSystem>();
|
||||
var xformSystem = _entitySystemManager.GetEntitySystem<TransformSystem>();
|
||||
|
||||
// Use worldbounds for this one as we only care if the light intersects our actual bounds
|
||||
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
var state = (this, count: 0, shadowCastingCount: 0, xformSystem, xforms, worldAABB);
|
||||
var state = (this, count: 0, shadowCastingCount: 0, xforms, worldAABB);
|
||||
|
||||
foreach (var (uid, comp) in lightTreeSys.GetIntersectingTrees(map, worldAABB))
|
||||
foreach (var (uid, comp) in _lightTreeSystem.GetIntersectingTrees(map, worldAABB))
|
||||
{
|
||||
var bounds = xformSystem.GetInvWorldMatrix(uid, xforms).TransformBox(worldBounds);
|
||||
var bounds = _transformSystem.GetInvWorldMatrix(uid, xforms).TransformBox(worldBounds);
|
||||
comp.Tree.QueryAabb(ref state, LightQuery, bounds);
|
||||
}
|
||||
|
||||
@@ -692,7 +687,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// Blur horizontally to _wallBleedIntermediateRenderTarget1.
|
||||
shader.SetUniformMaybe("direction", Vector2.UnitX);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3.Identity, shader);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
|
||||
|
||||
SetTexture(TextureUnit.Texture0, viewport.LightBlurTarget.Texture);
|
||||
|
||||
@@ -700,7 +695,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// Blur vertically to _wallBleedIntermediateRenderTarget2.
|
||||
shader.SetUniformMaybe("direction", Vector2.UnitY);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3.Identity, shader);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
|
||||
|
||||
SetTexture(TextureUnit.Texture0, viewport.LightRenderTarget.Texture);
|
||||
}
|
||||
@@ -754,14 +749,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// Blur horizontally to _wallBleedIntermediateRenderTarget1.
|
||||
shader.SetUniformMaybe("direction", Vector2.UnitX);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3.Identity, shader);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
|
||||
|
||||
SetTexture(TextureUnit.Texture0, viewport.WallBleedIntermediateRenderTarget1.Texture);
|
||||
BindRenderTargetFull(viewport.WallBleedIntermediateRenderTarget2);
|
||||
|
||||
// Blur vertically to _wallBleedIntermediateRenderTarget2.
|
||||
shader.SetUniformMaybe("direction", Vector2.UnitY);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3.Identity, shader);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
|
||||
|
||||
SetTexture(TextureUnit.Texture0, viewport.WallBleedIntermediateRenderTarget2.Texture);
|
||||
}
|
||||
@@ -909,13 +904,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Second modification is that output must be fov-centred (difference-space)
|
||||
uZero -= fovCentre;
|
||||
|
||||
var clipToDiff = new Matrix3(in uX, in uY, in uZero);
|
||||
var clipToDiff = new Matrix3x2(uX.X, uX.Y, uY.X, uY.Y, uZero.X, uZero.Y);
|
||||
|
||||
fovShader.SetUniformMaybe("clipToDiff", clipToDiff);
|
||||
_drawQuad(Vector2.Zero, Vector2.One, Matrix3.Identity, fovShader);
|
||||
_drawQuad(Vector2.Zero, Vector2.One, Matrix3x2.Identity, fovShader);
|
||||
}
|
||||
|
||||
private void UpdateOcclusionGeometry(MapId map, Box2 expandedBounds, Matrix3 eyeTransform)
|
||||
private void UpdateOcclusionGeometry(MapId map, Box2 expandedBounds, Matrix3x2 eyeTransform)
|
||||
{
|
||||
using var _ = _prof.Group("UpdateOcclusionGeometry");
|
||||
using var _p = DebugGroup(nameof(UpdateOcclusionGeometry));
|
||||
@@ -941,18 +936,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var imi = 0;
|
||||
var amiMax = _maxOccluders * 4;
|
||||
|
||||
var occluderSystem = _entitySystemManager.GetEntitySystem<OccluderSystem>();
|
||||
var xformSystem = _entitySystemManager.GetEntitySystem<TransformSystem>();
|
||||
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var (uid, comp) in occluderSystem.GetIntersectingTrees(map, expandedBounds))
|
||||
foreach (var (uid, comp) in _occluderSystem.GetIntersectingTrees(map, expandedBounds))
|
||||
{
|
||||
if (ami >= amiMax)
|
||||
break;
|
||||
|
||||
var treeBounds = xforms.GetComponent(uid).InvWorldMatrix.TransformBox(expandedBounds);
|
||||
var treeBounds = _transformSystem.GetInvWorldMatrix(uid).TransformBox(expandedBounds);
|
||||
|
||||
comp.Tree.QueryAabb((in ComponentTreeEntry<OccluderComponent> entry) =>
|
||||
{
|
||||
@@ -965,12 +958,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (ami >= amiMax)
|
||||
return false;
|
||||
|
||||
var worldTransform = xformSystem.GetWorldMatrix(transform, xforms);
|
||||
var worldTransform = _transformSystem.GetWorldMatrix(transform, xforms);
|
||||
var box = occluder.BoundingBox;
|
||||
|
||||
var tl = worldTransform.Transform(box.TopLeft);
|
||||
var tr = worldTransform.Transform(box.TopRight);
|
||||
var br = worldTransform.Transform(box.BottomRight);
|
||||
var tl = Vector2.Transform(box.TopLeft, worldTransform);
|
||||
var tr = Vector2.Transform(box.TopRight, worldTransform);
|
||||
var br = Vector2.Transform(box.BottomRight, worldTransform);
|
||||
var bl = tl + br - tr;
|
||||
|
||||
// Faces.
|
||||
@@ -1010,9 +1003,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
//
|
||||
|
||||
// Calculate delta positions from camera.
|
||||
var dTl = eyeTransform.Transform(tl);
|
||||
var dTr = eyeTransform.Transform(tr);
|
||||
var dBl = eyeTransform.Transform(bl);
|
||||
var dTl = Vector2.Transform(tl, eyeTransform);
|
||||
var dTr = Vector2.Transform(tr, eyeTransform);
|
||||
var dBl = Vector2.Transform(bl, eyeTransform);
|
||||
var dBr = dBl + dTr - dTl;
|
||||
|
||||
// Get which neighbors are occluding.
|
||||
|
||||
@@ -33,17 +33,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
DrawingHandleWorld = new DrawingHandleWorldImpl(white, this);
|
||||
}
|
||||
|
||||
public void SetModelTransform(in Matrix3 matrix)
|
||||
public void SetModelTransform(in Matrix3x2 matrix)
|
||||
{
|
||||
_clyde.DrawSetModelTransform(matrix);
|
||||
}
|
||||
|
||||
public Matrix3 GetModelTransform()
|
||||
public Matrix3x2 GetModelTransform()
|
||||
{
|
||||
return _clyde.DrawGetModelTransform();
|
||||
}
|
||||
|
||||
public void SetProjView(in Matrix3 proj, in Matrix3 view)
|
||||
public void SetProjView(in Matrix3x2 proj, in Matrix3x2 view)
|
||||
{
|
||||
_clyde.DrawSetProjViewTransform(proj, view);
|
||||
}
|
||||
@@ -177,9 +177,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var oldModel = _clyde._currentMatrixModel;
|
||||
|
||||
var newModel = oldModel;
|
||||
position += new Vector2(oldModel.R0C2, oldModel.R1C2);
|
||||
newModel.R0C2 = 0;
|
||||
newModel.R1C2 = 0;
|
||||
position += new Vector2(oldModel.M31, oldModel.M32);
|
||||
newModel.M31 = 0;
|
||||
newModel.M32 = 0;
|
||||
SetModelTransform(newModel);
|
||||
|
||||
// Switch rendering to pseudo-world space.
|
||||
@@ -194,7 +194,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Maaaaybe this is meant to have a minus sign.
|
||||
var rot = -(float) eyeRot.Theta;
|
||||
|
||||
var view = Matrix3.CreateTransform(ofsX, ofsY, rot, scale.X, scale.Y);
|
||||
var view = Matrix3Helpers.CreateTransform(ofsX, ofsY, rot, scale.X, scale.Y);
|
||||
SetProjView(proj, view);
|
||||
}
|
||||
|
||||
@@ -297,12 +297,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_renderHandle = renderHandle;
|
||||
}
|
||||
|
||||
public override void SetTransform(in Matrix3 matrix)
|
||||
public override void SetTransform(in Matrix3x2 matrix)
|
||||
{
|
||||
_renderHandle.SetModelTransform(matrix);
|
||||
}
|
||||
|
||||
public override Matrix3 GetTransform()
|
||||
public override Matrix3x2 GetTransform()
|
||||
{
|
||||
return _renderHandle.GetModelTransform();
|
||||
}
|
||||
@@ -402,12 +402,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_renderHandle = renderHandle;
|
||||
}
|
||||
|
||||
public override void SetTransform(in Matrix3 matrix)
|
||||
public override void SetTransform(in Matrix3x2 matrix)
|
||||
{
|
||||
_renderHandle.SetModelTransform(matrix);
|
||||
}
|
||||
|
||||
public override Matrix3 GetTransform()
|
||||
public override Matrix3x2 GetTransform()
|
||||
{
|
||||
return _renderHandle.GetModelTransform();
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// This matrix is applied to most normal geometry coming in.
|
||||
// Some is applied while the batch is being created (e.g. simple texture draw calls).
|
||||
// For DrawPrimitives OTOH the model matrix is passed along with the render command so is applied in the shader.
|
||||
private Matrix3 _currentMatrixModel = Matrix3.Identity;
|
||||
private Matrix3x2 _currentMatrixModel = Matrix3x2.Identity;
|
||||
|
||||
// Buffers and data for the batching system. Written into during (queue) and processed during (submit).
|
||||
private readonly Vertex2D[] BatchVertexData = new Vertex2D[MaxBatchQuads * 4];
|
||||
@@ -84,8 +84,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// Current projection & view matrices that are being used ot render.
|
||||
// This gets updated to keep track during (queue) and (misc), but not during (submit).
|
||||
private Matrix3 _currentMatrixProj;
|
||||
private Matrix3 _currentMatrixView;
|
||||
private Matrix3x2 _currentMatrixProj;
|
||||
private Matrix3x2 _currentMatrixView;
|
||||
|
||||
// (queue) and (misc), current state of the scissor test. Null if disabled.
|
||||
private UIBox2i? _currentScissorState;
|
||||
@@ -110,25 +110,25 @@ namespace Robust.Client.Graphics.Clyde
|
||||
UniformConstantsUBO.Reallocate(constants);
|
||||
}
|
||||
|
||||
private void CalcScreenMatrices(in Vector2i screenSize, out Matrix3 proj, out Matrix3 view)
|
||||
private void CalcScreenMatrices(in Vector2i screenSize, out Matrix3x2 proj, out Matrix3x2 view)
|
||||
{
|
||||
proj = Matrix3.Identity;
|
||||
proj.R0C0 = 2f / screenSize.X;
|
||||
proj.R1C1 = -2f / screenSize.Y;
|
||||
proj.R0C2 = -1;
|
||||
proj.R1C2 = 1;
|
||||
proj = Matrix3x2.Identity;
|
||||
proj.M11 = 2f / screenSize.X;
|
||||
proj.M22 = -2f / screenSize.Y;
|
||||
proj.M31 = -1;
|
||||
proj.M32 = 1;
|
||||
|
||||
if (_currentRenderTarget.FlipY)
|
||||
{
|
||||
proj.R1C1 *= -1;
|
||||
proj.R1C2 *= -1;
|
||||
proj.M22 *= -1;
|
||||
proj.M32 *= -1;
|
||||
}
|
||||
|
||||
view = Matrix3.Identity;
|
||||
view = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
private void CalcWorldMatrices(in Vector2i screenSize, in Vector2 renderScale, IEye eye,
|
||||
out Matrix3 proj, out Matrix3 view)
|
||||
out Matrix3x2 proj, out Matrix3x2 view)
|
||||
{
|
||||
eye.GetViewMatrix(out view, renderScale);
|
||||
|
||||
@@ -136,20 +136,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void CalcWorldProjMatrix(in Vector2i screenSize, out Matrix3 proj)
|
||||
private void CalcWorldProjMatrix(in Vector2i screenSize, out Matrix3x2 proj)
|
||||
{
|
||||
proj = Matrix3.Identity;
|
||||
proj.R0C0 = EyeManager.PixelsPerMeter * 2f / screenSize.X;
|
||||
proj.R1C1 = EyeManager.PixelsPerMeter * 2f / screenSize.Y;
|
||||
proj = Matrix3x2.Identity;
|
||||
proj.M11 = EyeManager.PixelsPerMeter * 2f / screenSize.X;
|
||||
proj.M22 = EyeManager.PixelsPerMeter * 2f / screenSize.Y;
|
||||
|
||||
if (_currentRenderTarget.FlipY)
|
||||
{
|
||||
proj.R1C1 *= -1;
|
||||
proj.R1C2 *= -1;
|
||||
proj.M22 *= -1;
|
||||
proj.M32 *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetProjViewBuffer(in Matrix3 proj, in Matrix3 view)
|
||||
private void SetProjViewBuffer(in Matrix3x2 proj, in Matrix3x2 view)
|
||||
{
|
||||
// TODO: Fix perf here.
|
||||
// This immediately causes a glBufferData() call every time this is changed.
|
||||
@@ -160,7 +160,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
ProjViewUBO.Reallocate(combined);
|
||||
}
|
||||
|
||||
private void SetProjViewFull(in Matrix3 proj, in Matrix3 view)
|
||||
private void SetProjViewFull(in Matrix3x2 proj, in Matrix3x2 view)
|
||||
{
|
||||
_currentMatrixProj = proj;
|
||||
_currentMatrixView = view;
|
||||
@@ -285,21 +285,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
};
|
||||
}
|
||||
|
||||
private void _drawQuad(Vector2 a, Vector2 b, in Matrix3 modelMatrix, GLShaderProgram program)
|
||||
private void _drawQuad(Vector2 a, Vector2 b, in Matrix3x2 modelMatrix, GLShaderProgram program)
|
||||
{
|
||||
DrawQuadWithVao(QuadVAO, a, b, modelMatrix, program);
|
||||
}
|
||||
|
||||
private void DrawQuadWithVao(GLHandle vao, Vector2 a, Vector2 b, in Matrix3 modelMatrix,
|
||||
private void DrawQuadWithVao(GLHandle vao, Vector2 a, Vector2 b, in Matrix3x2 modelMatrix,
|
||||
GLShaderProgram program)
|
||||
{
|
||||
BindVertexArray(vao.Handle);
|
||||
CheckGlError();
|
||||
|
||||
var rectTransform = Matrix3.Identity;
|
||||
(rectTransform.R0C0, rectTransform.R1C1) = b - a;
|
||||
(rectTransform.R0C2, rectTransform.R1C2) = a;
|
||||
rectTransform.Multiply(modelMatrix);
|
||||
var rectTransform = Matrix3x2.Identity;
|
||||
(rectTransform.M11, rectTransform.M22) = b - a;
|
||||
(rectTransform.M31, rectTransform.M32) = a;
|
||||
rectTransform = rectTransform * modelMatrix;
|
||||
program.SetUniformMaybe(UniIModelMatrix, rectTransform);
|
||||
|
||||
_debugStats.LastGLDrawCalls += 1;
|
||||
@@ -315,7 +315,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
FlushBatchQueue();
|
||||
|
||||
// Reset renderer state.
|
||||
_currentMatrixModel = Matrix3.Identity;
|
||||
_currentMatrixModel = Matrix3x2.Identity;
|
||||
_queuedShaderInstance = _defaultShader;
|
||||
SetScissorFull(null);
|
||||
}
|
||||
@@ -496,7 +496,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
case bool b:
|
||||
program.SetUniform(name, b ? 1 : 0);
|
||||
break;
|
||||
case Matrix3 matrix3:
|
||||
case bool[] bArr:
|
||||
program.SetUniform(name, bArr);
|
||||
break;
|
||||
case Matrix3x2 matrix3:
|
||||
program.SetUniform(name, matrix3);
|
||||
break;
|
||||
case Matrix4 matrix4:
|
||||
@@ -532,17 +535,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return ref command;
|
||||
}
|
||||
|
||||
private void DrawSetModelTransform(in Matrix3 matrix)
|
||||
private void DrawSetModelTransform(in Matrix3x2 matrix)
|
||||
{
|
||||
_currentMatrixModel = matrix;
|
||||
}
|
||||
|
||||
private Matrix3 DrawGetModelTransform()
|
||||
private Matrix3x2 DrawGetModelTransform()
|
||||
{
|
||||
return _currentMatrixModel;
|
||||
}
|
||||
|
||||
private void DrawSetProjViewTransform(in Matrix3 proj, in Matrix3 view)
|
||||
private void DrawSetProjViewTransform(in Matrix3x2 proj, in Matrix3x2 view)
|
||||
{
|
||||
BreakBatch();
|
||||
|
||||
@@ -571,9 +574,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
EnsureBatchSpaceAvailable(4, GetQuadBatchIndexCount());
|
||||
EnsureBatchState(texture, true, GetQuadBatchPrimitiveType(), _queuedShader);
|
||||
|
||||
bl = _currentMatrixModel.Transform(bl);
|
||||
br = _currentMatrixModel.Transform(br);
|
||||
tr = _currentMatrixModel.Transform(tr);
|
||||
bl = Vector2.Transform(bl, _currentMatrixModel);
|
||||
br = Vector2.Transform(br, _currentMatrixModel);
|
||||
tr = Vector2.Transform(tr, _currentMatrixModel);
|
||||
tl = tr + bl - br;
|
||||
|
||||
// TODO: split batch if necessary.
|
||||
@@ -676,8 +679,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
EnsureBatchSpaceAvailable(2, 0);
|
||||
EnsureBatchState(_stockTextureWhite.TextureId, false, BatchPrimitiveType.LineList, _queuedShader);
|
||||
|
||||
a = _currentMatrixModel.Transform(a);
|
||||
b = _currentMatrixModel.Transform(b);
|
||||
a = Vector2.Transform(a, _currentMatrixModel);
|
||||
b = Vector2.Transform(b, _currentMatrixModel);
|
||||
|
||||
// TODO: split batch if necessary.
|
||||
var vIdx = BatchVertexIndex;
|
||||
@@ -807,7 +810,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
command.DrawBatch.ShaderInstance = metaData.ShaderInstance;
|
||||
|
||||
command.DrawBatch.Count = currentIndex - metaData.StartIndex;
|
||||
command.DrawBatch.ModelMatrix = Matrix3.Identity;
|
||||
command.DrawBatch.ModelMatrix = Matrix3x2.Identity;
|
||||
|
||||
_debugStats.LastBatches += 1;
|
||||
}
|
||||
@@ -882,7 +885,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_queuedRenderCommands.Clear();
|
||||
_currentViewport = null;
|
||||
_lightingReady = false;
|
||||
_currentMatrixModel = Matrix3.Identity;
|
||||
_currentMatrixModel = Matrix3x2.Identity;
|
||||
SetScissorFull(null);
|
||||
BindRenderTargetFull(_mainWindow!.RenderTarget);
|
||||
_batchMetaData = null;
|
||||
@@ -961,13 +964,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public BatchPrimitiveType PrimitiveType;
|
||||
|
||||
// TODO: this makes the render commands so much more large please remove.
|
||||
public Matrix3 ModelMatrix;
|
||||
public Matrix3x2 ModelMatrix;
|
||||
}
|
||||
|
||||
private struct RenderCommandProjViewMatrix
|
||||
{
|
||||
public Matrix3 ProjMatrix;
|
||||
public Matrix3 ViewMatrix;
|
||||
public Matrix3x2 ProjMatrix;
|
||||
public Matrix3x2 ViewMatrix;
|
||||
}
|
||||
|
||||
private struct RenderCommandScissor
|
||||
@@ -1056,11 +1059,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private readonly struct FullStoredRendererState
|
||||
{
|
||||
public readonly Matrix3 ProjMatrix;
|
||||
public readonly Matrix3 ViewMatrix;
|
||||
public readonly Matrix3x2 ProjMatrix;
|
||||
public readonly Matrix3x2 ViewMatrix;
|
||||
public readonly LoadedRenderTarget RenderTarget;
|
||||
|
||||
public FullStoredRendererState(in Matrix3 projMatrix, in Matrix3 viewMatrix,
|
||||
public FullStoredRendererState(in Matrix3x2 projMatrix, in Matrix3x2 viewMatrix,
|
||||
LoadedRenderTarget renderTarget)
|
||||
{
|
||||
ProjMatrix = projMatrix;
|
||||
|
||||
@@ -506,7 +506,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
data.Parameters[name] = value;
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, in Matrix3 value)
|
||||
private protected override void SetParameterImpl(string name, bool[] value)
|
||||
{
|
||||
var data = Parent._shaderInstances[Handle];
|
||||
data.ParametersDirty = true;
|
||||
data.Parameters[name] = value;
|
||||
}
|
||||
|
||||
|
||||
private protected override void SetParameterImpl(string name, in Matrix3x2 value)
|
||||
{
|
||||
var data = Parent._shaderInstances[Handle];
|
||||
data.ParametersDirty = true;
|
||||
|
||||
@@ -61,12 +61,11 @@ internal partial class Clyde
|
||||
var index = 0;
|
||||
var added = 0;
|
||||
var opts = new ParallelOptions { MaxDegreeOfParallelism = _parMan.ParallelProcessCount };
|
||||
var xformSystem = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
|
||||
|
||||
foreach (var (treeOwner, comp) in _entitySystemManager.GetEntitySystem<SpriteTreeSystem>().GetIntersectingTrees(map, worldBounds))
|
||||
foreach (var (treeOwner, comp) in _spriteTreeSystem.GetIntersectingTrees(map, worldBounds))
|
||||
{
|
||||
var treeXform = query.GetComponent(treeOwner);
|
||||
var bounds = xformSystem.GetInvWorldMatrix(treeOwner).TransformBox(worldBounds);
|
||||
var bounds = _transformSystem.GetInvWorldMatrix(treeOwner).TransformBox(worldBounds);
|
||||
DebugTools.Assert(treeXform.MapUid == treeXform.ParentUid || !treeXform.ParentUid.IsValid());
|
||||
|
||||
treeData = treeData with
|
||||
|
||||
39
Robust.Client/Graphics/Clyde/Clyde.Systems.cs
Normal file
39
Robust.Client/Graphics/Clyde/Clyde.Systems.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Robust.Client.ComponentTrees;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde;
|
||||
|
||||
internal sealed partial class Clyde
|
||||
{
|
||||
// Caches entity systems required by Clyde.
|
||||
|
||||
private MapSystem _mapSystem = default!;
|
||||
private LightTreeSystem _lightTreeSystem = default!;
|
||||
private TransformSystem _transformSystem = default!;
|
||||
private SpriteTreeSystem _spriteTreeSystem = default!;
|
||||
private ClientOccluderSystem _occluderSystem = default!;
|
||||
|
||||
private void InitSystems()
|
||||
{
|
||||
_entityManager.AfterStartup += EntityManagerOnAfterStartup;
|
||||
_entityManager.AfterShutdown += EntityManagerOnAfterShutdown;
|
||||
}
|
||||
|
||||
private void EntityManagerOnAfterStartup()
|
||||
{
|
||||
_mapSystem = _entitySystemManager.GetEntitySystem<MapSystem>();
|
||||
_lightTreeSystem = _entitySystemManager.GetEntitySystem<LightTreeSystem>();
|
||||
_transformSystem = _entitySystemManager.GetEntitySystem<TransformSystem>();
|
||||
_spriteTreeSystem = _entitySystemManager.GetEntitySystem<SpriteTreeSystem>();
|
||||
_occluderSystem = _entitySystemManager.GetEntitySystem<ClientOccluderSystem>();
|
||||
}
|
||||
|
||||
private void EntityManagerOnAfterShutdown()
|
||||
{
|
||||
_mapSystem = null!;
|
||||
_lightTreeSystem = null!;
|
||||
_transformSystem = null!;
|
||||
_spriteTreeSystem = null!;
|
||||
_occluderSystem = null!;
|
||||
}
|
||||
}
|
||||
@@ -664,7 +664,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
GL.GetnTexImage(TextureTarget.Texture2D, 0, PF.Rgba, PT.UnsignedByte, bufSize, (IntPtr) p);
|
||||
GL.GetTexImage(TextureTarget.Texture2D, 0, PF.Rgba, PT.UnsignedByte, (IntPtr) p);
|
||||
}
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, curTexture2D);
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// view matrix
|
||||
vp.Eye.GetViewMatrixInv(out var viewMatrixInv, vp.RenderScale);
|
||||
point = viewMatrixInv * point;
|
||||
point = Vector2.Transform(point, viewMatrixInv);
|
||||
|
||||
return point;
|
||||
}
|
||||
@@ -136,7 +136,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// view matrix
|
||||
Eye.GetViewMatrixInv(out var viewMatrixInv, RenderScale);
|
||||
newPoint = viewMatrixInv * newPoint;
|
||||
newPoint = Vector2.Transform(newPoint, viewMatrixInv);
|
||||
|
||||
return new MapCoordinates(newPoint, Eye.Position.MapId);
|
||||
}
|
||||
@@ -150,7 +150,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var newPoint = point;
|
||||
|
||||
eye.GetViewMatrix(out var viewMatrix, RenderScale);
|
||||
newPoint = viewMatrix * newPoint;
|
||||
newPoint = Vector2.Transform(newPoint, viewMatrix);
|
||||
|
||||
// (inlined version of UiProjMatrix)
|
||||
newPoint *= new Vector2(1, -1) * EyeManager.PixelsPerMeter;
|
||||
@@ -159,14 +159,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return newPoint;
|
||||
}
|
||||
|
||||
public Matrix3 GetWorldToLocalMatrix()
|
||||
public Matrix3x2 GetWorldToLocalMatrix()
|
||||
{
|
||||
if (Eye == null)
|
||||
return Matrix3.Identity;
|
||||
return Matrix3x2.Identity;
|
||||
|
||||
Eye.GetViewMatrix(out var viewMatrix, RenderScale * new Vector2(EyeManager.PixelsPerMeter, -EyeManager.PixelsPerMeter));
|
||||
viewMatrix.R0C2 += Size.X / 2f;
|
||||
viewMatrix.R1C2 += Size.Y / 2f;
|
||||
viewMatrix.M31 += Size.X / 2f;
|
||||
viewMatrix.M32 += Size.Y / 2f;
|
||||
return viewMatrix;
|
||||
}
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
if (!TryInitMainWindow(glSpec, out lastError))
|
||||
{
|
||||
Logger.DebugS("clyde.win", $"OpenGL {glSpec.OpenGLVersion} unsupported: {lastError}");
|
||||
_sawmillWin.Debug($"OpenGL {glSpec.OpenGLVersion} unsupported: {lastError}");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
else
|
||||
{
|
||||
if (!TryInitMainWindow(null, out lastError))
|
||||
Logger.DebugS("clyde.win", $"Failed to create window: {lastError}");
|
||||
_sawmillWin.Debug($"Failed to create window: {lastError}");
|
||||
else
|
||||
succeeded = true;
|
||||
}
|
||||
@@ -230,8 +230,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
Logger.FatalS("clyde.win",
|
||||
"Failed to create main game window! " +
|
||||
_sawmillWin.Fatal("Failed to create main game window! " +
|
||||
"This probably means your GPU is too old to run the game. " +
|
||||
$"That or update your graphics drivers. {lastError}");
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using OpenToolkit;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.ResourceManagement;
|
||||
@@ -46,6 +47,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
[Dependency] private readonly IDependencyCollection _deps = default!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly ClientEntityManager _entityManager = default!;
|
||||
|
||||
private GLUniformBuffer<ProjViewMatrices> ProjViewUBO = default!;
|
||||
private GLUniformBuffer<UniformConstants> UniformConstantsUBO = default!;
|
||||
@@ -78,6 +80,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private ISawmill _clydeSawmill = default!;
|
||||
private ISawmill _sawmillOgl = default!;
|
||||
private ISawmill _sawmillWin = default!;
|
||||
|
||||
private IBindingsContext _glBindingsContext = default!;
|
||||
private bool _earlyGLInit;
|
||||
@@ -94,6 +97,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_clydeSawmill = _logManager.GetSawmill("clyde");
|
||||
_sawmillOgl = _logManager.GetSawmill("clyde.ogl");
|
||||
_sawmillWin = _logManager.GetSawmill("clyde.win");
|
||||
|
||||
_cfg.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
|
||||
_cfg.OnValueChanged(CVars.DisplayVSync, VSyncChanged, true);
|
||||
@@ -122,6 +126,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_gameThread = Thread.CurrentThread;
|
||||
|
||||
InitSystems();
|
||||
|
||||
InitGLContextManager();
|
||||
if (!InitMainWindowAndRenderer())
|
||||
return false;
|
||||
|
||||
@@ -361,7 +361,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, in Matrix3 value)
|
||||
private protected override void SetParameterImpl(string name, bool[] value)
|
||||
{
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, in Matrix3x2 value)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -482,7 +486,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return default;
|
||||
}
|
||||
|
||||
public Matrix3 GetWorldToLocalMatrix() => default;
|
||||
public Matrix3x2 GetWorldToLocalMatrix() => default;
|
||||
|
||||
public Vector2 WorldToLocal(Vector2 point)
|
||||
{
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// NOTE: This class only handles GLES3/D3D11.
|
||||
// For anything lower we just let ANGLE fall back and do the work 100%.
|
||||
|
||||
private readonly ISawmill _sawmill;
|
||||
|
||||
private IDXGIFactory1* _factory;
|
||||
private IDXGIAdapter1* _adapter;
|
||||
private ID3D11Device* _device;
|
||||
@@ -58,6 +60,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public GLContextAngle(Clyde clyde) : base(clyde)
|
||||
{
|
||||
_sawmill = clyde._logManager.GetSawmill("clyde.ogl.angle");
|
||||
}
|
||||
|
||||
public override GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version)
|
||||
@@ -187,7 +190,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("clyde.ogl.angle", $"Failed to initialize custom ANGLE: {e}");
|
||||
_sawmill.Error($"Failed to initialize custom ANGLE: {e}");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
@@ -207,7 +210,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private void TryInitializeCore()
|
||||
{
|
||||
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(null, EGL_EXTENSIONS));
|
||||
Logger.DebugS("clyde.ogl.angle", $"EGL client extensions: {extensions}!");
|
||||
_sawmill.Debug($"EGL client extensions: {extensions}!");
|
||||
|
||||
CreateD3D11Device();
|
||||
CreateEglContext();
|
||||
@@ -232,10 +235,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var version = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VERSION));
|
||||
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_EXTENSIONS));
|
||||
|
||||
Logger.DebugS("clyde.ogl.angle", "EGL initialized!");
|
||||
Logger.DebugS("clyde.ogl.angle", $"EGL vendor: {vendor}!");
|
||||
Logger.DebugS("clyde.ogl.angle", $"EGL version: {version}!");
|
||||
Logger.DebugS("clyde.ogl.angle", $"EGL extensions: {extensions}!");
|
||||
_sawmill.Debug("EGL initialized!");
|
||||
_sawmill.Debug($"EGL vendor: {vendor}!");
|
||||
_sawmill.Debug($"EGL version: {version}!");
|
||||
_sawmill.Debug($"EGL extensions: {extensions}!");
|
||||
|
||||
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE)
|
||||
throw new Exception("eglBindAPI failed.");
|
||||
@@ -262,11 +265,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (numConfigs == 0)
|
||||
throw new Exception("No compatible EGL configurations returned!");
|
||||
|
||||
Logger.DebugS("clyde.ogl.angle", $"{numConfigs} EGL configs possible!");
|
||||
_sawmill.Debug($"{numConfigs} EGL configs possible!");
|
||||
|
||||
for (var i = 0; i < numConfigs; i++)
|
||||
{
|
||||
Logger.DebugS("clyde.ogl.angle", DumpEglConfig(_eglDisplay, configs[i]));
|
||||
_sawmill.Debug(DumpEglConfig(_eglDisplay, configs[i]));
|
||||
}
|
||||
|
||||
_eglConfig = configs[0];
|
||||
@@ -286,7 +289,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (_eglContext == (void*) EGL_NO_CONTEXT)
|
||||
throw new Exception("eglCreateContext failed!");
|
||||
|
||||
Logger.DebugS("clyde.ogl.angle", "EGL context created!");
|
||||
_sawmill.Debug("EGL context created!");
|
||||
|
||||
Clyde._openGLVersion = _es3 ? RendererOpenGLVersion.GLES3 : RendererOpenGLVersion.GLES2;
|
||||
}
|
||||
@@ -311,11 +314,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
if (_adapter == null)
|
||||
{
|
||||
Logger.WarningS("clyde.ogl.angle",
|
||||
$"Unable to find display adapter with requested name: {adapterName}");
|
||||
_sawmill.Warning($"Unable to find display adapter with requested name: {adapterName}");
|
||||
}
|
||||
|
||||
Logger.DebugS("clyde.ogl.angle", $"Found display adapter with name: {adapterName}");
|
||||
_sawmill.Debug($"Found display adapter with name: {adapterName}");
|
||||
}
|
||||
|
||||
#pragma warning disable CA1416
|
||||
@@ -415,9 +417,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var descName = ((ReadOnlySpan<char>)desc.Description).TrimEnd('\0');
|
||||
|
||||
Logger.DebugS("clyde.ogl.angle", "Successfully created D3D11 device!");
|
||||
Logger.DebugS("clyde.ogl.angle", $"D3D11 Device Adapter: {descName.ToString()}");
|
||||
Logger.DebugS("clyde.ogl.angle", $"D3D11 Device FL: {_deviceFl}");
|
||||
_sawmill.Debug("Successfully created D3D11 device!");
|
||||
_sawmill.Debug($"D3D11 Device Adapter: {descName.ToString()}");
|
||||
_sawmill.Debug($"D3D11 Device FL: {_deviceFl}");
|
||||
|
||||
if (_deviceFl == D3D_FEATURE_LEVEL_9_1)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private readonly Dictionary<WindowId, WindowData> _windowData = new();
|
||||
|
||||
private readonly ISawmill _sawmill;
|
||||
|
||||
private void* _eglDisplay;
|
||||
private void* _eglContext;
|
||||
private void* _eglConfig;
|
||||
@@ -30,6 +32,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public GLContextEgl(Clyde clyde) : base(clyde)
|
||||
{
|
||||
_sawmill = clyde._logManager.GetSawmill("clyde.ogl.egl");
|
||||
}
|
||||
|
||||
public override GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version)
|
||||
@@ -47,7 +50,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public void InitializePublic()
|
||||
{
|
||||
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(null, EGL_EXTENSIONS));
|
||||
Logger.DebugS("clyde.ogl.egl", $"EGL client extensions: {extensions}!");
|
||||
_sawmill.Debug($"EGL client extensions: {extensions}!");
|
||||
}
|
||||
|
||||
public override void WindowCreated(GLContextSpec? spec, WindowReg reg)
|
||||
@@ -133,10 +136,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var version = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VERSION));
|
||||
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_EXTENSIONS));
|
||||
|
||||
Logger.DebugS("clyde.ogl.egl", "EGL initialized!");
|
||||
Logger.DebugS("clyde.ogl.egl", $"EGL vendor: {vendor}!");
|
||||
Logger.DebugS("clyde.ogl.egl", $"EGL version: {version}!");
|
||||
Logger.DebugS("clyde.ogl.egl", $"EGL extensions: {extensions}!");
|
||||
_sawmill.Debug("EGL initialized!");
|
||||
_sawmill.Debug($"EGL vendor: {vendor}!");
|
||||
_sawmill.Debug($"EGL version: {version}!");
|
||||
_sawmill.Debug($"EGL extensions: {extensions}!");
|
||||
|
||||
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE)
|
||||
throw new Exception("eglBindAPI failed.");
|
||||
@@ -164,11 +167,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (numConfigs == 0)
|
||||
throw new Exception("No compatible EGL configurations returned!");
|
||||
|
||||
Logger.DebugS("clyde.ogl.egl", $"{numConfigs} EGL configs possible!");
|
||||
_sawmill.Debug($"{numConfigs} EGL configs possible!");
|
||||
|
||||
for (var i = 0; i < numConfigs; i++)
|
||||
{
|
||||
Logger.DebugS("clyde.ogl.egl", DumpEglConfig(_eglDisplay, configs[i]));
|
||||
_sawmill.Debug(DumpEglConfig(_eglDisplay, configs[i]));
|
||||
}
|
||||
|
||||
_eglConfig = configs[0];
|
||||
@@ -183,7 +186,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (_eglContext == (void*) EGL_NO_CONTEXT)
|
||||
throw new Exception("eglCreateContext failed!");
|
||||
|
||||
Logger.DebugS("clyde.ogl.egl", "EGL context created!");
|
||||
_sawmill.Debug("EGL context created!");
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
|
||||
@@ -247,28 +247,33 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.Uniform1(uniformId, singles.Length, singles);
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, in Matrix3 matrix)
|
||||
public void SetUniform(string uniformName, in Matrix3x2 matrix)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, matrix);
|
||||
}
|
||||
|
||||
public void SetUniform(int uniformName, in Matrix3 matrix)
|
||||
public void SetUniform(int uniformName, in Matrix3x2 matrix)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, matrix);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void SetUniformDirect(int slot, in Matrix3 value, bool transpose=true)
|
||||
private unsafe void SetUniformDirect(int slot, in Matrix3x2 value)
|
||||
{
|
||||
Matrix3 tmpTranspose = value;
|
||||
if (transpose)
|
||||
{
|
||||
// transposition not supported on GLES2, & no access to _hasGLES
|
||||
tmpTranspose.Transpose();
|
||||
}
|
||||
GL.UniformMatrix3(slot, 1, false, (float*) &tmpTranspose);
|
||||
// We put the rows of the input matrix into the columns of our GPU matrices
|
||||
// this transpose is required, as in C#, we premultiply vectors with matrices
|
||||
// (vM) while GL postmultiplies vectors with matrices (Mv); however, since
|
||||
// the Matrix3x2 data is stored row-major, and GL uses column-major, the
|
||||
// memory layout is the same (or would be, if Matrix3x2 didn't have an
|
||||
// implicit column)
|
||||
var buf = stackalloc float[9]{
|
||||
value.M11, value.M12, 0,
|
||||
value.M21, value.M22, 0,
|
||||
value.M31, value.M32, 1
|
||||
};
|
||||
GL.UniformMatrix3(slot, 1, false, (float*)buf);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
@@ -413,6 +418,37 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, bool[] bools)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, bools);
|
||||
}
|
||||
|
||||
public void SetUniform(int uniformName, bool[] bools)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, bools);
|
||||
}
|
||||
|
||||
private void SetUniformDirect(int slot, bool[] bools)
|
||||
{
|
||||
Span<int> intBools = stackalloc int[bools.Length];
|
||||
|
||||
for (var i = 0; i < bools.Length; i++)
|
||||
{
|
||||
intBools[i] = bools[i] ? 1 : 0;
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (int* intBoolsPtr = intBools)
|
||||
{
|
||||
GL.Uniform1(slot, bools.Length, intBoolsPtr);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformTexture(string uniformName, TextureUnit textureUnit)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
@@ -474,7 +510,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(string uniformName, in Matrix3 value)
|
||||
public void SetUniformMaybe(string uniformName, in Matrix3x2 value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
@@ -490,7 +526,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(int uniformName, in Matrix3 value)
|
||||
public void SetUniformMaybe(int uniformName, in Matrix3x2 value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
|
||||
@@ -44,19 +44,19 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
var matrix = Matrix3.CreateTransform(in position, in rotation, in scale);
|
||||
var matrix = Matrix3Helpers.CreateTransform(in position, in rotation, in scale);
|
||||
SetTransform(in matrix);
|
||||
}
|
||||
|
||||
public void SetTransform(in Vector2 position, in Angle rotation)
|
||||
{
|
||||
var matrix = Matrix3.CreateTransform(in position, in rotation);
|
||||
var matrix = Matrix3Helpers.CreateTransform(in position, in rotation);
|
||||
SetTransform(in matrix);
|
||||
}
|
||||
|
||||
public abstract void SetTransform(in Matrix3 matrix);
|
||||
public abstract void SetTransform(in Matrix3x2 matrix);
|
||||
|
||||
public abstract Matrix3 GetTransform();
|
||||
public abstract Matrix3x2 GetTransform();
|
||||
|
||||
public abstract void UseShader(ShaderInstance? shader);
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Robust.Client.Graphics
|
||||
/// <summary>
|
||||
/// Matrix equivalent of <see cref="LocalToWorld(Vector2)"/>.
|
||||
/// </summary>
|
||||
Matrix3 GetWorldToLocalMatrix();
|
||||
Matrix3x2 GetWorldToLocalMatrix();
|
||||
|
||||
/// <summary>
|
||||
/// Converts a point in world-space to the viewport's screen coordinates.
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Robust.Client.Graphics
|
||||
/// If set to true, <see cref="ScreenTexture"/> will be set to the current frame (at the moment before the overlay is rendered). This can be costly to performance, but
|
||||
/// some shaders will require it as a passed in uniform to operate.
|
||||
/// </summary>
|
||||
public virtual bool RequestScreenTexture => false;
|
||||
public virtual bool RequestScreenTexture { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// If <see cref="RequestScreenTexture"> is true, then this will be set to the texture corresponding to the current frame. If false, it will always be null.
|
||||
|
||||
@@ -224,7 +224,8 @@ namespace Robust.Client.Graphics
|
||||
// TODO: add support for int, and vec3/4 arrays
|
||||
return
|
||||
(type == ShaderDataType.Float) ||
|
||||
(type == ShaderDataType.Vec2);
|
||||
(type == ShaderDataType.Vec2) ||
|
||||
(type == ShaderDataType.Bool);
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
|
||||
@@ -148,7 +148,14 @@ namespace Robust.Client.Graphics
|
||||
SetParameterImpl(name, value);
|
||||
}
|
||||
|
||||
public void SetParameter(string name, in Matrix3 value)
|
||||
public void SetParameter(string name, bool[] value)
|
||||
{
|
||||
EnsureAlive();
|
||||
EnsureMutable();
|
||||
SetParameterImpl(name, value);
|
||||
}
|
||||
|
||||
public void SetParameter(string name, in Matrix3x2 value)
|
||||
{
|
||||
EnsureAlive();
|
||||
EnsureMutable();
|
||||
@@ -219,7 +226,8 @@ namespace Robust.Client.Graphics
|
||||
private protected abstract void SetParameterImpl(string name, int value);
|
||||
private protected abstract void SetParameterImpl(string name, Vector2i value);
|
||||
private protected abstract void SetParameterImpl(string name, bool value);
|
||||
private protected abstract void SetParameterImpl(string name, in Matrix3 value);
|
||||
private protected abstract void SetParameterImpl(string name, bool[] value);
|
||||
private protected abstract void SetParameterImpl(string name, in Matrix3x2 value);
|
||||
private protected abstract void SetParameterImpl(string name, in Matrix4 value);
|
||||
private protected abstract void SetParameterImpl(string name, Texture value);
|
||||
private protected abstract void SetStencilImpl(StencilParameters value);
|
||||
|
||||
@@ -176,7 +176,7 @@ namespace Robust.Client.Graphics
|
||||
return node.AsVector4();
|
||||
}
|
||||
case ShaderDataType.Mat3:
|
||||
return node.AsMatrix3();
|
||||
return node.AsMatrix3x2();
|
||||
case ShaderDataType.Mat4:
|
||||
return node.AsMatrix4();
|
||||
default:
|
||||
@@ -219,7 +219,7 @@ namespace Robust.Client.Graphics
|
||||
case bool i:
|
||||
instance.SetParameter(key, i);
|
||||
break;
|
||||
case Matrix3 i:
|
||||
case Matrix3x2 i:
|
||||
instance.SetParameter(key, i);
|
||||
break;
|
||||
case Matrix4 i:
|
||||
|
||||
@@ -346,7 +346,7 @@ namespace Robust.Client.Input
|
||||
{
|
||||
if (binding.CanRepeat)
|
||||
{
|
||||
return SetBindState(binding, BoundKeyState.Down, uiOnly);
|
||||
return SetBindState(binding, BoundKeyState.Down, uiOnly, isRepeat);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -375,7 +375,7 @@ namespace Robust.Client.Input
|
||||
SetBindState(binding, BoundKeyState.Up);
|
||||
}
|
||||
|
||||
private bool SetBindState(KeyBinding binding, BoundKeyState state, bool uiOnly = false)
|
||||
private bool SetBindState(KeyBinding binding, BoundKeyState state, bool uiOnly = false, bool isRepeat = false)
|
||||
{
|
||||
if (binding.BindingType == KeyBindingType.Command && state == BoundKeyState.Down)
|
||||
{
|
||||
@@ -387,6 +387,7 @@ namespace Robust.Client.Input
|
||||
// I honestly have no idea what the best solution here is.
|
||||
// note from the future: context switches won't cause re-entrancy anymore because InputContextContainer defers context switches
|
||||
DebugTools.Assert(!_currentlyFindingViewport, "Re-entrant key events??");
|
||||
DebugTools.Assert(!isRepeat || binding.CanRepeat);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -399,7 +400,7 @@ namespace Robust.Client.Input
|
||||
binding.State = state;
|
||||
|
||||
var eventArgs = new BoundKeyEventArgs(binding.Function, binding.State,
|
||||
MouseScreenPosition, binding.CanFocus);
|
||||
MouseScreenPosition, binding.CanFocus, isRepeat);
|
||||
|
||||
// UI returns true here into blockPass if it wants to prevent us from giving input events
|
||||
// to the viewport, but doesn't want it hard-handled so we keep processing possible key actions.
|
||||
|
||||
@@ -21,9 +21,12 @@ using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace Robust.Client.Map
|
||||
{
|
||||
internal sealed class ClydeTileDefinitionManager : TileDefinitionManager, IClydeTileDefinitionManager
|
||||
internal sealed class ClydeTileDefinitionManager : TileDefinitionManager, IClydeTileDefinitionManager, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _manager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private Texture? _tileTextureAtlas;
|
||||
|
||||
@@ -98,8 +101,7 @@ namespace Robust.Client.Map
|
||||
if (imgWidth >= 2048 || imgHeight >= 2048)
|
||||
{
|
||||
// Sanity warning, some machines don't have textures larger than this and need multiple atlases.
|
||||
Logger.WarningS("clyde",
|
||||
$"Tile texture atlas is ({imgWidth} x {imgHeight}), larger than 2048 x 2048. If you really need {tileCount} tiles, file an issue on RobustToolbox.");
|
||||
_sawmill.Warning($"Tile texture atlas is ({imgWidth} x {imgHeight}), larger than 2048 x 2048. If you really need {tileCount} tiles, file an issue on RobustToolbox.");
|
||||
}
|
||||
|
||||
var column = 1;
|
||||
@@ -151,6 +153,11 @@ namespace Robust.Client.Map
|
||||
|
||||
_tileTextureAtlas = Texture.LoadFromImage(sheet, "Tile Atlas");
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_sawmill = _logManager.GetSawmill("clyde");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ReloadTileTexturesCommand : LocalizedCommands
|
||||
|
||||
@@ -120,6 +120,6 @@ public sealed class TileEdgeOverlay : GridOverlay
|
||||
}
|
||||
}
|
||||
|
||||
args.WorldHandle.SetTransform(Matrix3.Identity);
|
||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Robust.Client.Physics
|
||||
return true;
|
||||
}, true);
|
||||
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Robust.Client.Placement.Modes
|
||||
|
||||
public SnapgridCenter(PlacementManager pMan) : base(pMan) { }
|
||||
|
||||
public override void Render(DrawingHandleWorld handle)
|
||||
public override void Render(in OverlayDrawArgs args)
|
||||
{
|
||||
if (Grid != null)
|
||||
{
|
||||
@@ -34,18 +34,18 @@ namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
var from = ScreenToWorld(new Vector2(a, 0));
|
||||
var to = ScreenToWorld(new Vector2(a, viewportSize.Y));
|
||||
handle.DrawLine(from, to, new Color(0, 0, 1f));
|
||||
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 0.3f));
|
||||
}
|
||||
for (var a = gridstart.Y; a < viewportSize.Y; a += SnapSize * EyeManager.PixelsPerMeter)
|
||||
{
|
||||
var from = ScreenToWorld(new Vector2(0, a));
|
||||
var to = ScreenToWorld(new Vector2(viewportSize.X, a));
|
||||
handle.DrawLine(from, to, new Color(0, 0, 1f));
|
||||
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 0.3f));
|
||||
}
|
||||
}
|
||||
|
||||
// Draw grid BELOW the ghost thing.
|
||||
base.Render(handle);
|
||||
base.Render(args);
|
||||
}
|
||||
|
||||
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
|
||||
|
||||
@@ -628,20 +628,20 @@ namespace Robust.Client.Placement
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Render(DrawingHandleWorld handle)
|
||||
private void Render(in OverlayDrawArgs args)
|
||||
{
|
||||
if (CurrentMode == null || !IsActive)
|
||||
{
|
||||
if (EraserRect.HasValue)
|
||||
{
|
||||
handle.UseShader(_drawingShader);
|
||||
handle.DrawRect(EraserRect.Value, new Color(255, 0, 0, 50));
|
||||
handle.UseShader(null);
|
||||
args.WorldHandle.UseShader(_drawingShader);
|
||||
args.WorldHandle.DrawRect(EraserRect.Value, new Color(255, 0, 0, 50));
|
||||
args.WorldHandle.UseShader(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentMode.Render(handle);
|
||||
CurrentMode.Render(args);
|
||||
|
||||
if (CurrentPermission is not {Range: > 0} ||
|
||||
!CurrentMode.RangeRequired ||
|
||||
@@ -650,7 +650,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
var worldPos = EntityManager.GetComponent<TransformComponent>(controlled).WorldPosition;
|
||||
|
||||
handle.DrawCircle(worldPos, CurrentPermission.Range, new Color(1, 1, 1, 0.25f));
|
||||
args.WorldHandle.DrawCircle(worldPos, CurrentPermission.Range, new Color(1, 1, 1, 0.25f));
|
||||
}
|
||||
|
||||
private void HandleStartPlacement(MsgPlacement msg)
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace Robust.Client.Placement
|
||||
/// <returns></returns>
|
||||
public abstract bool IsValidPosition(EntityCoordinates position);
|
||||
|
||||
public virtual void Render(DrawingHandleWorld handle)
|
||||
public virtual void Render(in OverlayDrawArgs args)
|
||||
{
|
||||
var uid = pManager.CurrentPlacementOverlayEntity;
|
||||
if (!pManager.EntityManager.TryGetComponent(uid, out SpriteComponent? sprite) || !sprite.Visible)
|
||||
@@ -125,7 +125,8 @@ namespace Robust.Client.Placement
|
||||
var worldRot = pManager.EntityManager.GetComponent<TransformComponent>(coordinate.EntityId).WorldRotation + dirAng;
|
||||
|
||||
sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor;
|
||||
spriteSys.Render(uid.Value, sprite, handle, pManager.EyeManager.CurrentEye.Rotation, worldRot, worldPos);
|
||||
var rot = args.Viewport.Eye?.Rotation ?? default;
|
||||
spriteSys.Render(uid.Value, sprite, args.WorldHandle, rot, worldRot, worldPos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
_manager.Render(args.WorldHandle);
|
||||
_manager.Render(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +167,6 @@ namespace Robust.Client.Player
|
||||
if (_client.RunLevel != ClientRunLevel.SinglePlayerGame)
|
||||
Sawmill.Warning($"Attaching local player to an entity {EntManager.ToPrettyString(uid)} without an eye. This eye will not be netsynced and may cause issues.");
|
||||
var eye = (EyeComponent) Factory.GetComponent(typeof(EyeComponent));
|
||||
eye.Owner = uid.Value;
|
||||
eye.NetSyncEnabled = false;
|
||||
EntManager.AddComponent(uid.Value, eye);
|
||||
}
|
||||
@@ -261,8 +260,8 @@ namespace Robust.Client.Player
|
||||
{
|
||||
// This is a new userid, so we create a new session.
|
||||
DebugTools.Assert(state.UserId != LocalPlayer?.UserId);
|
||||
var newSession = (CommonSession) CreateAndAddSession(state.UserId, state.Name);
|
||||
newSession.Ping = state.Ping;
|
||||
var newSession = (ICommonSessionInternal)CreateAndAddSession(state.UserId, state.Name);
|
||||
newSession.SetPing(state.Ping);
|
||||
SetStatus(newSession, state.Status);
|
||||
SetAttachedEntity(newSession, controlled, out _, true);
|
||||
dirty = true;
|
||||
@@ -279,9 +278,9 @@ namespace Robust.Client.Player
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
var local = (CommonSession) session;
|
||||
local.Name = state.Name;
|
||||
local.Ping = state.Ping;
|
||||
var local = (ICommonSessionInternal)session;
|
||||
local.SetName(state.Name);
|
||||
local.SetPing(state.Ping);
|
||||
SetStatus(local, state.Status);
|
||||
SetAttachedEntity(local, controlled, out _, true);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,31 @@ namespace Robust.Client.Replays.Loading;
|
||||
// so that when jumping to tick 1001 the client only has to apply states for tick 1000 and 1001, instead of 0, 1, 2, ...
|
||||
public sealed partial class ReplayLoadManager
|
||||
{
|
||||
// Scratch data used by UpdateEntityStates.
|
||||
// Avoids copying changes for every change to an entity between checkpoints, instead copies once per checkpoint on
|
||||
// first change. We can also use this to avoid building a dictionary of ComponentChange inside the inner loop.
|
||||
private class UpdateScratchData
|
||||
{
|
||||
public Dictionary<ushort, ComponentChange> Changes;
|
||||
public EntityState lastChange;
|
||||
public HashSet<ushort>? netComps;
|
||||
|
||||
public UpdateScratchData(EntityState oldEntState)
|
||||
{
|
||||
Changes = oldEntState.ComponentChanges.Value.ToDictionary(x => x.NetID);
|
||||
lastChange = oldEntState;
|
||||
netComps = oldEntState.NetComponents;
|
||||
}
|
||||
|
||||
public EntityState BakeChanges()
|
||||
{
|
||||
return new EntityState(lastChange.NetEntity,
|
||||
Changes.Values.ToList(),
|
||||
lastChange.EntityLastModified,
|
||||
netComps);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(CheckpointState[], TimeSpan[])> GenerateCheckpointsAsync(
|
||||
ReplayMessage? initMessages,
|
||||
HashSet<string> initialCvars,
|
||||
@@ -133,6 +158,12 @@ public sealed partial class ReplayLoadManager
|
||||
var spawnedTracker = 0;
|
||||
var stateTracker = 0;
|
||||
var curState = state0;
|
||||
|
||||
var stats_due_ticks = 0;
|
||||
var stats_due_spawned = 0;
|
||||
var stats_due_state = 0;
|
||||
|
||||
var modifiedEntities = new Dictionary<NetEntity, UpdateScratchData>();
|
||||
for (var i = 1; i < states.Count; i++)
|
||||
{
|
||||
if (i % 10 == 0)
|
||||
@@ -143,23 +174,42 @@ public sealed partial class ReplayLoadManager
|
||||
DebugTools.Assert(curState.FromSequence <= lastState.ToSequence);
|
||||
|
||||
UpdatePlayerStates(curState.PlayerStates.Span, playerStates);
|
||||
UpdateEntityStates(curState.EntityStates.Span, entStates, ref spawnedTracker, ref stateTracker, detached);
|
||||
UpdateEntityStates(curState.EntityStates.Span, entStates, modifiedEntities, ref spawnedTracker, ref stateTracker, detached);
|
||||
UpdateMessages(messages[i], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase);
|
||||
ProcessQueue(curState.ToSequence, detachQueue, detached, entStates);
|
||||
UpdateDeletions(curState.EntityDeletions, entStates, detached);
|
||||
UpdateDeletions(curState.EntityDeletions, entStates, detached, modifiedEntities);
|
||||
serverTime[i] = GetTime(curState.ToSequence) - initialTime;
|
||||
ticksSinceLastCheckpoint++;
|
||||
|
||||
// Don't create checkpoints too frequently no matter the circumstance
|
||||
if (ticksSinceLastCheckpoint < _checkpointMinInterval)
|
||||
continue;
|
||||
|
||||
// Check if enough time, spawned entities or changed states have occurred.
|
||||
if (ticksSinceLastCheckpoint < _checkpointInterval
|
||||
&& spawnedTracker < _checkpointEntitySpawnThreshold
|
||||
&& stateTracker < _checkpointEntityStateThreshold)
|
||||
{
|
||||
continue;
|
||||
|
||||
// Track and update statistics about why checkpoints are getting created:
|
||||
if (ticksSinceLastCheckpoint >= _checkpointInterval)
|
||||
{
|
||||
stats_due_ticks += 1;
|
||||
}
|
||||
else if (spawnedTracker >= _checkpointEntitySpawnThreshold)
|
||||
{
|
||||
stats_due_spawned += 1;
|
||||
}
|
||||
else if (stateTracker >= _checkpointEntityStateThreshold)
|
||||
{
|
||||
stats_due_state += 1;
|
||||
}
|
||||
|
||||
ticksSinceLastCheckpoint = 0;
|
||||
spawnedTracker = 0;
|
||||
stateTracker = 0;
|
||||
ApplyModifiedEntities(entStates, modifiedEntities);
|
||||
|
||||
var newState = new GameState(GameTick.Zero,
|
||||
curState.ToSequence,
|
||||
default,
|
||||
@@ -169,7 +219,8 @@ public sealed partial class ReplayLoadManager
|
||||
checkPoints.Add(new CheckpointState(newState, timeBase, cvars, i, detached));
|
||||
}
|
||||
|
||||
_sawmill.Info($"Finished generating checkpoints. Elapsed time: {st.Elapsed}");
|
||||
_sawmill.Info($"Finished generating {checkPoints.Count} checkpoints. Elapsed time: {st.Elapsed}. Checkpoint every {(float)states.Count / checkPoints.Count} ticks on average");
|
||||
_sawmill.Info($"Checkpoint stats - Spawning: {stats_due_spawned} StateChanges: {stats_due_state} Ticks: {stats_due_ticks}. ");
|
||||
await callback(states.Count, states.Count, LoadingState.ProcessingFiles, false);
|
||||
return (checkPoints.ToArray(), serverTime);
|
||||
}
|
||||
@@ -316,16 +367,18 @@ public sealed partial class ReplayLoadManager
|
||||
}
|
||||
|
||||
private void UpdateDeletions(NetListAsArray<NetEntity> entityDeletions,
|
||||
Dictionary<NetEntity, EntityState> entStates, HashSet<NetEntity> detached)
|
||||
Dictionary<NetEntity, EntityState> entStates, HashSet<NetEntity> detached, Dictionary<NetEntity, UpdateScratchData> modifiedEntities)
|
||||
{
|
||||
foreach (var ent in entityDeletions.Span)
|
||||
{
|
||||
entStates.Remove(ent);
|
||||
detached.Remove(ent);
|
||||
modifiedEntities.Remove(ent);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateEntityStates(ReadOnlySpan<EntityState> span, Dictionary<NetEntity, EntityState> entStates,
|
||||
Dictionary<NetEntity, UpdateScratchData> modified,
|
||||
ref int spawnedTracker, ref int stateTracker, HashSet<NetEntity> detached)
|
||||
{
|
||||
foreach (var entState in span)
|
||||
@@ -340,25 +393,85 @@ public sealed partial class ReplayLoadManager
|
||||
#if DEBUG
|
||||
foreach (var state in modifiedState.ComponentChanges.Value)
|
||||
{
|
||||
DebugTools.Assert(state.State is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(state.State is not IComponentDeltaState delta);
|
||||
}
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get scratch versions (with write access) for entities modified since last checkpoint
|
||||
UpdateScratchData? scratch;
|
||||
if (!modified.TryGetValue(entState.NetEntity, out scratch))
|
||||
{
|
||||
scratch = new UpdateScratchData(oldEntState);
|
||||
modified[entState.NetEntity] = scratch;
|
||||
}
|
||||
|
||||
stateTracker++;
|
||||
DebugTools.Assert(oldEntState.NetEntity == entState.NetEntity);
|
||||
entStates[entState.NetEntity] = MergeStates(entState, oldEntState.ComponentChanges.Value, oldEntState.NetComponents);
|
||||
// Note this does not change entStates, that change occurs later in ApplyModifiedEntities (to avoid early copies)
|
||||
UpdateScratch(entState, scratch.Changes);
|
||||
if (entState.NetComponents != null)
|
||||
scratch.netComps = entState.NetComponents;
|
||||
scratch.lastChange = entState;
|
||||
|
||||
|
||||
#if DEBUG
|
||||
foreach (var state in entStates[entState.NetEntity].ComponentChanges.Span)
|
||||
{
|
||||
DebugTools.Assert(state.State is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(state.State is not IComponentDeltaState delta);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyModifiedEntities(Dictionary<NetEntity, EntityState> entStates, Dictionary<NetEntity, UpdateScratchData> modifiedEntities)
|
||||
{
|
||||
foreach (var modified in modifiedEntities)
|
||||
{
|
||||
entStates[modified.Key] = modified.Value.BakeChanges();
|
||||
}
|
||||
|
||||
modifiedEntities.Clear();
|
||||
}
|
||||
|
||||
private void UpdateScratch(
|
||||
EntityState newState,
|
||||
Dictionary<ushort, ComponentChange> oldState)
|
||||
{
|
||||
// remove any deleted components
|
||||
if (newState.NetComponents != null)
|
||||
{
|
||||
foreach (var change in oldState.Values)
|
||||
{
|
||||
if (!newState.NetComponents.Contains(change.NetID))
|
||||
oldState.Remove(change.NetID);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var newCompState in newState.ComponentChanges.Value)
|
||||
{
|
||||
if (!oldState.TryGetValue(newCompState.NetID, out var existing))
|
||||
{
|
||||
// This is a new component
|
||||
// I'm not 100% sure about this, but I think delta states should always be full states here?
|
||||
DebugTools.Assert(newCompState.State is not IComponentDeltaState newDelta);
|
||||
oldState[newCompState.NetID] = newCompState;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Modify or replace existing component
|
||||
if (newCompState.State is not IComponentDeltaState delta)
|
||||
{
|
||||
oldState[newCompState.NetID] = newCompState;
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(existing.State != null && existing.State is not IComponentDeltaState);
|
||||
oldState[newCompState.NetID] = new ComponentChange(existing.NetID, delta.CreateNewFullState(existing.State!), newCompState.LastModifiedTick);
|
||||
}
|
||||
}
|
||||
|
||||
private EntityState MergeStates(
|
||||
EntityState newState,
|
||||
IReadOnlyCollection<ComponentChange> oldState,
|
||||
@@ -384,20 +497,20 @@ public sealed partial class ReplayLoadManager
|
||||
if (!newCompStates.Remove(existing.NetID, out var newCompState))
|
||||
continue;
|
||||
|
||||
if (newCompState.State is not IComponentDeltaState delta || delta.FullState)
|
||||
if (newCompState.State is not IComponentDeltaState delta)
|
||||
{
|
||||
combined[index] = newCompState;
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(existing.State is IComponentDeltaState fullDelta && fullDelta.FullState);
|
||||
combined[index] = new ComponentChange(existing.NetID, delta.CreateNewFullState(existing.State), newCompState.LastModifiedTick);
|
||||
DebugTools.Assert(existing.State != null && existing.State is not IComponentDeltaState);
|
||||
combined[index] = new ComponentChange(existing.NetID, delta.CreateNewFullState(existing.State!), newCompState.LastModifiedTick);
|
||||
}
|
||||
|
||||
foreach (var compChange in newCompStates.Values)
|
||||
{
|
||||
// I'm not 100% sure about this, but I think delta states should always be full states here?
|
||||
DebugTools.Assert(compChange.State is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(compChange.State is not IComponentDeltaState);
|
||||
combined.Add(compChange);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ public sealed partial class ReplayLoadManager
|
||||
continue;
|
||||
|
||||
var state = _entMan.GetComponentState(_entMan.EventBus, component, null, GameTick.Zero);
|
||||
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(state is not IComponentDeltaState);
|
||||
list.Add(new ComponentChange(netId, state, GameTick.Zero));
|
||||
set.Add(netId);
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public sealed partial class ReplayLoadManager
|
||||
{
|
||||
if (comp.NetID == _metaId)
|
||||
{
|
||||
var state = (MetaDataComponentState) comp.State;
|
||||
var state = (MetaDataComponentState) comp.State!;
|
||||
return state.PrototypeId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,9 +162,7 @@ public sealed partial class ReplayLoadManager
|
||||
}
|
||||
|
||||
using var stringFile = fileReader.Open(FileStrings);
|
||||
var stringData = new byte[stringFile.Length];
|
||||
stringFile.ReadExactly(stringData);
|
||||
_serializer.SetStringSerializerPackage(stringHash, stringData);
|
||||
_serializer.SetStringSerializerPackage(stringHash, stringFile.CopyToArray());
|
||||
|
||||
using var cvarsFile = fileReader.Open(FileCvars);
|
||||
// Note, this does not invoke the received-initial-cvars event. But at least currently, that doesn't matter
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user