mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
265 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74fe177985 | ||
|
|
bb90d79a3f | ||
|
|
92b0e7f1a8 | ||
|
|
47d1c372b2 | ||
|
|
453f763128 | ||
|
|
65a7942d63 | ||
|
|
f1f3c60d1f | ||
|
|
d4e8a27c23 | ||
|
|
18a17da8fa | ||
|
|
90a8c66e96 | ||
|
|
45c14b2bc3 | ||
|
|
d227613997 | ||
|
|
7557cc703c | ||
|
|
7b81d0d881 | ||
|
|
b59f7801ac | ||
|
|
d724c5b3eb | ||
|
|
f812dc4dac | ||
|
|
2a1bcb6f1e | ||
|
|
fa9030e59c | ||
|
|
8dcae8631b | ||
|
|
21c3535486 | ||
|
|
4e100d96bc | ||
|
|
14d3699ae2 | ||
|
|
350fa8736d | ||
|
|
5a82df216d | ||
|
|
32bca7cfd4 | ||
|
|
008babebc6 | ||
|
|
c65c4ba57e | ||
|
|
eb5b838e61 | ||
|
|
6b43036c9d | ||
|
|
f23a55793d | ||
|
|
46143d2589 | ||
|
|
ba7d1452c1 | ||
|
|
1c1343466e | ||
|
|
0d534e8bcd | ||
|
|
c83c6f9592 | ||
|
|
c794bd84bf | ||
|
|
d1d43f834b | ||
|
|
9505cb68df | ||
|
|
9763f5fdf4 | ||
|
|
80a963ec05 | ||
|
|
3a670ec25e | ||
|
|
e45950a557 | ||
|
|
6f1427ef3c | ||
|
|
9a7d1a39c1 | ||
|
|
9be903ee56 | ||
|
|
72f9f9c343 | ||
|
|
3ad760a99e | ||
|
|
e2f3722ce9 | ||
|
|
b4beca6562 | ||
|
|
ea02260230 | ||
|
|
3b243e487d | ||
|
|
f40ccb7558 | ||
|
|
f467a7027b | ||
|
|
c9d7d442d9 | ||
|
|
342626ad9b | ||
|
|
1c3ea968e4 | ||
|
|
f0ed3537ee | ||
|
|
74e7e61a98 | ||
|
|
fb9b0ae89b | ||
|
|
dbe297b1fc | ||
|
|
b84917e8e4 | ||
|
|
abb3f65fe4 | ||
|
|
41ec2dc131 | ||
|
|
e714dcc83c | ||
|
|
46291af1be | ||
|
|
ad929c9955 | ||
|
|
c86cb0b795 | ||
|
|
8d03feb84f | ||
|
|
0fa21ee2d2 | ||
|
|
9be0f032e8 | ||
|
|
afffb33446 | ||
|
|
19a87fb67a | ||
|
|
2fda62a274 | ||
|
|
5218bf70b0 | ||
|
|
4f95c07ab3 | ||
|
|
786acae47a | ||
|
|
f81e30a031 | ||
|
|
f5c1d870f9 | ||
|
|
4949b34c88 | ||
|
|
0f60ad9018 | ||
|
|
f7287b181d | ||
|
|
45b7500d93 | ||
|
|
dbe6f65880 | ||
|
|
4faef1bfd3 | ||
|
|
48d70a09c6 | ||
|
|
f682fb9cc7 | ||
|
|
814e5bcf17 | ||
|
|
dbc4e80e61 | ||
|
|
5eb5ddd96e | ||
|
|
405ed378c0 | ||
|
|
be9db264dd | ||
|
|
2f73f6190d | ||
|
|
f3dfa1f666 | ||
|
|
b0d17e9527 | ||
|
|
4c81e68bf1 | ||
|
|
4490751001 | ||
|
|
bc8d2c154c | ||
|
|
3c83f8e62a | ||
|
|
c36919d76a | ||
|
|
70a853cdd5 | ||
|
|
fd3eb092cc | ||
|
|
c740026014 | ||
|
|
44f9262d1a | ||
|
|
df2160b151 | ||
|
|
5c7b1e6823 | ||
|
|
eaf7a6ba0f | ||
|
|
9ab4286592 | ||
|
|
3f02ef3730 | ||
|
|
2f17cbb1dc | ||
|
|
c2657812f5 | ||
|
|
f17f077849 | ||
|
|
306deddbd2 | ||
|
|
cdd8df743a | ||
|
|
e7ac5ad047 | ||
|
|
0e621a26be | ||
|
|
bbcc7cfe1f | ||
|
|
1208c25dcd | ||
|
|
38c227b692 | ||
|
|
4e73d72753 | ||
|
|
b1e1a0cd88 | ||
|
|
6e25ead588 | ||
|
|
cfae6e1f95 | ||
|
|
da56851846 | ||
|
|
69ed2c3c33 | ||
|
|
c558a0327b | ||
|
|
3bb7df3254 | ||
|
|
ab6bd19817 | ||
|
|
73ef69aa94 | ||
|
|
656835e7fa | ||
|
|
26c87b5858 | ||
|
|
be36001ab8 | ||
|
|
2002402af8 | ||
|
|
f659b2b58c | ||
|
|
b1e13f5b13 | ||
|
|
e5995d4edc | ||
|
|
6eb080a277 | ||
|
|
b0cb41e94a | ||
|
|
23a23f7c22 | ||
|
|
ec3a74d268 | ||
|
|
12b0bc4e0a | ||
|
|
903041dfd1 | ||
|
|
b96419f0b2 | ||
|
|
fe33ad2652 | ||
|
|
057a68b366 | ||
|
|
1a2c9008fe | ||
|
|
cd95929ebe | ||
|
|
6396ec472d | ||
|
|
d7aa5daf6a | ||
|
|
e3819f8245 | ||
|
|
57f133b742 | ||
|
|
04344ffe19 | ||
|
|
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 |
@@ -19,6 +19,7 @@
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit" Version="1.1.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.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" />
|
||||
@@ -57,7 +58,7 @@
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
<PackageVersion Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
Submodule NetSerializer updated: 7f51deaeca...4882400f2c
465
RELEASE-NOTES.md
465
RELEASE-NOTES.md
@@ -54,10 +54,471 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 226.2.2
|
||||
## 237.2.1
|
||||
|
||||
|
||||
## 226.2.1
|
||||
## 237.2.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `SharedEyeSystem..SetTarget()` will now also automatically remove the old target from the session's ViewSubscriptions
|
||||
|
||||
### New features
|
||||
|
||||
* `ImmutableArray<T>` can now be serialized by `RobustSerializer`.
|
||||
* `RequiresLocationAttribute`, used by `ref readonly`, is now allowed by the sandbox.
|
||||
* Added `DAT-OBJ()` localization function, for the dative case in certain languages.
|
||||
* Client builds for FreeBSD are now made.
|
||||
* Added `FormattedMessage.TrimEnd()`.
|
||||
* Added Toolshed `with` for `ProtoId<T>`.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `UniqueIndex<,>.RemoveRange()` and`UniqueIndexHkm<,>.RemoveRange()` clearing the whole set instead of just removing the specified values.
|
||||
* Avoid server crashes on some weird console setups (notably Pterodactyl).
|
||||
* Avoid unhandled exceptions during server shutdown getting swallowed due logging into a disposed logger.
|
||||
* Fix sandbox definitions for `Regex` functions returning `MatchCollection`.
|
||||
* Fix minor layout bugs with `SplitContainer` and `BoxContainer`.
|
||||
|
||||
### Other
|
||||
|
||||
* Changed how multi-window rendering presents to the screen with a new CVar `display.thread_unlock_before_swap`. This is an experiment to see if it solves some synchronization issues.
|
||||
* View Variables no longer clears the window on refresh while waiting on response from server.
|
||||
* `SpinBox` buttons now have a `+` prefix for the positive ones.
|
||||
* Improve Toolshed type intersection mechanism
|
||||
|
||||
### Internal
|
||||
|
||||
* Warning cleanup.
|
||||
|
||||
## 237.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* csi's auto import-system can now handle generic types.
|
||||
* csi's reflection helpers (like `fld()`) handle private members up the inheritance chain.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `UniqueIndexHkm<,>` and, by extension, entity data storage memory leaking.
|
||||
* Fix bugs related to UIScale on `OSWindow`s.
|
||||
|
||||
|
||||
## 237.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `IClydeWindow.Size` is now settable, allowing window sizes to be changed after creation.
|
||||
|
||||
### New features
|
||||
|
||||
* The game server's `/update` endpoint now supports passing more information on why an update is available.
|
||||
* This information is accessible via `IWatchdogApi.RestartRequested`.
|
||||
* Information can be specified by passing a JSON object with a `Reason` code and `Message` field.
|
||||
* Added an "Erase" button to the tile spawn menu.
|
||||
* Added `OSWindow.Create()`, which allows OS windows to be created & initialised without immediately opening/showing them.
|
||||
|
||||
### Other
|
||||
|
||||
* Made `WatchdogApi` and some members of `IWatchdogApi` private. These symbols should never have been accessed by content.
|
||||
|
||||
|
||||
## 236.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* `RequiredMemberAttribute` and `SetsRequiredMembersAttribute` have been added to the sandbox whitelist. I.e., you can now use the `required` keyword in client/shared code.
|
||||
* Added `SwitchExpressionException` to sandbox. This type gets used if you have a `switch` expression with no default case.
|
||||
* Added `LineEdit.SelectAllOnFocus`.
|
||||
* `GameTitle`, `WindowIconSet` and `SplashLogo` are exposed in `IGameController`. These will return said information set in game options or whatever is set in `manifest.yml`.
|
||||
* `BoundUserInterface` inheritors now have access to `PlayerManager`.
|
||||
* Added `MuteSounds` bool to `BaseButton`.
|
||||
* The engine has a new future-proof HWID system.
|
||||
* The auth server now manages HWIDs. This avoids HWID impersonation attacks.
|
||||
* The auth server can return multiple HWIDs. They are accessible in `NetUserData.ModernHWIds`.
|
||||
* The auth server also returns a trust score factor, accessible as `NetUserData.Trust`.
|
||||
* HWID can be disabled client side (`ROBUST_AUTH_ALLOW_HWID` env var) or server side (`net.hwid` cvar).
|
||||
* The old HWID system is still in place. It is intended that content switches to placing new bans against the new HWIDs.
|
||||
* Old HWIDs no longer work if the connection is not authenticated.
|
||||
* `launchauth` command now recognizes `SS14_LAUNCHER_APPDATA_NAME`.
|
||||
* Added new overload to `EntityLookupSystem.GetEntitiesIntersecting`.
|
||||
* Added `Control.RemoveChild(int childIndex)`.
|
||||
* `build.entities_category_filter` allows filtering the entity spawn panel to a specific category.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `SpriteView` offset calculations when scaled.
|
||||
|
||||
### Other
|
||||
|
||||
* Sprite flicks are applied immediately when started.
|
||||
* More warning fixes.
|
||||
* If the server gets shut down before finishing startup, the reason is now logged properly.
|
||||
|
||||
|
||||
## 236.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Revert IsTouching only being set to true if the contact were laready touching in clientside physics prediction.
|
||||
* Don't touch IsTouching if both bodies are asleep for clientside physics contacts. This change and the one above should fix a lot of clientside contact issues, particularly around repeated incorrect clientside contact events.
|
||||
|
||||
### New features
|
||||
|
||||
* Added an analyzer to detect duplicate Dependency fields.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Auto-networked dictionaries now use `TryAdd()` to avoid duplicate key errors when a dictionary contains multiple unknown networked entities.
|
||||
* Fixed `ICommonSession.Ping` always returning zero instead of the ping. Note that this will still return zero for client-side code when trying to get the ping of other players.
|
||||
* Hot reload XAML files on rename to fix them potentially not being reloaded with Visual Studio.
|
||||
* Fix TabContainer click detection for non-1.0 UI scales.
|
||||
|
||||
### Other
|
||||
|
||||
* Obsolete some static localization methods.
|
||||
* Tried to improve PVS tolerance to exceptions occurring.
|
||||
|
||||
|
||||
## 235.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Several different `AudioSystem` methods were incorrectly given a `[return: NotNullIfNotNull]` attribute. Content code that uses these methods needs to be updated to perform null checks.
|
||||
* noSpawn is no longer obsolete and is now removed in lieu of the EntityCategory HideSpawnMenu.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* physics.maxlinvelocity is now a replicated cvar.
|
||||
* Fix DistanceJoint debug drawing in physics not using the local anchors.
|
||||
* Fixed filtered AudioSystem methods playing a sound for all players when given an empty filter.
|
||||
* Fixed equality checks for `MarkupNode` not properly handling attributes.
|
||||
* Fixed `MarkupNode` not having a `GetHashCode()` implementation.
|
||||
* Fixed a PVS error that could occur when trying to delete the first entity that gets created in a round.
|
||||
* Fixed the "to" and "take" toolshed commands not working as intended.
|
||||
* Rich text controls within an `OutputPanel` control will now become invisible when they are out of view.
|
||||
|
||||
### Other
|
||||
|
||||
* Improve precision for Quaternion2D constructor from angles.
|
||||
|
||||
|
||||
## 234.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* SharedAudioSystem now has PlayLocal which only runs audio locally on the client.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix AudioParams not being passed through on PlayGlobal methods.
|
||||
|
||||
|
||||
## 234.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Remove a lot of obsoleted code that has been obsoleted for a while.
|
||||
|
||||
### New features
|
||||
|
||||
* Add another GetLocalEntitiesIntersecting override.
|
||||
|
||||
### Other
|
||||
|
||||
* Mark large replays as requiring Server GC.
|
||||
* Obsolete some IResourceCache proxies.
|
||||
|
||||
|
||||
## 233.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add GetGridEntities and another GetEntitiesIntersecting overload to EntityLookupSystem.
|
||||
* `MarkupNode` is now `IEquatable<MarkupNode>`. It already supported equality checks, now it implements the interface.
|
||||
* Added `Entity<T>` overloads to the following `SharedMapSystem` methods: `GetTileRef`, `GetAnchoredEntities`, `TileIndicesFor`.
|
||||
* Added `EntityUid`-only overloads to the following `SharedTransformSystem` methods: `AnchorEntity`, `Unanchor`.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed equality checks for `MarkupNode` not properly handling attributes.
|
||||
* Fixed toolshed commands failing to generate error messages when working with array types
|
||||
* Fixed `MarkupNode` not having a `GetHashCode()` implementation.
|
||||
|
||||
### Other
|
||||
|
||||
* If `EntityManager.FlushEntities()` fails to delete all entities, it will now attempt to do so a second time before throwing an exception.
|
||||
|
||||
|
||||
## 233.0.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix exceptions in client game state handling for grids. Now they will rely upon the networked fixture data and not try to rebuild in the grid state handler.
|
||||
|
||||
|
||||
## 233.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix IsHardCollidable component to EntityUid references.
|
||||
|
||||
|
||||
## 233.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Made EntityRenamed a broadcast event & added additional args.
|
||||
* Made test runs parallelizable.
|
||||
* Added a debug assert that other threads aren't touching entities.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix some entitylookup method transformations and add more tests.
|
||||
* Fix mousehover not updating if new controls showed up under the mouse.
|
||||
|
||||
### Internal
|
||||
|
||||
* `ClientGameStateManager` now only initialises or starts entities after their parents have already been initialized. There are also some new debug asserts to try ensure that this rule isn't broken elsewhere.
|
||||
* Engine version script now supports dashes.
|
||||
|
||||
|
||||
## 232.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Obsolete method `AppearanceComponent.TryGetData` is now access-restricted to `SharedAppearanceSystem`; use `SharedAppearanceSystem.TryGetData` instead.
|
||||
|
||||
### New features
|
||||
|
||||
* Added `SharedAppearanceSystem.AppendData`, which appends non-existing `AppearanceData` from one `AppearanceComponent` to another.
|
||||
* Added `AppearanceComponent.AppearanceDataInit`, which can be used to set initial `AppearanceData` entries in .yaml.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix BUI interfaces not deep-copying in state handling.
|
||||
* Add Robust.Xaml.csproj to the solution to fix some XAML issues.
|
||||
|
||||
### Other
|
||||
|
||||
* Serialization will now add type tags (`!type:<T>`) for necessary `NodeData` when writing (currently only for `object` nodes).
|
||||
|
||||
### Internal
|
||||
|
||||
* Added `ObjectSerializer`, which handles serialization of the generic `object` type.
|
||||
|
||||
|
||||
## 231.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed a bug where the client might not add entities to the broadphase/lookup components.
|
||||
* Fixed various toolshed commands not working, including `sort`, `sortdown` `join` (for strings), and `emplace`
|
||||
|
||||
### Other
|
||||
|
||||
* Toolshed command blocks now stop executing if previous errors were not handled / cleared.
|
||||
|
||||
|
||||
## 231.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Network `InterfaceData` on `UserInterfaceComponent`.
|
||||
* Added `System.Decimal` to sandbox.
|
||||
* Added XAML hot reloading.
|
||||
* Added API for content to write custom files into replay through `IReplayFileWriter`.
|
||||
|
||||
### Other
|
||||
|
||||
* Optimized `EntityLookup` and other physics systems.
|
||||
|
||||
### Internal
|
||||
|
||||
* Added more tests related to physics.
|
||||
|
||||
|
||||
## 231.0.1
|
||||
|
||||
### Other
|
||||
|
||||
* Add better logging to failed PVS sends.
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
@@ -18,3 +18,9 @@
|
||||
description: entity-category-desc-hide
|
||||
hideSpawnMenu: true
|
||||
inheritable: false
|
||||
|
||||
# Entity prototypes added by the fork. With CVar you can hide all entities without this category
|
||||
- type: entityCategory
|
||||
id: ForkFiltered
|
||||
name: entity-category-name-fork
|
||||
description: entity-category-desc-fork
|
||||
@@ -20,6 +20,15 @@ zzzz-object-pronoun = { GENDER($ent) ->
|
||||
*[neuter] it
|
||||
}
|
||||
|
||||
# Used internally by the DAT-OBJ() function.
|
||||
# Not used in en-US. Created for supporting other languages.
|
||||
zzzz-dat-object = { GENDER($ent) ->
|
||||
[male] him
|
||||
[female] her
|
||||
[epicene] them
|
||||
*[neuter] it
|
||||
}
|
||||
|
||||
# Used internally by the POSS-PRONOUN() function.
|
||||
zzzz-possessive-pronoun = { GENDER($ent) ->
|
||||
[male] his
|
||||
|
||||
@@ -4,9 +4,16 @@ entity-spawn-window-title = Entity Spawn Panel
|
||||
entity-spawn-window-search-bar-placeholder = search
|
||||
entity-spawn-window-clear-button = Clear
|
||||
entity-spawn-window-replace-button-text = Replace
|
||||
entity-spawn-window-erase-button-text = Erase Mode
|
||||
entity-spawn-window-override-menu-tooltip = Override placement
|
||||
|
||||
## TileSpawnWindow
|
||||
|
||||
tile-spawn-window-title = Place Tiles
|
||||
|
||||
## Console
|
||||
|
||||
console-line-edit-placeholder = Command Here
|
||||
|
||||
## Common Used
|
||||
|
||||
window-erase-button-text = Erase Mode
|
||||
|
||||
@@ -7,3 +7,6 @@ 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 entity spawn menus
|
||||
|
||||
entity-category-name-fork = Fork Filtered
|
||||
entity-category-desc-fork = Entity prototypes added by the fork. With CVar you can hide all entities without this category
|
||||
@@ -219,9 +219,9 @@ command-description-MulVecCommand =
|
||||
command-description-DivVecCommand =
|
||||
Divides every element in the input by a scalar (single value).
|
||||
command-description-rng-to =
|
||||
Returns a number from its input to its argument (i.e. n..m inclusive)
|
||||
Returns a number between the input (inclusive) and the argument (exclusive).
|
||||
command-description-rng-from =
|
||||
Returns a number to its input from its argument (i.e. m..n inclusive)
|
||||
Returns a number between the argument (inclusive) and the input (exclusive))
|
||||
command-description-rng-prob =
|
||||
Returns a boolean based on the input probability/chance (from 0 to 1)
|
||||
command-description-sum =
|
||||
|
||||
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")
|
||||
);
|
||||
}
|
||||
}
|
||||
63
Robust.Analyzers.Tests/DuplicateDependencyAnalyzerTest.cs
Normal file
63
Robust.Analyzers.Tests/DuplicateDependencyAnalyzerTest.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
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.DuplicateDependencyAnalyzer>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
[TestOf(typeof(DuplicateDependencyAnalyzer))]
|
||||
public sealed class DuplicateDependencyAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<DuplicateDependencyAnalyzer, NUnitVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.IoC.DependencyAttribute.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.IoC;
|
||||
|
||||
public sealed class Foo
|
||||
{
|
||||
[Dependency]
|
||||
private object? Field;
|
||||
|
||||
[Dependency]
|
||||
private object? Field2;
|
||||
|
||||
[Dependency]
|
||||
private string? DifferentField;
|
||||
|
||||
private string? NonDependency1;
|
||||
private string? NonDependency2;
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(9,21): warning RA0032: Another [Dependency] field of type 'object?' already exists in this type as field 'Field'
|
||||
VerifyCS.Diagnostic().WithSpan(9, 21, 9, 27).WithArguments("object?", "Field"));
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
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"/>
|
||||
|
||||
@@ -7,6 +7,7 @@ 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;
|
||||
|
||||
@@ -16,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,
|
||||
@@ -66,9 +70,20 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
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,
|
||||
DataFieldRedundantTagRule
|
||||
DataFieldRedundantTagRule, DataFieldNoVVReadWriteRule
|
||||
);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
@@ -139,7 +154,14 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
if (HasRedundantTag(fieldSymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,7 +190,14 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
if (HasRedundantTag(propertySymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,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)
|
||||
@@ -292,6 +339,34 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
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))
|
||||
|
||||
@@ -15,9 +15,11 @@ 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,
|
||||
IdDataFieldRedundantTag
|
||||
IdDataFieldRedundantTag, IdDataFieldNoVVReadWrite
|
||||
);
|
||||
|
||||
public override Task RegisterCodeFixesAsync(CodeFixContext context)
|
||||
@@ -36,6 +38,8 @@ public sealed class DefinitionFixer : CodeFixProvider
|
||||
return RegisterDataFieldPropertyFix(context, diagnostic);
|
||||
case IdDataFieldRedundantTag:
|
||||
return RegisterRedundantTagFix(context, diagnostic);
|
||||
case IdDataFieldNoVVReadWrite:
|
||||
return RegisterVVReadWriteFix(context, diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +140,48 @@ public sealed class DefinitionFixer : CodeFixProvider
|
||||
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);
|
||||
|
||||
126
Robust.Analyzers/DuplicateDependencyAnalyzer.cs
Normal file
126
Robust.Analyzers/DuplicateDependencyAnalyzer.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
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>
|
||||
/// Analyzer that detects duplicate <c>[Dependency]</c> fields inside a single type.
|
||||
/// </summary>
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class DuplicateDependencyAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string DependencyAttributeType = "Robust.Shared.IoC.DependencyAttribute";
|
||||
|
||||
private static readonly DiagnosticDescriptor Rule = new(
|
||||
Diagnostics.IdDuplicateDependency,
|
||||
"Duplicate dependency field",
|
||||
"Another [Dependency] field of type '{0}' already exists in this type with field '{1}'",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterCompilationStartAction(compilationContext =>
|
||||
{
|
||||
var dependencyAttributeType = compilationContext.Compilation.GetTypeByMetadataName(DependencyAttributeType);
|
||||
if (dependencyAttributeType == null)
|
||||
return;
|
||||
|
||||
compilationContext.RegisterSymbolStartAction(symbolContext =>
|
||||
{
|
||||
var typeSymbol = (INamedTypeSymbol)symbolContext.Symbol;
|
||||
// Only deal with non-static classes, doesn't make sense to have dependencies in anything else.
|
||||
if (typeSymbol.TypeKind != TypeKind.Class || typeSymbol.IsStatic)
|
||||
return;
|
||||
|
||||
var state = new AnalyzerState(dependencyAttributeType);
|
||||
symbolContext.RegisterSyntaxNodeAction(state.AnalyzeField, SyntaxKind.FieldDeclaration);
|
||||
symbolContext.RegisterSymbolEndAction(state.End);
|
||||
},
|
||||
SymbolKind.NamedType);
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class AnalyzerState(INamedTypeSymbol dependencyAttributeType)
|
||||
{
|
||||
private readonly Dictionary<ITypeSymbol, List<IFieldSymbol>> _dependencyFields = new(SymbolEqualityComparer.Default);
|
||||
|
||||
public void AnalyzeField(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
var field = (FieldDeclarationSyntax)context.Node;
|
||||
if (field.AttributeLists.Count == 0)
|
||||
return;
|
||||
|
||||
if (context.ContainingSymbol is not IFieldSymbol fieldSymbol)
|
||||
return;
|
||||
|
||||
// Can't have [Dependency]s for non-reference types.
|
||||
if (!fieldSymbol.Type.IsReferenceType)
|
||||
return;
|
||||
|
||||
if (!IsDependency(context.ContainingSymbol))
|
||||
return;
|
||||
|
||||
lock (_dependencyFields)
|
||||
{
|
||||
if (!_dependencyFields.TryGetValue(fieldSymbol.Type, out var dependencyFields))
|
||||
{
|
||||
dependencyFields = [];
|
||||
_dependencyFields.Add(fieldSymbol.Type, dependencyFields);
|
||||
}
|
||||
|
||||
dependencyFields.Add(fieldSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsDependency(ISymbol symbol)
|
||||
{
|
||||
foreach (var attributeData in symbol.GetAttributes())
|
||||
{
|
||||
if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, dependencyAttributeType))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void End(SymbolAnalysisContext context)
|
||||
{
|
||||
lock (_dependencyFields)
|
||||
{
|
||||
foreach (var pair in _dependencyFields)
|
||||
{
|
||||
var fieldType = pair.Key;
|
||||
var fields = pair.Value;
|
||||
if (fields.Count <= 1)
|
||||
continue;
|
||||
|
||||
// Sort so we can have deterministic order to skip reporting for a single field.
|
||||
// Whichever sorts first doesn't get reported.
|
||||
fields.Sort(static (a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
||||
|
||||
// Start at index 1 to skip first field.
|
||||
var firstField = fields[0];
|
||||
for (var i = 1; i < fields.Count; i++)
|
||||
{
|
||||
var field = fields[i];
|
||||
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(Rule, field.Locations[0], fieldType.ToDisplayString(), firstField.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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,9 +16,21 @@
|
||||
<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" />
|
||||
|
||||
96
Robust.Benchmarks/Physics/BoxStackBenchmark.cs
Normal file
96
Robust.Benchmarks/Physics/BoxStackBenchmark.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.Physics;
|
||||
|
||||
[Virtual]
|
||||
[MediumRunJob]
|
||||
public class PhysicsBoxStackBenchmark
|
||||
{
|
||||
private ISimulation _sim = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
SetupTumbler(entManager, mapId);
|
||||
|
||||
for (var i = 0; i < 30; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BoxStack()
|
||||
{
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTumbler(IEntityManager entManager, MapId mapId)
|
||||
{
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
|
||||
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
|
||||
|
||||
var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10));
|
||||
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
|
||||
|
||||
var xs = new[]
|
||||
{
|
||||
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
|
||||
};
|
||||
|
||||
var columnCount = 1;
|
||||
var rowCount = 15;
|
||||
PolygonShape shape;
|
||||
|
||||
for (var j = 0; j < columnCount; j++)
|
||||
{
|
||||
for (var i = 0; i < rowCount; i++)
|
||||
{
|
||||
var x = 0.0f;
|
||||
|
||||
var boxUid = entManager.SpawnEntity(null,
|
||||
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 1.1f * i), mapId));
|
||||
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
|
||||
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
|
||||
shape = new PolygonShape();
|
||||
shape.SetAsBox(0.5f, 0.5f);
|
||||
physics.SetFixedRotation(boxUid, false, body: box);
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true), body: box);
|
||||
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
physics.SetSleepingAllowed(boxUid, box, false);
|
||||
}
|
||||
}
|
||||
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
}
|
||||
}
|
||||
92
Robust.Benchmarks/Physics/CircleStackBenchmark.cs
Normal file
92
Robust.Benchmarks/Physics/CircleStackBenchmark.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.Physics;
|
||||
|
||||
[Virtual]
|
||||
public class PhysicsCircleStackBenchmark
|
||||
{
|
||||
private ISimulation _sim = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
SetupTumbler(entManager, mapId);
|
||||
|
||||
for (var i = 0; i < 30; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CircleStack()
|
||||
{
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTumbler(IEntityManager entManager, MapId mapId)
|
||||
{
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
|
||||
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
|
||||
|
||||
var vertical = new EdgeShape(new Vector2(20, 0), new Vector2(20, 20));
|
||||
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
|
||||
|
||||
var xs = new[]
|
||||
{
|
||||
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
|
||||
};
|
||||
|
||||
var columnCount = 1;
|
||||
var rowCount = 15;
|
||||
PhysShapeCircle shape;
|
||||
|
||||
for (var j = 0; j < columnCount; j++)
|
||||
{
|
||||
for (var i = 0; i < rowCount; i++)
|
||||
{
|
||||
var x = 0.0f;
|
||||
|
||||
var boxUid = entManager.SpawnEntity(null,
|
||||
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 2.1f * i), mapId));
|
||||
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
|
||||
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
shape = new PhysShapeCircle(0.5f);
|
||||
physics.SetFixedRotation(boxUid, false, body: box);
|
||||
// TODO: Need to detect shape and work out if we need to use fixedrotation
|
||||
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f));
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
physics.SetSleepingAllowed(boxUid, box, false);
|
||||
}
|
||||
}
|
||||
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
}
|
||||
}
|
||||
91
Robust.Benchmarks/Physics/PyramidBenchmark.cs
Normal file
91
Robust.Benchmarks/Physics/PyramidBenchmark.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.Physics;
|
||||
|
||||
[Virtual]
|
||||
public class PhysicsPyramidBenchmark
|
||||
{
|
||||
private ISimulation _sim = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
SetupTumbler(entManager, mapId);
|
||||
|
||||
for (var i = 0; i < 300; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Pyramid()
|
||||
{
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
|
||||
for (var i = 0; i < 5000; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTumbler(IEntityManager entManager, MapId mapId)
|
||||
{
|
||||
const byte count = 20;
|
||||
|
||||
// Setup ground
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(40, 0), new Vector2(-40, 0));
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
|
||||
// Setup boxes
|
||||
float a = 0.5f;
|
||||
PolygonShape shape = new();
|
||||
shape.SetAsBox(a, a);
|
||||
|
||||
var x = new Vector2(-7.0f, 0.75f);
|
||||
Vector2 y;
|
||||
Vector2 deltaX = new Vector2(0.5625f, 1.25f);
|
||||
Vector2 deltaY = new Vector2(1.125f, 0.0f);
|
||||
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
y = x;
|
||||
|
||||
for (var j = i; j < count; ++j)
|
||||
{
|
||||
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(y, mapId));
|
||||
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f), body: box);
|
||||
y += deltaY;
|
||||
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
physics.SetSleepingAllowed(boxUid, box, false);
|
||||
}
|
||||
|
||||
x += deltaX;
|
||||
}
|
||||
}
|
||||
}
|
||||
105
Robust.Benchmarks/Physics/TumblerBenchmark.cs
Normal file
105
Robust.Benchmarks/Physics/TumblerBenchmark.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.Physics;
|
||||
|
||||
[Virtual]
|
||||
public class PhysicsTumblerBenchmark
|
||||
{
|
||||
private ISimulation _sim = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
SetupTumbler(entManager, mapId);
|
||||
|
||||
for (var i = 0; i < 800; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
|
||||
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
physics.SetFixedRotation(boxUid, false, body: box);
|
||||
var shape = new PolygonShape();
|
||||
shape.SetAsBox(0.125f, 0.125f);
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 0.0625f), body: box);
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
physics.SetSleepingAllowed(boxUid, box, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Tumbler()
|
||||
{
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
|
||||
for (var i = 0; i < 5000; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTumbler(IEntityManager entManager, MapId mapId)
|
||||
{
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
var joints = entManager.System<SharedJointSystem>();
|
||||
|
||||
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 0f, mapId));
|
||||
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
// Due to lookup changes fixtureless bodies are invalid, so
|
||||
var cShape = new PhysShapeCircle(1f);
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(cShape, 0, 0, false));
|
||||
|
||||
var bodyUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
|
||||
var body = entManager.AddComponent<PhysicsComponent>(bodyUid);
|
||||
|
||||
physics.SetBodyType(bodyUid, BodyType.Dynamic, body: body);
|
||||
physics.SetSleepingAllowed(bodyUid, body, false);
|
||||
physics.SetFixedRotation(bodyUid, false, body: body);
|
||||
|
||||
|
||||
// TODO: Box2D just deref, bleh shape structs someday
|
||||
var shape1 = new PolygonShape();
|
||||
shape1.SetAsBox(0.5f, 10.0f, new Vector2(10.0f, 0.0f), 0.0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix1", new Fixture(shape1, 2, 0, true, 20f));
|
||||
|
||||
var shape2 = new PolygonShape();
|
||||
shape2.SetAsBox(0.5f, 10.0f, new Vector2(-10.0f, 0.0f), 0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix2", new Fixture(shape2, 2, 0, true, 20f));
|
||||
|
||||
var shape3 = new PolygonShape();
|
||||
shape3.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, 10.0f), 0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix3", new Fixture(shape3, 2, 0, true, 20f));
|
||||
|
||||
var shape4 = new PolygonShape();
|
||||
shape4.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, -10.0f), 0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix4", new Fixture(shape4, 2, 0, true, 20f));
|
||||
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
physics.WakeBody(bodyUid, body: body);
|
||||
var revolute = joints.CreateRevoluteJoint(groundUid, bodyUid);
|
||||
revolute.LocalAnchorA = new Vector2(0f, 10f);
|
||||
revolute.LocalAnchorB = new Vector2(0f, 0f);
|
||||
revolute.ReferenceAngle = 0f;
|
||||
revolute.MotorSpeed = 0.05f * MathF.PI;
|
||||
revolute.MaxMotorTorque = 100000000f;
|
||||
revolute.EnableMotor = true;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Build.Framework;
|
||||
using Robust.Xaml;
|
||||
|
||||
namespace Robust.Build.Tasks
|
||||
{
|
||||
@@ -37,10 +37,12 @@ namespace Robust.Build.Tasks
|
||||
var msg = $"CompileRobustXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{OutputPath}";
|
||||
BuildEngine.LogMessage(msg, MessageImportance.High);
|
||||
|
||||
var res = XamlCompiler.Compile(BuildEngine, input,
|
||||
var res = XamlAotCompiler.Compile(
|
||||
BuildEngine, input,
|
||||
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
|
||||
ProjectDirectory, OutputPath,
|
||||
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null);
|
||||
OutputPath,
|
||||
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null
|
||||
);
|
||||
if (!res.success)
|
||||
return false;
|
||||
if (!res.writtentofile)
|
||||
@@ -65,22 +67,24 @@ namespace Robust.Build.Tasks
|
||||
return true;
|
||||
}
|
||||
|
||||
// PYREX NOTE: This project was comically null-unsafe before I touched it. I'm just marking what it did accurately
|
||||
[Required]
|
||||
public string ReferencesFilePath { get; set; }
|
||||
public string ReferencesFilePath { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
public string ProjectDirectory { get; set; }
|
||||
|
||||
public string ProjectDirectory { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
public string AssemblyFile { get; set; }
|
||||
public string AssemblyFile { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
public string OriginalCopyPath { get; set; }
|
||||
public string? OriginalCopyPath { get; set; } = null;
|
||||
|
||||
public string OutputPath { get; set; }
|
||||
public string UpdateBuildIndicator { get; set; }
|
||||
public string? OutputPath { get; set; }
|
||||
public string UpdateBuildIndicator { get; set; } = null!;
|
||||
|
||||
public string AssemblyOriginatorKeyFile { get; set; }
|
||||
public string AssemblyOriginatorKeyFile { get; set; } = null!;
|
||||
public bool SignAssembly { get; set; }
|
||||
public bool DelaySign { get; set; }
|
||||
|
||||
@@ -95,7 +99,7 @@ namespace Robust.Build.Tasks
|
||||
return rv;
|
||||
}
|
||||
|
||||
public IBuildEngine BuildEngine { get; set; }
|
||||
public ITaskHost HostObject { get; set; }
|
||||
public IBuildEngine BuildEngine { get; set; } = null!;
|
||||
public ITaskHost HostObject { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
using System.Linq;
|
||||
using Pidgin;
|
||||
using static Pidgin.Parser;
|
||||
|
||||
|
||||
namespace Robust.Build.Tasks
|
||||
{
|
||||
public static class MathParsing
|
||||
{
|
||||
public static Parser<char, float> Single { get; } = Real.Select(c => (float) c);
|
||||
|
||||
public static Parser<char, float> Single1 { get; }
|
||||
= Single.Between(SkipWhitespaces);
|
||||
|
||||
public static Parser<char, (float, float)> Single2 { get; }
|
||||
= Single.Before(SkipWhitespaces).Repeat(2).Select(e =>
|
||||
{
|
||||
var arr = e.ToArray();
|
||||
return (arr[0], arr[1]);
|
||||
});
|
||||
|
||||
public static Parser<char, (float, float, float, float)> Single4 { get; }
|
||||
= Single.Before(SkipWhitespaces).Repeat(4).Select(e =>
|
||||
{
|
||||
var arr = e.ToArray();
|
||||
return (arr[0], arr[1], arr[2], arr[3]);
|
||||
});
|
||||
|
||||
public static Parser<char, float[]> Thickness { get; }
|
||||
= SkipWhitespaces.Then(
|
||||
OneOf(
|
||||
Try(Single4.Select(c => new[] {c.Item1, c.Item2, c.Item3, c.Item4})),
|
||||
Try(Single2.Select(c => new[] {c.Item1, c.Item2})),
|
||||
Try(Single1.Select(c => new[] {c}))
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -55,9 +55,11 @@ namespace Robust.Build.Tasks
|
||||
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties,
|
||||
IDictionary targetOutputs) => throw new NotSupportedException();
|
||||
|
||||
public bool ContinueOnError { get; }
|
||||
public int LineNumberOfTaskNode { get; }
|
||||
public int ColumnNumberOfTaskNode { get; }
|
||||
public string ProjectFileOfTaskNode { get; }
|
||||
// PYREX NOTE: This project was extremely null-unsafe before I touched it. I'm just marking what it did already
|
||||
// Here's the broken interface of IBuildEngine that we started with
|
||||
public bool ContinueOnError => default;
|
||||
public int LineNumberOfTaskNode => default;
|
||||
public int ColumnNumberOfTaskNode => default;
|
||||
public string ProjectFileOfTaskNode => null!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\MSBuild\Robust.Engine.props" />
|
||||
|
||||
<!--
|
||||
PJB3005 (2024-08-24)
|
||||
So the reason that Robust.Client.Injectors is NS2.0 is that Visual Studio
|
||||
still ships a .NET FX based MSBuild for some godforsaken reason. This means
|
||||
that when having Robust.Client.Injectors loaded directly by the main MSBuild
|
||||
process... that would break.
|
||||
|
||||
Except we don't do that anyways right now due to file locking issues, so maybe
|
||||
it's fine to give up on that. Whatever.
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build.Framework" Version="17.8.3" />
|
||||
<PackageReference Include="Mono.Cecil" Version="0.11.5" />
|
||||
<PackageReference Include="Pidgin" Version="2.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\XamlX\src\XamlX.IL.Cecil\XamlX.IL.Cecil.csproj" />
|
||||
<ProjectReference Include="..\Robust.Xaml\Robust.Xaml.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,389 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Build.Framework;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
using Pidgin;
|
||||
using XamlX;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Emit;
|
||||
using XamlX.IL;
|
||||
using XamlX.Parsers;
|
||||
using XamlX.Transform;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Robust.Build.Tasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Based on https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
|
||||
/// Adjusted for our UI-Framework
|
||||
/// </summary>
|
||||
public partial class XamlCompiler
|
||||
{
|
||||
public static (bool success, bool writtentofile) Compile(IBuildEngine engine, string input, string[] references,
|
||||
string projectDirectory, string output, string strongNameKey)
|
||||
{
|
||||
var typeSystem = new CecilTypeSystem(references
|
||||
.Where(r => !r.ToLowerInvariant().EndsWith("robust.build.tasks.dll"))
|
||||
.Concat(new[] { input }), input);
|
||||
|
||||
var asm = typeSystem.TargetAssemblyDefinition;
|
||||
|
||||
if (asm.MainModule.GetType("CompiledRobustXaml", "XamlIlContext") != null)
|
||||
{
|
||||
// If this type exists, the assembly has already been processed by us.
|
||||
// Do not run again, it would corrupt the file.
|
||||
// This *shouldn't* be possible due to Inputs/Outputs dependencies in the build system,
|
||||
// but better safe than sorry eh?
|
||||
engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", "", 0, 0, 0, 0, "Ran twice on same assembly file; ignoring.", "", ""));
|
||||
return (true, false);
|
||||
}
|
||||
|
||||
var compileRes = CompileCore(engine, typeSystem);
|
||||
if (compileRes == null)
|
||||
return (true, false);
|
||||
if (compileRes == false)
|
||||
return (false, false);
|
||||
|
||||
var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols };
|
||||
if (!string.IsNullOrWhiteSpace(strongNameKey))
|
||||
writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
|
||||
|
||||
asm.Write(output, writerParameters);
|
||||
|
||||
return (true, true);
|
||||
|
||||
}
|
||||
|
||||
static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem)
|
||||
{
|
||||
var asm = typeSystem.TargetAssemblyDefinition;
|
||||
var embrsc = new EmbeddedResources(asm);
|
||||
|
||||
if (embrsc.Resources.Count(CheckXamlName) == 0)
|
||||
// Nothing to do
|
||||
return null;
|
||||
|
||||
var xamlLanguage = new XamlLanguageTypeMappings(typeSystem)
|
||||
{
|
||||
XmlnsAttributes =
|
||||
{
|
||||
typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute"),
|
||||
|
||||
},
|
||||
ContentAttributes =
|
||||
{
|
||||
typeSystem.GetType("Avalonia.Metadata.ContentAttribute")
|
||||
},
|
||||
UsableDuringInitializationAttributes =
|
||||
{
|
||||
typeSystem.GetType("Robust.Client.UserInterface.XAML.UsableDuringInitializationAttribute")
|
||||
},
|
||||
DeferredContentPropertyAttributes =
|
||||
{
|
||||
typeSystem.GetType("Robust.Client.UserInterface.XAML.DeferredContentAttribute")
|
||||
},
|
||||
RootObjectProvider = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestRootObjectProvider"),
|
||||
UriContextProvider = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestUriContext"),
|
||||
ProvideValueTarget = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestProvideValueTarget"),
|
||||
};
|
||||
var emitConfig = new XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult>
|
||||
{
|
||||
ContextTypeBuilderCallback = (b,c) => EmitNameScopeField(xamlLanguage, typeSystem, b, c)
|
||||
};
|
||||
|
||||
var transformerconfig = new TransformerConfiguration(
|
||||
typeSystem,
|
||||
typeSystem.TargetAssembly,
|
||||
xamlLanguage,
|
||||
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage), CustomValueConverter);
|
||||
|
||||
var contextDef = new TypeDefinition("CompiledRobustXaml", "XamlIlContext",
|
||||
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
|
||||
asm.MainModule.Types.Add(contextDef);
|
||||
var contextClass = XamlILContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
|
||||
xamlLanguage, emitConfig);
|
||||
|
||||
var compiler =
|
||||
new RobustXamlILCompiler(transformerconfig, emitConfig, true);
|
||||
|
||||
bool CompileGroup(IResourceGroup group)
|
||||
{
|
||||
var typeDef = new TypeDefinition("CompiledRobustXaml", "!" + group.Name, TypeAttributes.Class,
|
||||
asm.MainModule.TypeSystem.Object);
|
||||
|
||||
//typeDef.CustomAttributes.Add(new CustomAttribute(ed));
|
||||
asm.MainModule.Types.Add(typeDef);
|
||||
var builder = typeSystem.CreateTypeBuilder(typeDef);
|
||||
|
||||
foreach (var res in group.Resources.Where(CheckXamlName))
|
||||
{
|
||||
try
|
||||
{
|
||||
engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", MessageImportance.Low);
|
||||
|
||||
var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
|
||||
var parsed = XDocumentXamlParser.Parse(xaml);
|
||||
|
||||
var initialRoot = (XamlAstObjectNode) parsed.Root;
|
||||
|
||||
var classDirective = initialRoot.Children.OfType<XamlAstXmlDirective>()
|
||||
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class");
|
||||
string classname;
|
||||
if (classDirective != null && classDirective.Values[0] is XamlAstTextNode tn)
|
||||
{
|
||||
classname = tn.Text;
|
||||
}
|
||||
else
|
||||
{
|
||||
classname = res.Name.Replace(".xaml","");
|
||||
}
|
||||
|
||||
var classType = typeSystem.TargetAssembly.FindType(classname);
|
||||
if (classType == null)
|
||||
throw new Exception($"Unable to find type '{classname}'");
|
||||
|
||||
compiler.Transform(parsed);
|
||||
|
||||
var populateName = $"Populate:{res.Name}";
|
||||
var buildName = $"Build:{res.Name}";
|
||||
|
||||
var classTypeDefinition = typeSystem.GetTypeReference(classType).Resolve();
|
||||
|
||||
var populateBuilder = typeSystem.CreateTypeBuilder(classTypeDefinition);
|
||||
|
||||
compiler.Compile(parsed, contextClass,
|
||||
compiler.DefinePopulateMethod(populateBuilder, parsed, populateName,
|
||||
classTypeDefinition == null),
|
||||
compiler.DefineBuildMethod(builder, parsed, buildName, true),
|
||||
null,
|
||||
(closureName, closureBaseType) =>
|
||||
populateBuilder.DefineSubType(closureBaseType, closureName, false),
|
||||
res.Uri, res
|
||||
);
|
||||
|
||||
//add compiled populate method
|
||||
var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve().Methods
|
||||
.First(m => m.Name == populateName);
|
||||
|
||||
const string TrampolineName = "!XamlIlPopulateTrampoline";
|
||||
var trampoline = new MethodDefinition(TrampolineName,
|
||||
MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void);
|
||||
trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition));
|
||||
classTypeDefinition.Methods.Add(trampoline);
|
||||
|
||||
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull));
|
||||
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
|
||||
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod));
|
||||
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
|
||||
|
||||
var foundXamlLoader = false;
|
||||
// Find RobustXamlLoader.Load(this) and replace it with !XamlIlPopulateTrampoline(this)
|
||||
foreach (var method in classTypeDefinition.Methods
|
||||
.Where(m => !m.Attributes.HasFlag(MethodAttributes.Static)))
|
||||
{
|
||||
var i = method.Body.Instructions;
|
||||
for (var c = 1; c < i.Count; c++)
|
||||
{
|
||||
if (i[c].OpCode == OpCodes.Call)
|
||||
{
|
||||
var op = i[c].Operand as MethodReference;
|
||||
|
||||
if (op != null
|
||||
&& op.Name == TrampolineName)
|
||||
{
|
||||
foundXamlLoader = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (op != null
|
||||
&& op.Name == "Load"
|
||||
&& op.Parameters.Count == 1
|
||||
&& op.Parameters[0].ParameterType.FullName == "System.Object"
|
||||
&& op.DeclaringType.FullName == "Robust.Client.UserInterface.XAML.RobustXamlLoader")
|
||||
{
|
||||
if (MatchThisCall(i, c - 1))
|
||||
{
|
||||
i[c].Operand = trampoline;
|
||||
foundXamlLoader = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundXamlLoader)
|
||||
{
|
||||
var ctors = classTypeDefinition.GetConstructors()
|
||||
.Where(c => !c.IsStatic).ToList();
|
||||
// We can inject xaml loader into default constructor
|
||||
if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o=>o.OpCode != OpCodes.Nop) == 3)
|
||||
{
|
||||
var i = ctors[0].Body.Instructions;
|
||||
var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret));
|
||||
i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampoline));
|
||||
i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidProgramException(
|
||||
$"No call to RobustXamlLoader.Load(this) call found anywhere in the type {classType.FullName} and type seems to have custom constructors.");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
|
||||
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
|
||||
}
|
||||
res.Remove();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (embrsc.Resources.Count(CheckXamlName) != 0)
|
||||
{
|
||||
if (!CompileGroup(embrsc))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool CustomValueConverter(
|
||||
AstTransformationContext context,
|
||||
IXamlAstValueNode node,
|
||||
IXamlType type,
|
||||
out IXamlAstValueNode result)
|
||||
{
|
||||
if (!(node is XamlAstTextNode textNode))
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var text = textNode.Text;
|
||||
var types = context.GetRobustTypes();
|
||||
|
||||
if (type.Equals(types.Vector2))
|
||||
{
|
||||
var foo = MathParsing.Single2.Parse(text);
|
||||
|
||||
if (!foo.Success)
|
||||
throw new XamlLoadException($"Unable to parse \"{text}\" as a Vector2", node);
|
||||
|
||||
var (x, y) = foo.Value;
|
||||
|
||||
result = new RXamlSingleVecLikeConstAstNode(
|
||||
node,
|
||||
types.Vector2, types.Vector2ConstructorFull,
|
||||
types.Single, new[] {x, y});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.Equals(types.Thickness))
|
||||
{
|
||||
var foo = MathParsing.Thickness.Parse(text);
|
||||
|
||||
if (!foo.Success)
|
||||
throw new XamlLoadException($"Unable to parse \"{text}\" as a Thickness", node);
|
||||
|
||||
var val = foo.Value;
|
||||
float[] full;
|
||||
if (val.Length == 1)
|
||||
{
|
||||
var u = val[0];
|
||||
full = new[] {u, u, u, u};
|
||||
}
|
||||
else if (val.Length == 2)
|
||||
{
|
||||
var h = val[0];
|
||||
var v = val[1];
|
||||
full = new[] {h, v, h, v};
|
||||
}
|
||||
else // 4
|
||||
{
|
||||
full = val;
|
||||
}
|
||||
|
||||
result = new RXamlSingleVecLikeConstAstNode(
|
||||
node,
|
||||
types.Thickness, types.ThicknessConstructorFull,
|
||||
types.Single, full);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.Equals(types.Thickness))
|
||||
{
|
||||
var foo = MathParsing.Thickness.Parse(text);
|
||||
|
||||
if (!foo.Success)
|
||||
throw new XamlLoadException($"Unable to parse \"{text}\" as a Thickness", node);
|
||||
|
||||
var val = foo.Value;
|
||||
float[] full;
|
||||
if (val.Length == 1)
|
||||
{
|
||||
var u = val[0];
|
||||
full = new[] {u, u, u, u};
|
||||
}
|
||||
else if (val.Length == 2)
|
||||
{
|
||||
var h = val[0];
|
||||
var v = val[1];
|
||||
full = new[] {h, v, h, v};
|
||||
}
|
||||
else // 4
|
||||
{
|
||||
full = val;
|
||||
}
|
||||
|
||||
result = new RXamlSingleVecLikeConstAstNode(
|
||||
node,
|
||||
types.Thickness, types.ThicknessConstructorFull,
|
||||
types.Single, full);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.Equals(types.Color))
|
||||
{
|
||||
// TODO: Interpret these colors at XAML compile time instead of at runtime.
|
||||
result = new RXamlColorAstNode(node, types, text);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public const string ContextNameScopeFieldName = "RobustNameScope";
|
||||
|
||||
private static void EmitNameScopeField(XamlLanguageTypeMappings xamlLanguage, CecilTypeSystem typeSystem, IXamlTypeBuilder<IXamlILEmitter> typeBuilder, IXamlILEmitter constructor)
|
||||
{
|
||||
var nameScopeType = typeSystem.FindType("Robust.Client.UserInterface.XAML.NameScope");
|
||||
var field = typeBuilder.DefineField(nameScopeType,
|
||||
ContextNameScopeFieldName, true, false);
|
||||
constructor
|
||||
.Ldarg_0()
|
||||
.Newobj(nameScopeType.GetConstructor())
|
||||
.Stfld(field);
|
||||
}
|
||||
}
|
||||
|
||||
interface IResource : IFileSource
|
||||
{
|
||||
string Uri { get; }
|
||||
string Name { get; }
|
||||
void Remove();
|
||||
|
||||
}
|
||||
|
||||
interface IResourceGroup
|
||||
{
|
||||
string Name { get; }
|
||||
IEnumerable<IResource> Resources { get; }
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public static class Program
|
||||
internal static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -24,6 +25,7 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameControllerInternal _gameController = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -61,7 +63,10 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
var cachePath = "";
|
||||
if (_resourceManager.UserData is WritableDirProvider userData)
|
||||
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
|
||||
{
|
||||
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
cachePath = Path.Combine(rootDir, "cef_cache", "0");
|
||||
}
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
|
||||
@@ -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,7 +37,6 @@ 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!;
|
||||
@@ -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);
|
||||
@@ -254,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;
|
||||
@@ -277,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;
|
||||
@@ -332,7 +343,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
Vector2 worldPos;
|
||||
component.Volume = component.Params.Volume;
|
||||
Vector2 delta;
|
||||
|
||||
// Handle grid audio differently by using grid position.
|
||||
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
|
||||
@@ -346,7 +356,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
// Max distance check
|
||||
delta = worldPos - listener.Position;
|
||||
var delta = worldPos - listener.Position;
|
||||
var distance = delta.Length();
|
||||
|
||||
// Out of range so just clip it for us.
|
||||
@@ -443,6 +453,17 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio....
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayLocal(
|
||||
SoundSpecifier? sound,
|
||||
EntityUid source,
|
||||
EntityUid? soundInitiator,
|
||||
AudioParams? audioParams = null
|
||||
)
|
||||
{
|
||||
return PlayPredicted(sound, source, soundInitiator, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
if (Timing.IsFirstTimePredicted && sound != null)
|
||||
@@ -648,7 +669,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
// TODO clamp the offset inside of SetPlaybackPosition() itself.
|
||||
var offset = audioP.PlayOffsetSeconds;
|
||||
offset = Math.Clamp(offset, 0f, (float) stream.Length.TotalSeconds - 0.01f);
|
||||
var maxOffset = Math.Max((float) stream.Length.TotalSeconds - 0.01f, 0f);
|
||||
offset = Math.Clamp(offset, 0f, maxOffset);
|
||||
source.PlaybackPosition = offset;
|
||||
|
||||
// For server we will rely on the adjusted one but locally we will have to adjust it ourselves.
|
||||
|
||||
@@ -33,12 +33,6 @@ public interface IMidiRenderer : IDisposable
|
||||
/// </summary>
|
||||
bool LoopMidi { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This increases all note on velocities to 127.
|
||||
/// </summary>
|
||||
[Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")]
|
||||
bool VolumeBoost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The midi program (instrument) the renderer is using.
|
||||
/// </summary>
|
||||
|
||||
@@ -205,14 +205,6 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")]
|
||||
public bool VolumeBoost
|
||||
{
|
||||
get => VelocityOverride == 127;
|
||||
set => VelocityOverride = value ? 127 : null;
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityUid? TrackingEntity { get; set; } = null;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Clyde;
|
||||
using Robust.Client.HWId;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.Placement;
|
||||
@@ -26,6 +27,7 @@ using Robust.Client.Upload;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.RichText;
|
||||
using Robust.Client.UserInterface.Themes;
|
||||
using Robust.Client.UserInterface.XAML.Proxy;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Client.ViewVariables;
|
||||
using Robust.Shared;
|
||||
@@ -146,7 +148,18 @@ namespace Robust.Client
|
||||
deps.Register<IConfigurationManagerInternal, ClientNetConfigurationManager>();
|
||||
deps.Register<IClientNetConfigurationManager, ClientNetConfigurationManager>();
|
||||
deps.Register<INetConfigurationManagerInternal, ClientNetConfigurationManager>();
|
||||
|
||||
#if TOOLS
|
||||
deps.Register<IXamlProxyManager, XamlProxyManager>();
|
||||
deps.Register<IXamlHotReloadManager, XamlHotReloadManager>();
|
||||
#else
|
||||
deps.Register<IXamlProxyManager, XamlProxyManagerStub>();
|
||||
deps.Register<IXamlHotReloadManager, XamlHotReloadManagerStub>();
|
||||
#endif
|
||||
|
||||
deps.Register<IXamlProxyHelper, XamlProxyHelper>();
|
||||
deps.Register<MarkupTagManager>();
|
||||
deps.Register<IHWId, BasicHWId>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,9 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
var wantName = args.Length > 0 ? args[0] : null;
|
||||
|
||||
var basePath = Path.GetDirectoryName(UserDataDir.GetUserDataDir(_gameController))!;
|
||||
var dbPath = Path.Combine(basePath, "launcher", "settings.db");
|
||||
var basePath = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
var launcherDirName = Environment.GetEnvironmentVariable("SS14_LAUNCHER_APPDATA_NAME") ?? "launcher";
|
||||
var dbPath = Path.Combine(basePath, launcherDirName, "settings.db");
|
||||
|
||||
#if USE_SYSTEM_SQLITE
|
||||
SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -14,15 +14,6 @@ namespace Robust.Client.Credits
|
||||
/// </summary>
|
||||
public static class CreditsManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of open source software used in the engine and their license.
|
||||
/// </summary>
|
||||
[Obsolete("Use overload that takes in an explicit resource manager instead.")]
|
||||
public static IEnumerable<LicenseEntry> GetLicenses()
|
||||
{
|
||||
return GetLicenses(IoCManager.Resolve<IResourceManager>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of open source software used in the engine and their license.
|
||||
/// </summary>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -517,8 +529,8 @@ 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.M31, matrix1.M32);
|
||||
var xf2 = new Vector2(matrix2.M31, matrix2.M32);
|
||||
@@ -526,13 +538,13 @@ namespace Robust.Client.Debugging
|
||||
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)
|
||||
{
|
||||
case DistanceJoint:
|
||||
worldHandle.DrawLine(xf1, xf2, JointColor);
|
||||
worldHandle.DrawLine(p1, p2, JointColor);
|
||||
break;
|
||||
case PrismaticJoint prisma:
|
||||
var pA = Transform.Mul(xfa, joint.LocalAnchorA);
|
||||
|
||||
@@ -19,6 +19,7 @@ using Robust.Client.State;
|
||||
using Robust.Client.Upload;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.RichText;
|
||||
using Robust.Client.UserInterface.XAML.Proxy;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Client.ViewVariables;
|
||||
using Robust.Client.WebViewHook;
|
||||
@@ -53,6 +54,8 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IResourceCacheInternal _resourceCache = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resManager = default!;
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
[Dependency] private readonly IXamlProxyManager _xamlProxyManager = default!;
|
||||
[Dependency] private readonly IXamlHotReloadManager _xamlHotReloadManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IClientNetManager _networkManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
@@ -109,14 +112,28 @@ namespace Robust.Client
|
||||
_commandLineArgs = args;
|
||||
}
|
||||
|
||||
public string GameTitle()
|
||||
{
|
||||
return Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox";
|
||||
}
|
||||
|
||||
public string WindowIconSet()
|
||||
{
|
||||
return Options.WindowIconSet?.ToString() ?? _resourceManifest!.WindowIconSet ?? "";
|
||||
}
|
||||
|
||||
public string SplashLogo()
|
||||
{
|
||||
return Options.SplashLogo?.ToString() ?? _resourceManifest!.SplashLogo ?? "";
|
||||
}
|
||||
|
||||
internal bool StartupContinue(DisplayMode displayMode)
|
||||
{
|
||||
DebugTools.AssertNotNull(_resourceManifest);
|
||||
|
||||
_clyde.InitializePostWindowing();
|
||||
_audio.InitializePostWindowing();
|
||||
_clyde.SetWindowTitle(
|
||||
Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox");
|
||||
_clyde.SetWindowTitle(GameTitle());
|
||||
|
||||
_taskManager.Initialize();
|
||||
_parallelMgr.Initialize();
|
||||
@@ -171,6 +188,8 @@ namespace Robust.Client
|
||||
_reflectionManager.Initialize();
|
||||
_prototypeManager.Initialize();
|
||||
_prototypeManager.LoadDefaultPrototypes();
|
||||
_xamlProxyManager.Initialize();
|
||||
_xamlHotReloadManager.Initialize();
|
||||
_userInterfaceManager.Initialize();
|
||||
_eyeManager.Initialize();
|
||||
_entityManager.Initialize();
|
||||
@@ -363,7 +382,7 @@ namespace Robust.Client
|
||||
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
@@ -394,10 +413,8 @@ namespace Robust.Client
|
||||
// Handle GameControllerOptions implicit CVar overrides.
|
||||
_configurationManager.OverrideConVars(new[]
|
||||
{
|
||||
(CVars.DisplayWindowIconSet.Name,
|
||||
options.WindowIconSet?.ToString() ?? _resourceManifest.WindowIconSet ?? ""),
|
||||
(CVars.DisplaySplashLogo.Name,
|
||||
options.SplashLogo?.ToString() ?? _resourceManifest.SplashLogo ?? "")
|
||||
(CVars.DisplayWindowIconSet.Name, WindowIconSet()),
|
||||
(CVars.DisplaySplashLogo.Name, SplashLogo())
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -76,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;
|
||||
@@ -98,15 +99,6 @@ namespace Robust.Client.GameObjects
|
||||
Play(new Entity<AnimationPlayerComponent>(uid, component), animation, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start playing an animation.
|
||||
/// </summary>
|
||||
[Obsolete("Use Play(EntityUid<AnimationPlayerComponent> ent, Animation animation, string key) instead")]
|
||||
public void Play(AnimationPlayerComponent component, Animation animation, string key)
|
||||
{
|
||||
Play(new Entity<AnimationPlayerComponent>(component.Owner, component), animation, key);
|
||||
}
|
||||
|
||||
public void Play(Entity<AnimationPlayerComponent> ent, Animation animation, string key)
|
||||
{
|
||||
AddComponent(ent);
|
||||
@@ -151,6 +143,14 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
#endif
|
||||
|
||||
foreach (var track in animation.AnimationTracks)
|
||||
{
|
||||
if (track is not AnimationTrackSpriteFlick)
|
||||
continue;
|
||||
|
||||
track.AdvancePlayback(ent.Owner, 0, 0, 0f);
|
||||
}
|
||||
|
||||
ent.Comp.PlayingAnimations.Add(key, playback);
|
||||
}
|
||||
|
||||
@@ -187,7 +187,8 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, new AnimationCompletedEvent {Uid = entity.Owner, Key = key}, true);
|
||||
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)
|
||||
@@ -203,5 +204,11 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
public sealed class ContainerSystem : SharedContainerSystem
|
||||
{
|
||||
[Dependency] private readonly INetManager _netMan = default!;
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
|
||||
[Dependency] private readonly PointLightSystem _lightSys = default!;
|
||||
|
||||
@@ -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,16 +60,20 @@ 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)
|
||||
@@ -74,20 +82,23 @@ namespace Robust.Client.GameObjects
|
||||
var viewport = args.WorldBounds;
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
var fixturesQuery = _entityManager.GetEntityQuery<FixturesComponent>();
|
||||
_grids.Clear();
|
||||
_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 fixtures = fixturesQuery.Comp(grid.Owner);
|
||||
|
||||
var chunkEnumerator = grid.Comp.GetMapChunks(viewport);
|
||||
var chunkEnumerator = _mapSystem.GetMapChunks(grid.Owner, grid.Comp, viewport);
|
||||
|
||||
while (chunkEnumerator.MoveNext(out var chunk))
|
||||
{
|
||||
foreach (var fixture in chunk.Fixtures.Values)
|
||||
foreach (var id in chunk.Fixtures)
|
||||
{
|
||||
var fixture = fixtures.Fixtures[id];
|
||||
var poly = (PolygonShape) fixture.Shape;
|
||||
|
||||
var verts = new Vector2[poly.VertexCount];
|
||||
|
||||
@@ -20,7 +20,6 @@ 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!;
|
||||
@@ -196,7 +195,7 @@ namespace Robust.Client.GameObjects
|
||||
wOffset = new Vector2(wX, wY);
|
||||
}
|
||||
|
||||
var coords = EntityCoordinates.FromMap(pent, _transform.GetMapCoordinates(pent).Offset(wOffset), _transform, EntityManager);
|
||||
var coords = _transform.ToCoordinates(pent, _transform.GetMapCoordinates(pent).Offset(wOffset));
|
||||
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
|
||||
|
||||
var message = new ClientFullInputCmdMessage(_timing.CurTick,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,7 +960,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
// Initialize and start the newly created entities.
|
||||
if (_toCreate.Count > 0)
|
||||
InitializeAndStart(_toCreate);
|
||||
InitializeAndStart(_toCreate, metas, xforms);
|
||||
|
||||
_prof.WriteValue("State Size", ProfData.Int32(curSpan.Length));
|
||||
_prof.WriteValue("Entered PVS", ProfData.Int32(enteringPvs));
|
||||
@@ -1188,7 +1188,10 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeAndStart(Dictionary<NetEntity, EntityState> toCreate)
|
||||
private void InitializeAndStart(
|
||||
Dictionary<NetEntity, EntityState> toCreate,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
EntityQuery<TransformComponent> xforms)
|
||||
{
|
||||
_toStart.Clear();
|
||||
|
||||
@@ -1197,22 +1200,8 @@ namespace Robust.Client.GameStates
|
||||
EntityUid entity = default;
|
||||
foreach (var netEntity in toCreate.Keys)
|
||||
{
|
||||
try
|
||||
{
|
||||
(entity, var meta) = _entityManager.GetEntityData(netEntity);
|
||||
_entities.InitializeEntity(entity, meta);
|
||||
_toStart.Add((entity, netEntity));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Server entity threw in Init: nent={netEntity}, ent={_entities.ToPrettyString(entity)}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
|
||||
_toCreate.Remove(netEntity);
|
||||
_brokenEnts.Add(entity);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
(entity, var meta) = _entityManager.GetEntityData(netEntity);
|
||||
InitializeRecursive(entity, meta, metas, xforms);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1244,6 +1233,44 @@ namespace Robust.Client.GameStates
|
||||
_brokenEnts.Clear();
|
||||
}
|
||||
|
||||
private void InitializeRecursive(
|
||||
EntityUid entity,
|
||||
MetaDataComponent meta,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
EntityQuery<TransformComponent> xforms)
|
||||
{
|
||||
var xform = xforms.GetComponent(entity);
|
||||
if (xform.ParentUid is {Valid: true} parent)
|
||||
{
|
||||
var parentMeta = metas.GetComponent(parent);
|
||||
if (parentMeta.EntityLifeStage < EntityLifeStage.Initialized)
|
||||
InitializeRecursive(parent, parentMeta, metas, xforms);
|
||||
}
|
||||
|
||||
if (meta.EntityLifeStage >= EntityLifeStage.Initialized)
|
||||
{
|
||||
// Was probably already initialized because one of its children appeared earlier in the list.
|
||||
DebugTools.AssertEqual(_toStart.Count(x => x.Item1 == entity), 1);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_entities.InitializeEntity(entity, meta);
|
||||
_toStart.Add((entity, meta.NetEntity));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Server entity threw in Init: nent={meta.NetEntity}, ent={_entities.ToPrettyString(entity)}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
|
||||
_toCreate.Remove(meta.NetEntity);
|
||||
_brokenEnts.Add(entity);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
|
||||
EntityState? nextState, GameTick lastApplied, GameTick toTick, bool enteringPvs)
|
||||
{
|
||||
|
||||
@@ -34,9 +34,6 @@ namespace Robust.Client.GameStates
|
||||
/// </summary>
|
||||
int GetApplicableStateCount();
|
||||
|
||||
[Obsolete("use GetApplicableStateCount()")]
|
||||
int CurrentBufferSize => GetApplicableStateCount();
|
||||
|
||||
/// <summary>
|
||||
/// Total number of game states currently in the state buffer.
|
||||
/// </summary>
|
||||
|
||||
@@ -46,7 +46,6 @@ namespace Robust.Client.GameStates
|
||||
|
||||
// sum of all data point sizes in bytes
|
||||
private int _totalHistoryPayload;
|
||||
private int _totalUncompressed;
|
||||
|
||||
public EntityUid WatchEntId { get; set; }
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -109,6 +109,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void SendWindowResized(WindowReg reg, Vector2i oldSize)
|
||||
{
|
||||
if (!reg.IsVisible) // Only send this for open windows
|
||||
return;
|
||||
|
||||
var loaded = RtToLoaded(reg.RenderTarget);
|
||||
loaded.Size = reg.FramebufferSize;
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,7 +958,7 @@ 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 = Vector2.Transform(box.TopLeft, worldTransform);
|
||||
|
||||
@@ -496,6 +496,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
case bool b:
|
||||
program.SetUniform(name, b ? 1 : 0);
|
||||
break;
|
||||
case bool[] bArr:
|
||||
program.SetUniform(name, bArr);
|
||||
break;
|
||||
case Matrix3x2 matrix3:
|
||||
program.SetUniform(name, matrix3);
|
||||
break;
|
||||
|
||||
@@ -506,6 +506,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
data.Parameters[name] = 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];
|
||||
|
||||
@@ -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!;
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
|
||||
@@ -344,6 +343,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (isMain)
|
||||
_mainWindow = reg;
|
||||
|
||||
reg.IsVisible = parameters.Visible;
|
||||
|
||||
_windows.Add(reg);
|
||||
_windowHandles.Add(reg.Handle);
|
||||
|
||||
@@ -445,6 +446,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_windowing!.CursorSet(_mainWindow!, cursor);
|
||||
}
|
||||
|
||||
private void SetWindowSize(WindowReg reg, Vector2i size)
|
||||
{
|
||||
DebugTools.AssertNotNull(_windowing);
|
||||
|
||||
_windowing!.WindowSetSize(reg, size);
|
||||
}
|
||||
|
||||
private void SetWindowVisible(WindowReg reg, bool visible)
|
||||
{
|
||||
@@ -534,7 +541,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_clyde.DoDestroyWindow(Reg);
|
||||
}
|
||||
|
||||
public Vector2i Size => Reg.FramebufferSize;
|
||||
public Vector2i Size
|
||||
{
|
||||
get => Reg.FramebufferSize;
|
||||
set => _clyde.SetWindowSize(Reg, value);
|
||||
}
|
||||
|
||||
public IRenderTarget RenderTarget => Reg.RenderTarget;
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -31,7 +32,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
internal sealed partial class Clyde : IClydeInternal, IPostInjectInit, IEntityEventSubscriber
|
||||
{
|
||||
[Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly ILightManager _lightManager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
@@ -46,6 +46,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 +79,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 +96,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 +125,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_gameThread = Thread.CurrentThread;
|
||||
|
||||
InitSystems();
|
||||
|
||||
InitGLContextManager();
|
||||
if (!InitMainWindowAndRenderer())
|
||||
return false;
|
||||
|
||||
@@ -361,6 +361,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, bool[] value)
|
||||
{
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, in Matrix3x2 value)
|
||||
{
|
||||
}
|
||||
@@ -513,7 +517,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
RenderTarget = renderTarget;
|
||||
}
|
||||
|
||||
public Vector2i Size { get; } = default;
|
||||
public Vector2i Size { get; set; } = default;
|
||||
public bool IsDisposed { get; private set; }
|
||||
public WindowId Id { get; set; }
|
||||
public IRenderTarget RenderTarget { get; }
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
@@ -176,6 +176,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
window.BlitDoneEvent!.Reset();
|
||||
window.BlitStartEvent!.Set();
|
||||
window.BlitDoneEvent.Wait();
|
||||
window.UnlockBeforeSwap = Clyde._cfg.GetCVar(CVars.DisplayThreadUnlockBeforeSwap);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -212,8 +213,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
|
||||
Clyde.CheckGlError();
|
||||
|
||||
window.BlitDoneEvent?.Set();
|
||||
if (window.UnlockBeforeSwap)
|
||||
{
|
||||
window.BlitDoneEvent?.Set();
|
||||
}
|
||||
Clyde._windowing!.WindowSwapBuffers(window.Reg);
|
||||
if (!window.UnlockBeforeSwap)
|
||||
{
|
||||
window.BlitDoneEvent?.Set();
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void BlitThreadInit(WindowData reg)
|
||||
@@ -336,6 +344,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public Thread? BlitThread;
|
||||
public ManualResetEventSlim? BlitStartEvent;
|
||||
public ManualResetEventSlim? BlitDoneEvent;
|
||||
public bool UnlockBeforeSwap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,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);
|
||||
|
||||
@@ -85,6 +85,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
WinThreadWinSetMonitor(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinSetSize cmd:
|
||||
WinThreadWinSetSize(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinSetVisible cmd:
|
||||
WinThreadWinSetVisible(cmd);
|
||||
break;
|
||||
@@ -234,6 +238,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
nint Window
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinSetSize(
|
||||
nint Window,
|
||||
int W, int H
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinSetVisible(
|
||||
nint Window,
|
||||
bool Visible
|
||||
|
||||
@@ -84,6 +84,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
);
|
||||
}
|
||||
|
||||
public void WindowSetSize(WindowReg window, Vector2i size)
|
||||
{
|
||||
var reg = (GlfwWindowReg) window;
|
||||
|
||||
SendCmd(new CmdWinSetSize((nint) reg.GlfwWindow, size.X, size.Y));
|
||||
}
|
||||
|
||||
public void WindowSetVisible(WindowReg window, bool visible)
|
||||
{
|
||||
var reg = (GlfwWindowReg) window;
|
||||
@@ -92,6 +99,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
SendCmd(new CmdWinSetVisible((nint) reg.GlfwWindow, visible));
|
||||
}
|
||||
|
||||
private void WinThreadWinSetSize(CmdWinSetSize cmd)
|
||||
{
|
||||
var win = (Window*) cmd.Window;
|
||||
|
||||
GLFW.SetWindowSize(win, cmd.W, cmd.H);
|
||||
}
|
||||
|
||||
private void WinThreadWinSetVisible(CmdWinSetVisible cmd)
|
||||
{
|
||||
var win = (Window*) cmd.Window;
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
void WindowDestroy(WindowReg reg);
|
||||
void WindowSetTitle(WindowReg window, string title);
|
||||
void WindowSetMonitor(WindowReg window, IClydeMonitor monitor);
|
||||
void WindowSetSize(WindowReg window, Vector2i size);
|
||||
void WindowSetVisible(WindowReg window, bool visible);
|
||||
void WindowRequestAttention(WindowReg window);
|
||||
void WindowSwapBuffers(WindowReg window);
|
||||
|
||||
@@ -93,6 +93,10 @@ internal partial class Clyde
|
||||
WinThreadWinRequestAttention(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinSetSize cmd:
|
||||
WinThreadWinSetSize(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinSetVisible cmd:
|
||||
WinThreadWinSetVisible(cmd);
|
||||
break;
|
||||
@@ -246,6 +250,11 @@ internal partial class Clyde
|
||||
nint Window
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinSetSize(
|
||||
nint Window,
|
||||
int W, int H
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinSetVisible(
|
||||
nint Window,
|
||||
bool Visible
|
||||
|
||||
@@ -336,11 +336,22 @@ internal partial class Clyde
|
||||
_sawmill.Warning("WindowSetMonitor not implemented on SDL2");
|
||||
}
|
||||
|
||||
public void WindowSetSize(WindowReg window, Vector2i size)
|
||||
{
|
||||
SendCmd(new CmdWinSetSize(WinPtr(window), size.X, size.Y));
|
||||
}
|
||||
|
||||
public void WindowSetVisible(WindowReg window, bool visible)
|
||||
{
|
||||
window.IsVisible = visible;
|
||||
SendCmd(new CmdWinSetVisible(WinPtr(window), visible));
|
||||
}
|
||||
|
||||
private static void WinThreadWinSetSize(CmdWinSetSize cmd)
|
||||
{
|
||||
SDL_SetWindowSize(cmd.Window, cmd.W, cmd.H);
|
||||
}
|
||||
|
||||
private static void WinThreadWinSetVisible(CmdWinSetVisible cmd)
|
||||
{
|
||||
if (cmd.Visible)
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Client.Graphics
|
||||
WindowId Id { get; }
|
||||
IRenderTarget RenderTarget { get; }
|
||||
string Title { get; set; }
|
||||
Vector2i Size { get; }
|
||||
Vector2i Size { get; set; }
|
||||
bool IsFocused { get; }
|
||||
bool IsMinimized { get; }
|
||||
bool IsVisible { get; set; }
|
||||
|
||||
@@ -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,6 +148,13 @@ namespace Robust.Client.Graphics
|
||||
SetParameterImpl(name, value);
|
||||
}
|
||||
|
||||
public void SetParameter(string name, bool[] value)
|
||||
{
|
||||
EnsureAlive();
|
||||
EnsureMutable();
|
||||
SetParameterImpl(name, value);
|
||||
}
|
||||
|
||||
public void SetParameter(string name, in Matrix3x2 value)
|
||||
{
|
||||
EnsureAlive();
|
||||
@@ -219,6 +226,7 @@ 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, 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);
|
||||
|
||||
86
Robust.Client/HWId/BasicHWId.cs
Normal file
86
Robust.Client/HWId/BasicHWId.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.Win32;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.Client.HWId;
|
||||
|
||||
internal sealed class BasicHWId : IHWId
|
||||
{
|
||||
[Dependency] private readonly IGameControllerInternal _gameController = default!;
|
||||
|
||||
public const int LengthHwid = 32;
|
||||
|
||||
public byte[] GetLegacy()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
return GetWindowsHWid("Hwid");
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public byte[] GetModern()
|
||||
{
|
||||
byte[] raw;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
raw = GetWindowsHWid("Hwid2");
|
||||
else
|
||||
raw = GetFileHWid();
|
||||
|
||||
return [0, ..raw];
|
||||
}
|
||||
|
||||
private static byte[] GetWindowsHWid(string keyName)
|
||||
{
|
||||
const string keyPath = @"HKEY_CURRENT_USER\SOFTWARE\Space Wizards\Robust";
|
||||
|
||||
var regKey = Registry.GetValue(keyPath, keyName, null);
|
||||
if (regKey is byte[] { Length: LengthHwid } bytes)
|
||||
return bytes;
|
||||
|
||||
var newId = new byte[LengthHwid];
|
||||
RandomNumberGenerator.Fill(newId);
|
||||
Registry.SetValue(
|
||||
keyPath,
|
||||
keyName,
|
||||
newId,
|
||||
RegistryValueKind.Binary);
|
||||
|
||||
return newId;
|
||||
}
|
||||
|
||||
private byte[] GetFileHWid()
|
||||
{
|
||||
var path = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
var hwidPath = Path.Combine(path, ".hwid");
|
||||
|
||||
var value = ReadHWidFile(hwidPath);
|
||||
if (value != null)
|
||||
return value;
|
||||
|
||||
value = RandomNumberGenerator.GetBytes(LengthHwid);
|
||||
File.WriteAllBytes(hwidPath, value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static byte[]? ReadHWidFile(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = File.ReadAllBytes(path);
|
||||
if (value.Length == LengthHwid)
|
||||
return value;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// First time the file won't exist.
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -26,5 +26,20 @@ public interface IGameController
|
||||
/// This exists to give content module more control over tick updating.
|
||||
/// </summary>
|
||||
event Action<FrameEventArgs>? TickUpdateOverride;
|
||||
|
||||
/// <summary>
|
||||
/// Get the games Title, if Options.DefaultWindowTitle or if defaultWindowTitle is not set in the manifest.yml, it will default to RobustToolbox.
|
||||
/// </summary>
|
||||
string GameTitle();
|
||||
|
||||
/// <summary>
|
||||
/// Get the games Window Icon set, if Options.WindowIconSet or if windowIconSet is not set in the manifest.yml, it will default to an empty string.
|
||||
/// </summary>
|
||||
string WindowIconSet();
|
||||
|
||||
/// <summary>
|
||||
/// Get the games Splash Logo, if Options.SplashLogo or if splashLogo is not set in the manifest.yml, it will default to an empty string.
|
||||
/// </summary>
|
||||
string SplashLogo();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -156,7 +156,6 @@ public sealed partial class PhysicsSystem
|
||||
|
||||
if (activeA == false && activeB == false)
|
||||
{
|
||||
contact.IsTouching = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,13 +34,13 @@ namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
var from = ScreenToWorld(new Vector2(a, 0));
|
||||
var to = ScreenToWorld(new Vector2(a, viewportSize.Y));
|
||||
args.WorldHandle.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));
|
||||
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 1f));
|
||||
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 0.3f));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
@@ -14,16 +15,16 @@ namespace Robust.Client.Placement.Modes
|
||||
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
|
||||
{
|
||||
// Go over diagonal size so when placing in a line it doesn't stop snapping.
|
||||
const float SearchBoxSize = 2f; // size of search box in meters
|
||||
const float searchBoxSize = 2f; // size of search box in meters
|
||||
|
||||
MouseCoords = ScreenToCursorGrid(mouseScreen).AlignWithClosestGridTile(SearchBoxSize, pManager.EntityManager, pManager.MapManager);
|
||||
MouseCoords = ScreenToCursorGrid(mouseScreen).AlignWithClosestGridTile(searchBoxSize, pManager.EntityManager, pManager.MapManager);
|
||||
|
||||
var gridId = MouseCoords.GetGridUid(pManager.EntityManager);
|
||||
var gridId = pManager.EntityManager.System<SharedTransformSystem>().GetGrid(MouseCoords);
|
||||
|
||||
if (!pManager.EntityManager.TryGetComponent<MapGridComponent>(gridId, out var mapGrid))
|
||||
return;
|
||||
|
||||
CurrentTile = mapGrid.GetTileRef(MouseCoords);
|
||||
CurrentTile = pManager.EntityManager.System<SharedMapSystem>().GetTileRef(gridId.Value, mapGrid, MouseCoords);
|
||||
float tileSize = mapGrid.TileSize; //convert from ushort to float
|
||||
GridDistancing = tileSize;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -19,12 +18,12 @@ namespace Robust.Client.Placement.Modes
|
||||
MouseCoords = ScreenToCursorGrid(mouseScreen);
|
||||
|
||||
var tileSize = 1f;
|
||||
var gridIdOpt = MouseCoords.GetGridUid(pManager.EntityManager);
|
||||
var gridIdOpt = pManager.EntityManager.System<SharedTransformSystem>().GetGrid(MouseCoords);
|
||||
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
if (gridIdOpt is { } gridId && gridId.IsValid())
|
||||
{
|
||||
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
CurrentTile = mapGrid.GetTileRef(MouseCoords);
|
||||
CurrentTile = pManager.EntityManager.System<SharedMapSystem>().GetTileRef(gridId, mapGrid ,MouseCoords);
|
||||
tileSize = mapGrid.TileSize; //convert from ushort to float
|
||||
}
|
||||
|
||||
@@ -50,12 +49,12 @@ namespace Robust.Client.Placement.Modes
|
||||
return false;
|
||||
}
|
||||
|
||||
var map = MouseCoords.GetMapId(pManager.EntityManager);
|
||||
var map = pManager.EntityManager.System<SharedTransformSystem>().GetMapId(MouseCoords);
|
||||
var bottomLeft = new Vector2(CurrentTile.X, CurrentTile.Y);
|
||||
var topRight = new Vector2(CurrentTile.X + 0.99f, CurrentTile.Y + 0.99f);
|
||||
var box = new Box2(bottomLeft, topRight);
|
||||
|
||||
return !EntitySystem.Get<EntityLookupSystem>().AnyEntitiesIntersecting(map, box);
|
||||
return !pManager.EntityManager.System<EntityLookupSystem>().AnyEntitiesIntersecting(map, box);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -754,14 +754,14 @@ namespace Robust.Client.Placement
|
||||
|
||||
if (CurrentPermission.IsTile)
|
||||
{
|
||||
var gridIdOpt = coordinates.GetGridUid(EntityManager);
|
||||
var gridIdOpt = EntityManager.System<SharedTransformSystem>().GetGrid(coordinates);
|
||||
// If we have actually placed something on a valid grid...
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
if (gridIdOpt is { } gridId && gridId.IsValid())
|
||||
{
|
||||
var grid = EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
|
||||
// no point changing the tile to the same thing.
|
||||
if (grid.GetTileRef(coordinates).Tile.TypeId == CurrentPermission.TileType)
|
||||
if (EntityManager.System<SharedMapSystem>().GetTileRef(gridId, grid, coordinates).Tile.TypeId == CurrentPermission.TileType)
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user