mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
206 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6018f2fb12 | ||
|
|
52c69fc293 | ||
|
|
4dc17f3aca | ||
|
|
d22280f177 | ||
|
|
9e8f7092ea | ||
|
|
de188cc773 | ||
|
|
784a02c0e7 | ||
|
|
c8db7f98db | ||
|
|
318c37e686 | ||
|
|
524be86449 | ||
|
|
ddeb78accd | ||
|
|
585e847818 | ||
|
|
ac3cb4dc2a | ||
|
|
c06ca39009 | ||
|
|
06b11a51f1 | ||
|
|
4938a159d4 | ||
|
|
27f2e270ce | ||
|
|
94e60e0b10 | ||
|
|
c6863033a5 | ||
|
|
917878d05f | ||
|
|
10e4766809 | ||
|
|
abd5149245 | ||
|
|
912b6da20a | ||
|
|
94fe0b7721 | ||
|
|
9fac1e78fb | ||
|
|
6697b76683 | ||
|
|
b2ab247b5b | ||
|
|
7411ae8138 | ||
|
|
d398e3a75b | ||
|
|
a5047224bb | ||
|
|
058821c08b | ||
|
|
0c691b061d | ||
|
|
51bbc5dc45 | ||
|
|
2d3522e752 | ||
|
|
d4f265c314 | ||
|
|
7654d38612 | ||
|
|
cdcc255123 | ||
|
|
2f56a6a110 | ||
|
|
16fc48cef2 | ||
|
|
5cecbb2cff | ||
|
|
6115d6d5cc | ||
|
|
60d26be139 | ||
|
|
186392ea80 | ||
|
|
ebe4538d4c | ||
|
|
745d0e5532 | ||
|
|
d4f7e60432 | ||
|
|
ced127c164 | ||
|
|
f91bcb62b1 | ||
|
|
5268a4a3f0 | ||
|
|
1f1e50539b | ||
|
|
ea3132bbba | ||
|
|
67ccaec418 | ||
|
|
40b70e9447 | ||
|
|
7d37db9ce0 | ||
|
|
dd8688df3d | ||
|
|
b02c53c6ad | ||
|
|
38d3b83818 | ||
|
|
f02cd0083a | ||
|
|
c2c8af16d0 | ||
|
|
b61003e2a0 | ||
|
|
fb0ec52f8c | ||
|
|
fee79d8aa5 | ||
|
|
4508105412 | ||
|
|
a0ebb290e2 | ||
|
|
d45497e53b | ||
|
|
ff8dd021c3 | ||
|
|
34a371ef1f | ||
|
|
83109b08e9 | ||
|
|
3d7b83db05 | ||
|
|
41b2ee19a1 | ||
|
|
856cdb8a3d | ||
|
|
b783cd79be | ||
|
|
b5ba964f61 | ||
|
|
09676a1d9f | ||
|
|
6959f21927 | ||
|
|
26a1fb35b5 | ||
|
|
7fb3ce0e70 | ||
|
|
5497b52100 | ||
|
|
18b5f33080 | ||
|
|
30d3367c50 | ||
|
|
f6aabd1a22 | ||
|
|
6d229a3eb2 | ||
|
|
da28bdbce5 | ||
|
|
0181988225 | ||
|
|
fb2ba7460a | ||
|
|
b70d20a217 | ||
|
|
ebc33df457 | ||
|
|
697af6771c | ||
|
|
20706870da | ||
|
|
372fa39228 | ||
|
|
d6bfbe4f6f | ||
|
|
54645b4adf | ||
|
|
7c16573f3e | ||
|
|
8935b39987 | ||
|
|
388f8369a8 | ||
|
|
217d889e36 | ||
|
|
f243baccf2 | ||
|
|
790f42ea70 | ||
|
|
a5fcf122b8 | ||
|
|
df2d6ab8c2 | ||
|
|
23c90c0c45 | ||
|
|
c69756e7f1 | ||
|
|
07fbd5263c | ||
|
|
a1cdd60602 | ||
|
|
6fcaee91b6 | ||
|
|
4d4f353680 | ||
|
|
9f0dad80e4 | ||
|
|
c3f4b9bd67 | ||
|
|
641411288f | ||
|
|
1fb7d3e723 | ||
|
|
8cbc5d4cd8 | ||
|
|
b4863dcc38 | ||
|
|
e771530de2 | ||
|
|
ce3a5f6bfa | ||
|
|
1cd802640a | ||
|
|
1983734e2d | ||
|
|
ea380056b4 | ||
|
|
9c26fba308 | ||
|
|
3de48d7595 | ||
|
|
7a510298e1 | ||
|
|
046db645e9 | ||
|
|
63e383bb17 | ||
|
|
e316649fd1 | ||
|
|
121b58ee9a | ||
|
|
d11f4bcc14 | ||
|
|
735ef09d42 | ||
|
|
772173cbaf | ||
|
|
4bd7aa16c1 | ||
|
|
bc4b4d3e6f | ||
|
|
7d9a039252 | ||
|
|
857f9a540b | ||
|
|
6ae332d543 | ||
|
|
f4786f2d90 | ||
|
|
dcbe0505dc | ||
|
|
c3489d4ded | ||
|
|
8498634993 | ||
|
|
3d289fbd83 | ||
|
|
ebce0daa1b | ||
|
|
bbbfcca303 | ||
|
|
e195ac4ce6 | ||
|
|
dc5cbd085b | ||
|
|
c4dff678a9 | ||
|
|
cd9616c87c | ||
|
|
d1c6c11755 | ||
|
|
1ebac7c894 | ||
|
|
6b41be8901 | ||
|
|
51c929c8ec | ||
|
|
4863b09f0a | ||
|
|
cdd3afaa4c | ||
|
|
fee67b648c | ||
|
|
0bf4123b8d | ||
|
|
9ea51432d1 | ||
|
|
c8da6f30a3 | ||
|
|
974c1e827d | ||
|
|
3c48b24539 | ||
|
|
d8aefe5118 | ||
|
|
6d9a4719a9 | ||
|
|
893173ab17 | ||
|
|
7c0f1b8031 | ||
|
|
bb57f82811 | ||
|
|
0fc9b0acd0 | ||
|
|
f2b7f0d8d2 | ||
|
|
0ec189dece | ||
|
|
c876eb1f4c | ||
|
|
1037fc735e | ||
|
|
d5df765467 | ||
|
|
93cf9f4227 | ||
|
|
d2977e2a63 | ||
|
|
a3f0ea19c4 | ||
|
|
d9032b8757 | ||
|
|
cba6e37f9f | ||
|
|
90ec9a80c9 | ||
|
|
7eaf2f590b | ||
|
|
0439ea9893 | ||
|
|
74aa8fa9ed | ||
|
|
ceeb002692 | ||
|
|
78d807b13c | ||
|
|
5cd4c187bf | ||
|
|
fec477bf41 | ||
|
|
9d00b1f093 | ||
|
|
de0871d17b | ||
|
|
053c469cac | ||
|
|
efa8975bc6 | ||
|
|
4851e913b0 | ||
|
|
74a318c521 | ||
|
|
e52a6bbbf2 | ||
|
|
e169d6a5a2 | ||
|
|
3634ee636b | ||
|
|
2349728eab | ||
|
|
777f02cadd | ||
|
|
0fc6f2bce6 | ||
|
|
dc3705e520 | ||
|
|
01f71ca55a | ||
|
|
c5e812836b | ||
|
|
56eda3ea92 | ||
|
|
9dffd36319 | ||
|
|
a45b72a1c5 | ||
|
|
bd0579ed6d | ||
|
|
c73b54862e | ||
|
|
6436ff8040 | ||
|
|
98313ae369 | ||
|
|
0e63391203 | ||
|
|
261bfaeeb8 | ||
|
|
4017e1f57e | ||
|
|
e170bf1ad2 | ||
|
|
da0abd2535 |
34
.github/workflows/build-all-configurations.yml
vendored
Normal file
34
.github/workflows/build-all-configurations.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Build All Configurations
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
targetOS: [Windows, Linux, MacOS]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Build Debug
|
||||
run: dotnet build --no-restore --configuration Debug /p:WarningsAsErrors=nullable /p:TargetOS=${{ matrix.targetOS }}
|
||||
- name: Build Tools
|
||||
run: dotnet build --no-restore --configuration Tools /p:WarningsAsErrors=nullable /p:TargetOS=${{ matrix.targetOS }}
|
||||
- name: Build Release
|
||||
run: dotnet build --no-restore --configuration Release /p:WarningsAsErrors=nullable /p:TargetOS=${{ matrix.targetOS }}
|
||||
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest] # , macos-latest] - temporarily disabled due to libfreetype.dll errors.
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
||||
2
.github/workflows/publish-client.yml
vendored
2
.github/workflows/publish-client.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Package client
|
||||
run: Tools/package_client_build.py -p windows mac linux
|
||||
run: Tools/package_client_build.py
|
||||
|
||||
- name: Shuffle files around
|
||||
run: |
|
||||
|
||||
@@ -44,10 +44,11 @@
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageVersion Include="Nett" Version="0.15.0" />
|
||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.9.4" />
|
||||
<PackageVersion Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
|
||||
<PackageVersion Include="Pidgin" Version="3.3.0" />
|
||||
<PackageVersion Include="Robust.Natives" Version="0.1.1" />
|
||||
<PackageVersion Include="Robust.Natives" Version="0.2.1" />
|
||||
<PackageVersion Include="Robust.Natives.Zstd" Version="0.1.0-zstd1.5.7" />
|
||||
<PackageVersion Include="Robust.Natives.Cef" Version="131.3.5" />
|
||||
<PackageVersion Include="Robust.Shared.AuthLib" Version="0.1.2" />
|
||||
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
|
||||
@@ -55,11 +56,14 @@
|
||||
<PackageVersion Include="Serilog" Version="4.2.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.Sdl" Version="1.0.0" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.1.0" />
|
||||
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
<PackageVersion Include="libsodium" Version="1.0.20.1" />
|
||||
<PackageVersion Include="System.Management" Version="9.0.8" />
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />
|
||||
<PackageVersion Include="TerraFX.Interop.Xlib" Version="6.4.0" />
|
||||
<PackageVersion Include="VorbisPizza" Version="1.3.0" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.NameGenerator\Robust.Client.NameGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\Robust.Client.Injectors.csproj" ReferenceOutputAssembly="false"/>
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\Robust.Client.Injectors.csproj" ReferenceOutputAssembly="false">
|
||||
<SetConfiguration Condition="'$(Configuration)' == 'DebugOpt'">Configuration=Debug</SetConfiguration>
|
||||
<SetConfiguration Condition="'$(Configuration)' == 'Tools'">Configuration=Release</SetConfiguration>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- XamlIL does not make use of special Robust configurations like DebugOpt. Convert these down. -->
|
||||
|
||||
Submodule NetSerializer updated: 4882400f2c...84ab8fec64
302
RELEASE-NOTES.md
302
RELEASE-NOTES.md
@@ -54,7 +54,307 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 263.0.1
|
||||
## 267.2.2
|
||||
|
||||
|
||||
## 267.2.1
|
||||
|
||||
|
||||
## 267.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Sprites and Sprite layers have a new `Loop` data field that can be set to false to automatically pause animations once they have finished.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `CollectionExtensions.TryGetValue` throwing an exception when given a negative list index.
|
||||
* Fixed `EntityManager.PredictedQueueDeleteEntity()` not deferring changes for networked entities until the end of the tick.
|
||||
* Fixed `EntityManager.IsQueuedForDeletion` not returning true foe entities getting deleted via `PredictedQueueDeleteEntity()`
|
||||
|
||||
### Other
|
||||
|
||||
* `IResourceManager.GetContentRoots()` has been obsoleted and returns no more results.
|
||||
|
||||
### Internal
|
||||
|
||||
* `IResourceManager.GetContentRoots()` has been replaced with a similar method on `IResourceManagerInternal`. This new method returns `string`s instead of `ResPath`s, and usage code has been updated to use these paths correctly.
|
||||
|
||||
|
||||
## 267.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Animation:
|
||||
* `AnimationTrackProperty.KeyFrame` can now have easings functions applied.
|
||||
* Graphics:
|
||||
* `PointLightComponent` now has two fields, `falloff` and `curveFactor`, for controlling light falloff and the shape of the light attenuation curve.
|
||||
* `IClydeViewport` now has an `Id` and `ClearCachedResources` event. Together, these allow you to properly cache rendering resources per viewport.
|
||||
* Miscellaneous:
|
||||
* Added `display.max_fps` CVar.
|
||||
* Added `IGameTiming.FrameStartTime`.
|
||||
* Sandbox:
|
||||
* Added `System.WeakReference<T>`.
|
||||
* Added `SpaceWizards.Sodium.CryptoGenericHashBlake2B.Hash()`.
|
||||
* Added `System.Globalization.UnicodeCategory`.
|
||||
* Serialization:
|
||||
* Added a new entity yaml deserialization option (`SerializationOptions.EntityExceptionBehaviour`) that can optionally make deserialization more exception tolerant.
|
||||
* Tooling:
|
||||
* `devwindow` now has a tab listing active `IRenderTarget`s, allowing insight into resource consumption.
|
||||
* `loadgrid` now creates a map if passed an invalid map ID.
|
||||
* Added game version information to F3 overlay.
|
||||
* Added completions to more map commands.
|
||||
* UI system:
|
||||
* `Control.OrderedChildCollection` (gotten from `.Children`) now implements `IReadOnlyList<Control>`, allowing it to be indexed directly.
|
||||
* Added `WrapContainer` control. This lays out multiple elements along an axis, wrapping them if there's not enough space. It comes with many options and can handle multiple axes.
|
||||
* Popups/modals now work in secondary windows. This entails putting roots for these on each UI root.
|
||||
* If you are not using `OSWindow` and are instead creating secondary windows manually, you need to call `WindowRoot.CreateRootControls()` manually for this to work.
|
||||
* Added `Axis` enum, `IAxisImplementation` interface and axis implementations. These allow writing general-purpose UI layout code that can work on multiple axis at once.
|
||||
* WebView:
|
||||
* Added `web.remote_debug_port` CVar to change Chromium's remote debug port.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Audio:
|
||||
* Fix audio occlusion & velocity being calculated with the audio entity instead of the source entity.
|
||||
* Bound UI:
|
||||
* Try to fix an assert related to `UserInterfaceComponent` delta states.
|
||||
* Configuration:
|
||||
* The client no longer tries to send `CLIENT | REPLICATED` CVars when not connected to a server. This could cause test failures.
|
||||
* Math:
|
||||
* Fixed `Matrix3Helpers.TransformBounds()` returning an incorrect result. Now it effectively behaves like `Matrix3Helpers.TransformBox()` and has been marked as obsolete.
|
||||
* Physics:
|
||||
* Work around an undiagnosed crash processing entities without parents.
|
||||
* Serialization:
|
||||
* Fix `[DataRecord]`s with computed get-only properties.
|
||||
* Resources:
|
||||
* Fix some edge case broken path joining in `DirLoader` and `WritableDirProvider`.
|
||||
* Tests:
|
||||
* Fix `PlacementManager.CurrentMousePosition` in integration tests.
|
||||
* UI system:
|
||||
* Animations for the debug console and scrolling are no longer framerate dependent.
|
||||
* Fix `OutputPanel.SetMessage` triggering a scrolling animation when editing messages other than the last one.
|
||||
* Fix word wrapping with two-`char` runes in `RichTextLabel` and `OutputPanel`.
|
||||
* WebView:
|
||||
* Multiple clients with WebView can now run at the same time, thanks to better CEF cache management.
|
||||
|
||||
### Other
|
||||
|
||||
* Audio:
|
||||
* Improved error logging for invalid file names in `SharedAudioSystem`.
|
||||
* Configuration:
|
||||
* Fix crash if more than 255 `REPLICATED` CVars exist. Also increased the max size of the CVar replication message.
|
||||
* Entities:
|
||||
* Transform:
|
||||
* `AnchorEntity` logs instead of using an assert for invalid arguments.
|
||||
* Containers:
|
||||
* `SharedContainerSystem.CleanContainer` now uses `PredictedDel()` instead.
|
||||
* Networking:
|
||||
* The client now logs an error when attempting to send a network message without server connection. Previously, it would be silently dropped.
|
||||
* `net.interp` and `net.buffer_size` CVars are now `REPLICATED`.
|
||||
* Graphics:
|
||||
* The function used for pointlight attenuation has been modified to be c1 continuous as opposed to simply c0 continuous, resulting in smoother boundary behavior.
|
||||
* RSI validator no longer allows empty (`""`) state names.
|
||||
* Packaging:
|
||||
* Server packaging now excludes all files in the `Audio/` directory.
|
||||
* Server packaging now excludes engine resources `EngineFonts/` and `Midi/`.
|
||||
* ACZ explicitly specifies manifest charset as UTF-8.
|
||||
* Serialization:
|
||||
* `CurTime`-relative `TimeSpan` values that are `MaxValue` now deserialize without overflow.
|
||||
* `SpriteSpecifier.Texture` will now fail to validate if the path is inside a `.rsi`. Use RSI sprite specifiers instead.
|
||||
* Resources:
|
||||
* `IWritableDirProvider.RootDir` is now null on clients.
|
||||
* WebView:
|
||||
* CEF cache is no longer in the content-accessible user data directory.
|
||||
|
||||
### Internal
|
||||
|
||||
* Added some debug commands for debugging viewport resource management: `vp_clear_all_cached` & `vp_test_finalize`
|
||||
* `uitest` command now supports command argument for tab selection, like `uitest2`.
|
||||
* Rewrote `BoxContainer` implementation to make use of new axis system.
|
||||
* Moved `uitest2` and `devwindow` to use the `OSWindow` control.
|
||||
* SDL3 binding has been moved to `SpaceWizards.Sdl` NuGet package.
|
||||
* `dmetamem` command has been moved from `DEBUG` to `TOOLS`.
|
||||
* Consolidate `AttachToGridOrMap` with `TryGetMapOrGridCoordinates`.
|
||||
* Secondary window render targets have clear names specified.
|
||||
* Updated `SpaceWizards.NFluidsynth` to `0.2.2`.
|
||||
* `Robust.Client.WebView.Cef.Program` is now internal.
|
||||
* `download_manifest_file.py` script in repo now always decodes as UTF-8 correctly.
|
||||
* Added a new debug assert to game state processing.
|
||||
|
||||
## 267.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* When a player disconnects, the relevant callbacks are now fired *after* removing the channel from `INetManager`.
|
||||
|
||||
### New features
|
||||
|
||||
* Engine builds are now published for ARM64 & FreeBSD.
|
||||
* CPU model names are now detected on Windows & Linux ARM64.
|
||||
* Toolshed's `spawn:in` command now works on entities without `Physics` component.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* SDL3 windowing backend fixes:
|
||||
* Avoid macOS freezes with multiple windows.
|
||||
* Fix macOS rendering breaking when closing secondary windows.
|
||||
* File dialogs properly associate parent windows.
|
||||
* Fix IME positions not working with UI scaling properly.
|
||||
* Properly specify library names for loading native library.
|
||||
|
||||
* WinBit threads don't permanently stay stuck when their window closes.
|
||||
* Checking for the "`null`" literal in serialization is now culture invariant.
|
||||
|
||||
### Other
|
||||
|
||||
* Compat mode on the client now defaults to on for Windows Snapdragon devices, to work around driver bugs.
|
||||
* Update various libraries & natives. This enables out-of-the-box ARM64 support on all platforms and is a long-overdue modernization.
|
||||
* Key name displays now use proper Unicode symbols for macOS ⌥ and ⌘.
|
||||
* Automated CI for RobustToolbox runs on macOS again.
|
||||
* Autocompletions for `ProtoId<T>` in Toolshed now use `PrototypeIdsLimited` instead of arbitrarily cutting out if more than 256 of a prototype exists.
|
||||
|
||||
|
||||
## 266.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* A new analyzer has been added that will error if you attempt to subscribe to `AfterAutoHandleStateEvent` on a
|
||||
component that doesn't have the `AutoGenerateComponentState` attribute, or doesn't have the first argument of that
|
||||
attribute set to `true`. In most cases you will want to set said argument to `true`.
|
||||
* The fields on `AutoGenerateComponentStateAttribute` are now `readonly`. Setting these directly (instead of using the constructor arguments) never worked in the first place, so this change only catches existing programming errors.
|
||||
* When a player disconnects, `ISharedPlayerManager.PlayerStatusChanged` is now fired *after* removing the session from the `Sessions` list.
|
||||
* `.rsi` files are now compacted into individual `.rsic` files on packaging. This should significantly reduce file count & improve performance all over release builds, but breaks the ability to access `.png` files into RSIs directly. To avoid this, `"rsic": false` can be specified in the RSI's JSON metadata.
|
||||
* The `scale` command has been removed, with the intent of it being moved to content instead.
|
||||
|
||||
### New features
|
||||
|
||||
* ViewVariables editors for `ProtoId` fields now have a Select button which opens a window listing all available prototypes of the appropriate type.
|
||||
* added **IConfigurationManager**.*SubscribeMultiple* ext. method to provide simpler way to unsubscribe from multiple cvar at once
|
||||
* Added `SharedMapSystem.QueueDeleteMap`, which deletes a map with the specified MapId in the next tick.
|
||||
* Added generic version of `ComponentRegistry.TryGetComponent`.
|
||||
* `AttributeHelper.HasAttribute` has had an overload's type signature loosened from `INamedTypeSymbol` to `ITypeSymbol`.
|
||||
* Errors are now logged when sending messages to disconnected `INetChannel`s.
|
||||
* Warnings are now logged if sending a message via Lidgren failed for some reason.
|
||||
* `.yml` and `.ftl` files in the same directory are now concatenated onto each other, to reduce file count in packaged builds. This is done through the new `AssetPassMergeTextDirectories` pass.
|
||||
* Added `System.Linq.ImmutableArrayExtensions` to sandbox.
|
||||
* `ImmutableDictionary<TKey, TValue>` and `ImmutableHashSet<T>` can now be network serialized.
|
||||
* `[AutoPausedField]` now works on fields of type `Dictionary<TKey, TimeSpan>`.
|
||||
* `[NotYamlSerializable]` analyzer now detects nullable fields of the not-serializable type.
|
||||
* `ItemList` items can now have a scale applied for the icon.
|
||||
* Added new OS mouse cursor shapes for the SDL3 backend. These are not available on the GLFW backend.
|
||||
* Added `IMidiRenderer.MinVolume` to scale the volume of MIDI notes.
|
||||
* Added `SharedPhysicsSystem.ScaleFixtures`, to apply the physics-only changes of the prior `scale` command.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `LayoutContainer.SetMarginsPreset` and `SetAnchorAndMarginPreset` now correctly use the provided control's top anchor when calculating the margins for its presets; it previously used the bottom anchor instead. This may result in a few UI differences, by a few pixels at most.
|
||||
* `IConfigurationManager` no longer logs a warning when saving configuration in an integration test.
|
||||
* Fixed impossible-to-source `ChannelClosedException`s when sending some net messages to disconnected `INetChannel`s.
|
||||
* Fixed an edge case causing some color values to throw an error in `ColorNaming`.
|
||||
* Fresh builds from specific projects should no longer cause errors related to `Robust.Client.Injectors` not being found.
|
||||
* Stopped errors getting logged about `NoteOff` and `NoteOn` operations failing in MIDI.
|
||||
* Fixed MIDI players not resuming properly when re-entering PVS range.
|
||||
|
||||
### Other
|
||||
|
||||
* Updated ImageSharp to 3.1.11 to stop the warning about a DoS vulnerability.
|
||||
* Prototype YAML documents that are completely empty are now skipped by the prototype loader. Previously they would cause a load error for the whole file.
|
||||
* `TileSpawnWindow` can now be localized.
|
||||
* `BaseWindow` uses the new mouse cursor shapes for diagonal resizing.
|
||||
* `NFluidsynth` has been updated to 0.2.0
|
||||
|
||||
### Internal
|
||||
|
||||
* Added `uitest` tab for standard mouse cursor shapes.
|
||||
|
||||
|
||||
## 265.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* More members in `IntegrationInstance` now enforce that the instance is idle before accessing it.
|
||||
* `Prototype.ValidateDirectory` now requires that prototype IDs have no spaces or periods in them.
|
||||
* `IPrototypeManager.TryIndex` no longer logs errors unless using the overload with an optional parameter. Use `Resolve()` instead if error logging is desired.
|
||||
* `LocalizedCommands` now has a `Loc` property that refers to `LocalizationManager`. This can cause compile failures if you have static methods in child types that referenced static `Loc`.
|
||||
* `[AutoGenerateComponentState]` now works on parent members for inherited classes. This can cause compile failures in certain formerly silently broken cases with overriden properties.
|
||||
* `Vector3`, `Vector4`, `Quaternion`, and `Matrix4` have been removed from `Robust.Shared.Maths`. Use the `System.Numerics` types instead.
|
||||
|
||||
### New features
|
||||
|
||||
* `RobustClientPackaging.WriteClientResources()` and `RobustServerPackaging.WriteServerResources()` now have an overload taking in a set of things to ignore in the content resources directory.
|
||||
* Added `IPrototypeManager.Resolve()`, which logs an error if the resolved prototype does not exist. This is effectively the previous (but not original) default behavior of `IPrototypeManager.TryIndex`.
|
||||
* There's now a ViewVariables property editor for tuples.
|
||||
* Added `ColorNaming` helper functions for getting textual descriptions of color values.
|
||||
* Added Oklab/Oklch conversion functions for `Color`.
|
||||
* `ColorSelectorSliders` now displays textual descriptions of color values.
|
||||
* Added `TimeSpanExt.TryTimeSpan` to parse `TimeSpan`s with the `1.5h` format available in YAML.
|
||||
* Added `ITestContextLike` and related classes to allow controlling pooled integration instances better.
|
||||
* `EntProtoId` VV prop editors now don't allow setting invalid prototype IDs, inline with `ProtoId<T>`.
|
||||
* Custom VV controls can now be registered using `IViewVariableControlFactory`.
|
||||
* The entity spawn window now shows all placement modes registered with `IPlacementManager`.
|
||||
* Added `VectorHelpers.InterpolateCubic` for `System.Numerics` `Vector3` and `Vector4`.
|
||||
* Added deconstruct helpers for `System.Numerics` `Vector3` and `Vector4`.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Pooled integration instances returned by `RobustIntegrationTest` are now treated as non-idle, for consistency with non-pooled startups.
|
||||
* `SharedAudioSystem.SetState` no longer calls `DirtyField` on `PlaybackPosition`, an unnetworked field.
|
||||
* Fix loading texture files from the root directory.
|
||||
* Fix integration test pooling leaking non-reusable instances.
|
||||
* Fix multiple bugs where VV displayed the wrong property editor for remote values.
|
||||
* VV displays group headings again in member list.
|
||||
* Fix a stack overflow that could occur with `ColorSelectorSliders`.
|
||||
* `MidiRenderer` now properly handles `NoteOn` events with 0 velocity (which should actually be treated as `NoteOff` events).
|
||||
|
||||
### Other
|
||||
|
||||
* The debug assert for `RobustRandom.Next(TimeSpan, TimeSpan)` now allows for the two arguments to be equal.
|
||||
* The configuration system will now report an error instead of warning if it fails to load the config file.
|
||||
* Members in `IntegrationInstance` that enforce the instance is idle now always allow access from the instance's thread (e.g. from a callback).
|
||||
* `IPrototypeManager` methods now have `[ForbidLiteral]` where appropriate.
|
||||
* Performance improvements to physics system.
|
||||
* `[ValidatePrototypeIdAttribute]` has been marked as obsolete.
|
||||
* `ParallelManager` no longer cuts out exception information for caught job exceptions.
|
||||
* Improved logging for PVS uninitialized/deleted entity errors.
|
||||
|
||||
### Internal
|
||||
|
||||
* General code & warning cleanup.
|
||||
* Fix `VisibilityTest` being unreliable.
|
||||
* `ColorSelectorSliders` has been internally refactored.
|
||||
* Added CI workflows that test all RT build configurations.
|
||||
|
||||
## 264.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `IPrototypeManager.Index(Type kind, string id)` now throws `UnknownPrototypeException` instead of `KeyNotFoundException`, for consistency with `IPrototypeManager.Index<T>`.
|
||||
|
||||
### New features
|
||||
|
||||
* Types can now implement the new interface `IRobustCloneable<T>` to be cloned by the component state source generator.
|
||||
* Added extra Roslyn Analyzers to detect some misuse of prototypes:
|
||||
* Network serializing prototypes (tagging them with `[Serializable, NetSerializable]`).
|
||||
* Constructing new instances of prototypes directly.
|
||||
* Add `PrototypeManagerExt.Index` helper function that takes a nullable `ProtoId<T>`, returning null if the ID is null.
|
||||
* Added an `AlwaysActive` field to `WebViewControl` to make a browser window active even when not in the UI tree.
|
||||
* Made some common dependencies accessible through `IPlacementManager`.
|
||||
* Added a new `GENITIVE()` localization helper function, which is useful for certain languages.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Sprite scale is now correctly applied to sprite boundaries in `SpriteSystem.GetLocalBounds`.
|
||||
* Fixed documentation for `IPrototypeManager.Index<T>` stating that `KeyNotFoundException` gets thrown, when in actuality `UnknownPrototypeException` gets thrown.
|
||||
|
||||
### Other
|
||||
|
||||
* More tiny optimizations to `DataDefinitionAnalyzer`.
|
||||
* NetSerializer has been updated. On debug, it will now report *where* a type that can't be serialized is referenced from.
|
||||
|
||||
### Internal
|
||||
|
||||
* Minor internal code cleanup.
|
||||
|
||||
|
||||
## 263.0.0
|
||||
|
||||
@@ -21,7 +21,8 @@ zzzz-object-pronoun = { GENDER($ent) ->
|
||||
}
|
||||
|
||||
# Used internally by the DAT-OBJ() function.
|
||||
# Not used in en-US. Created for supporting other languages.
|
||||
# Not used in en-US. Created to support other languages.
|
||||
# (e.g., "to him," "for her")
|
||||
zzzz-dat-object = { GENDER($ent) ->
|
||||
[male] him
|
||||
[female] her
|
||||
@@ -29,6 +30,16 @@ zzzz-dat-object = { GENDER($ent) ->
|
||||
*[neuter] it
|
||||
}
|
||||
|
||||
# Used internally by the GENITIVE() function.
|
||||
# Not used in en-US. Created to support other languages.
|
||||
# e.g., "у него" (Russian), "seines Vaters" (German).
|
||||
zzzz-genitive = { GENDER($ent) ->
|
||||
[male] his
|
||||
[female] her
|
||||
[epicene] their
|
||||
*[neuter] its
|
||||
}
|
||||
|
||||
# Used internally by the POSS-PRONOUN() function.
|
||||
zzzz-possessive-pronoun = { GENDER($ent) ->
|
||||
[male] his
|
||||
|
||||
3
Resources/Locale/en-US/_generic.ftl
Normal file
3
Resources/Locale/en-US/_generic.ftl
Normal file
@@ -0,0 +1,3 @@
|
||||
generic-map = map
|
||||
generic-grid = grid
|
||||
generic-mapid = map Id
|
||||
33
Resources/Locale/en-US/color-naming.ftl
Normal file
33
Resources/Locale/en-US/color-naming.ftl
Normal file
@@ -0,0 +1,33 @@
|
||||
color-hue-chroma-lightness = {$lightness} {$chroma} {$hue}
|
||||
color-hue-chroma = {$chroma} {$hue}
|
||||
color-hue-lightness = {$lightness} {$hue}
|
||||
color-very-dark = very dark
|
||||
color-dark = dark
|
||||
color-light = light
|
||||
color-very-light = very light
|
||||
color-mixed-hue = {$a} {$b}
|
||||
color-pale = pale
|
||||
color-gray-adjective = gray
|
||||
color-strong = strong
|
||||
color-pink = pink
|
||||
color-red = red
|
||||
color-orange = orange
|
||||
color-yellow = yellow
|
||||
color-green = green
|
||||
color-cyan = cyan
|
||||
color-blue = blue
|
||||
color-purple = purple
|
||||
color-brown = brown
|
||||
color-white = white
|
||||
color-gray = gray
|
||||
color-black = black
|
||||
color-unknown = unknown color, you should not see this
|
||||
|
||||
color-pink-color-red = pinkish red
|
||||
color-red-color-orange = reddish orange
|
||||
color-orange-color-yellow = orangeish yellow
|
||||
color-yellow-color-green = yellowish green
|
||||
color-green-color-cyan = greenish cyan
|
||||
color-cyan-color-blue = cyanish blue
|
||||
color-blue-color-purple = blueish purple
|
||||
color-purple-color-pink = purpleish pink
|
||||
@@ -411,9 +411,6 @@ cmd-spawn-help = spawn <prototype> OR spawn <prototype> <relative entity ID> OR
|
||||
cmd-cspawn-desc = Spawns a client-side entity with specific type at your feet.
|
||||
cmd-cspawn-help = cspawn <entity type>
|
||||
|
||||
cmd-scale-desc = Increases or decreases an entity's size naively.
|
||||
cmd-scale-help = scale <entityUid> <float>
|
||||
|
||||
cmd-dumpentities-desc = Dump entity list.
|
||||
cmd-dumpentities-help = Dumps entity list of UIDs and prototype.
|
||||
|
||||
@@ -580,3 +577,5 @@ cmd-localization_set_culture-desc = Set DefaultCulture for the client Localizati
|
||||
cmd-localization_set_culture-help = Usage: localization_set_culture <cultureName>
|
||||
cmd-localization_set_culture-culture-name = <cultureName>
|
||||
cmd-localization_set_culture-changed = Localization changed to { $code } ({ $nativeName } / { $englishName })
|
||||
|
||||
cmd-addmap-hint-2 = runMapInit [true / false]
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
## EntitySpawnWindow
|
||||
|
||||
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-override-menu-tooltip = Override placement
|
||||
|
||||
@@ -22,3 +20,5 @@ output-panel-scroll-down-button-text = Scroll Down
|
||||
## Common Used
|
||||
|
||||
window-erase-button-text = Erase Mode
|
||||
window-search-bar-placeholder = Search
|
||||
window-clear-button = Clear
|
||||
|
||||
@@ -8,3 +8,18 @@ dev-window-tab-textures-info = Width: { $width } Height: { $height }
|
||||
PixelType: { $pixelType } sRGB: { $srgb }
|
||||
Name: { $name }
|
||||
Est. memory usage: { $bytes }
|
||||
|
||||
## "Render Targets" dev window tab
|
||||
dev-window-tab-render-targets-title = Render Targets
|
||||
dev-window-tab-render-targets-reload = Reload
|
||||
dev-window-tab-render-targets-filter = Filter
|
||||
dev-window-tab-render-targets-column-id = ID
|
||||
dev-window-tab-render-targets-column-name = Name
|
||||
dev-window-tab-render-targets-column-size = Size
|
||||
dev-window-tab-render-targets-column-type = Type
|
||||
dev-window-tab-render-targets-column-vram = VRAM
|
||||
dev-window-tab-render-targets-column-thumbnail = Thumbnail
|
||||
|
||||
dev-window-tab-render-targets-value-null = null
|
||||
dev-window-tab-render-targets-value-not-available = Not available
|
||||
dev-window-tab-render-targets-summary = Total VRAM: { $vram }
|
||||
|
||||
@@ -2,6 +2,7 @@ input-key-Escape = Escape
|
||||
input-key-Control = Control
|
||||
input-key-Shift = Shift
|
||||
input-key-Alt = Alt
|
||||
input-key-Alt-mac = ⌥
|
||||
input-key-Menu = Menu
|
||||
input-key-F1 = F1
|
||||
input-key-F2 = F2
|
||||
@@ -70,8 +71,8 @@ input-key-MouseButton9 = Mouse 9
|
||||
|
||||
input-key-LSystem-win = Left Win
|
||||
input-key-RSystem-win = Right Win
|
||||
input-key-LSystem-mac = Left Cmd
|
||||
input-key-RSystem-mac = Right Cmd
|
||||
input-key-LSystem-mac = Left ⌘
|
||||
input-key-RSystem-mac = Right ⌘
|
||||
input-key-LSystem-linux = Left Meta
|
||||
input-key-RSystem-linux = Right Meta
|
||||
|
||||
|
||||
@@ -25,3 +25,9 @@ vv-sound-reference-distance = Reference Distance
|
||||
vv-sound-loop = Loop
|
||||
vv-sound-play-offset = Play Offset (s)
|
||||
vv-sound-variation = Pitch variation
|
||||
|
||||
|
||||
## ProtoId
|
||||
vv-protoid-id-placeholder = Prototype ID
|
||||
vv-protoid-select-button-label = Select
|
||||
vv-protoid-addwindow-title = Set Prototype
|
||||
|
||||
@@ -14,6 +14,8 @@ uniform highp vec2 lightCenter;
|
||||
uniform highp float lightRange;
|
||||
uniform highp float lightPower;
|
||||
uniform highp float lightSoftness;
|
||||
uniform highp float lightFalloff;
|
||||
uniform highp float lightCurveFactor;
|
||||
uniform highp float lightIndex;
|
||||
uniform sampler2D shadowMap;
|
||||
|
||||
@@ -47,8 +49,15 @@ void fragment()
|
||||
discard;
|
||||
}
|
||||
|
||||
highp float dist = dot(diff, diff) + LIGHTING_HEIGHT;
|
||||
highp float val = clamp((1.0 - clamp(sqrt(dist) / lightRange, 0.0, 1.0)) * (1.0 / (sqrt(dist + 1.0))), 0.0, 1.0);
|
||||
// this implementation of light attenuation primarily adapted from
|
||||
// https://lisyarus.github.io/blog/posts/point-light-attenuation.html
|
||||
highp float sqr_dist = dot(diff, diff) + LIGHTING_HEIGHT;
|
||||
|
||||
highp float s = clamp(sqrt(sqr_dist) / lightRange, 0.0, 1.0);
|
||||
highp float s2 = s * s;
|
||||
// controls curve by lerping between two variants (inverse-shape and inversequadratic-shape)
|
||||
highp float curveFactor = mix(s, s2, clamp(lightCurveFactor, 0.0, 1.0));
|
||||
highp float val = clamp(((1.0 - s2) * (1.0 - s2)) / (1.0 + lightFalloff * curveFactor), 0.0, 1.0);
|
||||
|
||||
val *= lightPower;
|
||||
val *= mask;
|
||||
|
||||
110
Robust.Analyzers.Tests/AfterAutoHandleStateAnalyzerTest.cs
Normal file
110
Robust.Analyzers.Tests/AfterAutoHandleStateAnalyzerTest.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.AfterAutoHandleStateAnalyzer,
|
||||
Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture, TestOf(typeof(AfterAutoHandleStateAnalyzer))]
|
||||
public sealed class AfterAutoHandleStateAnalyzerTest
|
||||
{
|
||||
private const string SubscribeEventDef = """
|
||||
using System;
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public readonly struct EntityUid;
|
||||
|
||||
public abstract class EntitySystem
|
||||
{
|
||||
public void SubscribeLocalEvent<T, TEvent>() where TEvent : notnull { }
|
||||
}
|
||||
|
||||
public interface IComponent;
|
||||
public interface IComponentState;
|
||||
""";
|
||||
|
||||
// A rare case for block-scoped namespace, I thought. Then I realized this
|
||||
// only needed the one type definition.
|
||||
private const string OtherTypeDefs = """
|
||||
using System;
|
||||
|
||||
namespace JetBrains.Annotations
|
||||
{
|
||||
public sealed class BaseTypeRequiredAttribute(Type baseType) : Attribute;
|
||||
}
|
||||
""";
|
||||
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<AfterAutoHandleStateAnalyzer, DefaultVerifier>
|
||||
{
|
||||
TestState = { Sources = { code } }
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(test.TestState,
|
||||
"Robust.Shared.Analyzers.ComponentNetworkGeneratorAuxiliary.cs",
|
||||
"Robust.Shared.GameObjects.EventBusAttributes.cs");
|
||||
|
||||
test.TestState.Sources.Add(("EntitySystem.Subscriptions.cs", SubscribeEventDef));
|
||||
test.TestState.Sources.Add(("Types.cs", OtherTypeDefs));
|
||||
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
[AutoGenerateComponentState(true)]
|
||||
public sealed class AutoGenTrue;
|
||||
[AutoGenerateComponentState(true, true)]
|
||||
public sealed class AutoGenTrueTrue;
|
||||
|
||||
public sealed class NotAutoGen;
|
||||
[AutoGenerateComponentState]
|
||||
public sealed class AutoGenNoArgs;
|
||||
[AutoGenerateComponentState(false)]
|
||||
public sealed class AutoGenFalse;
|
||||
|
||||
public sealed class Foo : EntitySystem
|
||||
{
|
||||
public void Good()
|
||||
{
|
||||
// Subscribing to other events works
|
||||
SubscribeLocalEvent<AutoGenNoArgs, object>();
|
||||
// First arg true allows subscribing
|
||||
SubscribeLocalEvent<AutoGenTrue, AfterAutoHandleStateEvent>();
|
||||
SubscribeLocalEvent<AutoGenTrueTrue, AfterAutoHandleStateEvent>();
|
||||
}
|
||||
|
||||
public void Bad()
|
||||
{
|
||||
// Can't subscribe if AutoGenerateComponentState isn't even present
|
||||
SubscribeLocalEvent<NotAutoGen, AfterAutoHandleStateEvent>();
|
||||
|
||||
// Can't subscribe if first arg is not specified/false
|
||||
SubscribeLocalEvent<AutoGenNoArgs, AfterAutoHandleStateEvent>();
|
||||
SubscribeLocalEvent<AutoGenFalse, AfterAutoHandleStateEvent>();
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(29,9): error RA0040: Tried to subscribe to AfterAutoHandleStateEvent for 'NotAutoGen' which doesn't have an AutoGenerateComponentState attribute
|
||||
VerifyCS.Diagnostic(AfterAutoHandleStateAnalyzer.MissingAttribute).WithSpan(29, 9, 29, 69).WithArguments("NotAutoGen"),
|
||||
// /0/Test0.cs(32,9): error RA0041: Tried to subscribe to AfterAutoHandleStateEvent for 'AutoGenNoArgs' which doesn't have raiseAfterAutoHandleState set
|
||||
VerifyCS.Diagnostic(AfterAutoHandleStateAnalyzer.MissingAttributeParam).WithSpan(32, 9, 32, 72).WithArguments("AutoGenNoArgs"),
|
||||
// /0/Test0.cs(33,9): error RA0041: Tried to subscribe to AfterAutoHandleStateEvent for 'AutoGenFalse' which doesn't have raiseAfterAutoHandleState set
|
||||
VerifyCS.Diagnostic(AfterAutoHandleStateAnalyzer.MissingAttributeParam).WithSpan(33, 9, 33, 71).WithArguments("AutoGenFalse")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
extern alias SerializationGenerator;
|
||||
extern alias SerializationGenerator;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.CodeAnalysis;
|
||||
@@ -126,6 +126,48 @@ public sealed class ComponentPauseGeneratorTest
|
||||
""");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDictionary()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
[AutoPausedField]
|
||||
public Dictionary<string, TimeSpan> Foo;
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoDiagnostics(result);
|
||||
ExpectSource(
|
||||
result,
|
||||
"""
|
||||
// <auto-generated />
|
||||
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
|
||||
}
|
||||
|
||||
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
foreach (var key in component.Foo.Keys)
|
||||
component.Foo[key] += args.PausedTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAutoState()
|
||||
{
|
||||
|
||||
@@ -203,6 +203,8 @@ public sealed class DataDefinitionAnalyzerTest
|
||||
|
||||
[NotYamlSerializable]
|
||||
public sealed class NotSerializableClass { }
|
||||
[NotYamlSerializable]
|
||||
public readonly struct NotSerializableStruct { }
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class Foo
|
||||
@@ -213,6 +215,21 @@ public sealed class DataDefinitionAnalyzerTest
|
||||
[DataField]
|
||||
public NotSerializableClass BadProperty { get; set; }
|
||||
|
||||
[DataField]
|
||||
public NotSerializableClass? BadNullableField;
|
||||
|
||||
[DataField]
|
||||
public NotSerializableStruct BadStructField;
|
||||
|
||||
[DataField]
|
||||
public NotSerializableStruct BadStructProperty { get; set; }
|
||||
|
||||
[DataField]
|
||||
public NotSerializableStruct? BadNullableStructField;
|
||||
|
||||
[DataField]
|
||||
public NotSerializableStruct? BadNullableStructProperty { get; set; }
|
||||
|
||||
public NotSerializableClass GoodField; // Not a DataField, not a problem
|
||||
|
||||
public NotSerializableClass GoodProperty { get; set; } // Not a DataField, not a problem
|
||||
@@ -220,10 +237,20 @@ public sealed class DataDefinitionAnalyzerTest
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(10,12): error RA0033: Data field BadField in data definition Foo is type NotSerializableClass, which is not YAML serializable
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(10, 12, 10, 32).WithArguments("BadField", "Foo", "NotSerializableClass"),
|
||||
// /0/Test0.cs(13,12): error RA0033: Data field BadProperty in data definition Foo is type NotSerializableClass, which is not YAML serializable
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(13, 12, 13, 32).WithArguments("BadProperty", "Foo", "NotSerializableClass")
|
||||
// /0/Test0.cs(12,12): error RA0033: Data field BadField in data definition Foo is type NotSerializableClass, which is not YAML serializable
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(12, 12, 12, 32).WithArguments("BadField", "Foo", "NotSerializableClass"),
|
||||
// /0/Test0.cs(15,12): error RA0033: Data field BadProperty in data definition Foo is type NotSerializableClass, which is not YAML serializable
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(15, 12, 15, 32).WithArguments("BadProperty", "Foo", "NotSerializableClass"),
|
||||
// /0/Test0.cs(18,12): error RA0036: Data field BadNullableField in data definition Foo is type NotSerializableClass, which is not YAML serializable
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(18, 12, 18, 33).WithArguments("BadNullableField", "Foo", "NotSerializableClass"),
|
||||
// /0/Test0.cs(21,12): error RA0036: Data field BadStructField in data definition Foo is type NotSerializableStruct, which is not YAML serializable
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(21, 12, 21, 33).WithArguments("BadStructField", "Foo", "NotSerializableStruct"),
|
||||
// /0/Test0.cs(24,12): error RA0036: Data field BadStructProperty in data definition Foo is type NotSerializableStruct, which is not YAML serializable
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(24, 12, 24, 33).WithArguments("BadStructProperty", "Foo", "NotSerializableStruct"),
|
||||
// /0/Test0.cs(27,12): error RA0036: Data field BadNullableStructField in data definition Foo is type NotSerializableStruct, which is not YAML serializable
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(27, 12, 27, 34).WithArguments("BadNullableStructField", "Foo", "NotSerializableStruct"),
|
||||
// /0/Test0.cs(30,12): error RA0036: Data field BadNullableStructProperty in data definition Foo is type NotSerializableStruct, which is not YAML serializable
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(30, 12, 30, 34).WithArguments("BadNullableStructProperty", "Foo", "NotSerializableStruct")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
64
Robust.Analyzers.Tests/PrototypeInstantiationAnalyzerTest.cs
Normal file
64
Robust.Analyzers.Tests/PrototypeInstantiationAnalyzerTest.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PrototypeInstantiationAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
[TestOf(typeof(PrototypeInstantiationAnalyzer))]
|
||||
public sealed class PrototypeInstantiationAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new RTAnalyzerTest<PrototypeInstantiationAnalyzer>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.Prototypes.Attributes.cs",
|
||||
"Robust.Shared.Prototypes.IPrototype.cs",
|
||||
"Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute.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.Serialization;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
[Prototype]
|
||||
public sealed class FooPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
}
|
||||
|
||||
public static class Bad
|
||||
{
|
||||
public static FooPrototype Real()
|
||||
{
|
||||
return new FooPrototype();
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(15,16): warning RA0039: Do not instantiate prototypes directly. Prototypes should always be instantiated by the prototype manager.
|
||||
VerifyCS.Diagnostic().WithSpan(15, 16, 15, 34));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PrototypeNetSerializableAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
[TestOf(typeof(PrototypeNetSerializableAnalyzer))]
|
||||
public sealed class PrototypeNetSerializableAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new RTAnalyzerTest<PrototypeNetSerializableAnalyzer>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.Serialization.NetSerializableAttribute.cs",
|
||||
"Robust.Shared.Prototypes.Attributes.cs",
|
||||
"Robust.Shared.Prototypes.IPrototype.cs",
|
||||
"Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute.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 System;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
[Prototype]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class FooPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(7,21): warning RA0037: Type FooPrototype is a prototype and marked as [NetSerializable]. Prototypes should not be directly sent over the network, send their IDs instead.
|
||||
VerifyCS.Diagnostic(PrototypeNetSerializableAnalyzer.RuleNetSerializable).WithSpan(7, 21, 7, 33).WithArguments("FooPrototype"),
|
||||
// /0/Test0.cs(7,21): warning RA0038: Type FooPrototype is a prototype and marked as [Serializable]. Prototypes should not be directly sent over the network, send their IDs instead.
|
||||
VerifyCS.Diagnostic(PrototypeNetSerializableAnalyzer.RuleSerializable).WithSpan(7, 21, 7, 33).WithArguments("FooPrototype"));
|
||||
}
|
||||
}
|
||||
17
Robust.Analyzers.Tests/RTAnalyzerTest.cs
Normal file
17
Robust.Analyzers.Tests/RTAnalyzerTest.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
public sealed class RTAnalyzerTest<TAnalyzer> : CSharpAnalyzerTest<TAnalyzer, DefaultVerifier>
|
||||
where TAnalyzer : DiagnosticAnalyzer, new()
|
||||
{
|
||||
protected override ParseOptions CreateParseOptions()
|
||||
{
|
||||
var baseOptions = (CSharpParseOptions) base.CreateParseOptions();
|
||||
return baseOptions.WithPreprocessorSymbols("ROBUST_ANALYZERS_TEST");
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
<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\ComponentNetworkGeneratorAuxiliary.cs" LogicalName="Robust.Shared.Analyzers.ComponentNetworkGeneratorAuxiliary.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" />
|
||||
@@ -17,6 +18,10 @@
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\ObsoleteInheritanceAttribute.cs" LogicalName="Robust.Shared.Analyzers.ObsoleteInheritanceAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\GameObjects\EventBusAttributes.cs" LogicalName="Robust.Shared.GameObjects.EventBusAttributes.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Serialization\NetSerializableAttribute.cs" LogicalName="Robust.Shared.Serialization.NetSerializableAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Prototypes\Attributes.cs" LogicalName="Robust.Shared.Prototypes.Attributes.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Prototypes\IPrototype.cs" LogicalName="Robust.Shared.Prototypes.IPrototype.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Serialization\Manager\Attributes\DataFieldAttribute.cs" LogicalName="Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
85
Robust.Analyzers/AfterAutoHandleStateAnalyzer.cs
Normal file
85
Robust.Analyzers/AfterAutoHandleStateAnalyzer.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
#nullable enable
|
||||
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 AfterAutoHandleStateAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string AfterAutoHandleStateEventName = "AfterAutoHandleStateEvent";
|
||||
private const string AutoGenStateAttribute = "Robust.Shared.Analyzers.AutoGenerateComponentStateAttribute";
|
||||
private const string SubscribeLocalEventName = "SubscribeLocalEvent";
|
||||
|
||||
public static readonly DiagnosticDescriptor MissingAttribute = new(
|
||||
Diagnostics.IdAutoGenStateAttributeMissing,
|
||||
"Unreachable AfterAutoHandleState subscription",
|
||||
"Tried to subscribe to AfterAutoHandleStateEvent for '{0}' which doesn't have an "
|
||||
+ "AutoGenerateComponentState attribute",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
// Does this even show up anywhere in Rider? >:(
|
||||
"You must mark your component with '[AutoGenerateComponentState(true)]' to subscribe to this event."
|
||||
);
|
||||
|
||||
public static readonly DiagnosticDescriptor MissingAttributeParam = new(
|
||||
Diagnostics.IdAutoGenStateParamMissing,
|
||||
"Unreachable AfterAutoHandleState subscription",
|
||||
"Tried to subscribe to AfterAutoHandleStateEvent for '{0}' which doesn't have "
|
||||
+ "raiseAfterAutoHandleState set",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
"The AutoGenerateComponentState attribute must be passed 'true' in order to subscribe to this event."
|
||||
);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
|
||||
[MissingAttribute, MissingAttributeParam];
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
// This is more to stop user error rather than code generation error
|
||||
// (Plus this shouldn't affect code gen anyway)
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.EnableConcurrentExecution();
|
||||
|
||||
context.RegisterCompilationStartAction(compilationContext =>
|
||||
{
|
||||
var autoGenStateAttribute = compilationContext.Compilation.GetTypeByMetadataName(AutoGenStateAttribute);
|
||||
// No attribute, no analyzer.
|
||||
if (autoGenStateAttribute is null)
|
||||
return;
|
||||
|
||||
compilationContext.RegisterOperationAction(
|
||||
analysisContext => CheckEventSubscription(analysisContext, autoGenStateAttribute),
|
||||
OperationKind.Invocation);
|
||||
});
|
||||
}
|
||||
|
||||
private static void CheckEventSubscription(OperationAnalysisContext context, ITypeSymbol autoGenStateAttribute)
|
||||
{
|
||||
if (context.Operation is not IInvocationOperation operation)
|
||||
return;
|
||||
|
||||
// Check the method has the right name and has the right type args
|
||||
if (operation.TargetMethod is not
|
||||
{ Name: SubscribeLocalEventName, TypeArguments: [var component, { Name: AfterAutoHandleStateEventName }] })
|
||||
return;
|
||||
|
||||
// Search the component's attributes for something matching autoGenStateAttribute
|
||||
AttributeHelper.HasAttribute(component, autoGenStateAttribute, out var autoGenAttribute);
|
||||
|
||||
// First argument is raiseAfterAutoHandleState—note it shouldn't ever
|
||||
// be null, since it has a default, but eh.
|
||||
if (autoGenAttribute?.ConstructorArguments[0].Value is true)
|
||||
return;
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(autoGenAttribute is null ? MissingAttribute : MissingAttributeParam,
|
||||
operation.Syntax.GetLocation(),
|
||||
component.Name));
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
}, SymbolKind.NamedType);
|
||||
}
|
||||
|
||||
private void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
|
||||
private static void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not TypeDeclarationSyntax declaration)
|
||||
return;
|
||||
@@ -147,7 +147,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
private void AnalyzeDataField(SyntaxNodeAnalysisContext context)
|
||||
private static void AnalyzeDataField(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not FieldDeclarationSyntax field)
|
||||
return;
|
||||
@@ -186,6 +186,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
if (context.SemanticModel.GetSymbolInfo(field.Declaration.Type).Symbol is not ITypeSymbol fieldTypeSymbol)
|
||||
continue;
|
||||
|
||||
fieldTypeSymbol = TypeSymbolHelper.GetNullableUnderlyingTypeOrSelf(fieldTypeSymbol);
|
||||
|
||||
if (IsNotYamlSerializable(fieldSymbol, fieldTypeSymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldYamlSerializableRule,
|
||||
@@ -198,7 +200,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
private void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
|
||||
private static void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not PropertyDeclarationSyntax property)
|
||||
return;
|
||||
@@ -239,6 +241,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
if (context.SemanticModel.GetSymbolInfo(property.Type).Symbol is not ITypeSymbol propertyTypeSymbol)
|
||||
return;
|
||||
|
||||
propertyTypeSymbol = TypeSymbolHelper.GetNullableUnderlyingTypeOrSelf(propertyTypeSymbol);
|
||||
|
||||
if (IsNotYamlSerializable(propertySymbol, propertyTypeSymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldYamlSerializableRule,
|
||||
|
||||
48
Robust.Analyzers/PrototypeInstantiationAnalyzer.cs
Normal file
48
Robust.Analyzers/PrototypeInstantiationAnalyzer.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
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 PrototypeInstantiationAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string PrototypeInterfaceType = "Robust.Shared.Prototypes.IPrototype";
|
||||
|
||||
public static readonly DiagnosticDescriptor Rule = new(
|
||||
Diagnostics.IdPrototypeInstantiation,
|
||||
"Do not instantiate prototypes directly",
|
||||
"Do not instantiate prototypes directly. Prototypes should always be instantiated by the prototype manager.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.EnableConcurrentExecution();
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.RegisterCompilationStartAction(static ctx =>
|
||||
{
|
||||
var prototypeInterface = ctx.Compilation.GetTypeByMetadataName(PrototypeInterfaceType);
|
||||
if (prototypeInterface == null)
|
||||
return;
|
||||
|
||||
ctx.RegisterOperationAction(symContext => Check(prototypeInterface, symContext), OperationKind.ObjectCreation);
|
||||
});
|
||||
}
|
||||
|
||||
private static void Check(INamedTypeSymbol prototypeInterface, OperationAnalysisContext ctx)
|
||||
{
|
||||
if (ctx.Operation is not IObjectCreationOperation { Type: { } resultType } creationOp)
|
||||
return;
|
||||
|
||||
if (!TypeSymbolHelper.ImplementsInterface(resultType, prototypeInterface))
|
||||
return;
|
||||
|
||||
ctx.ReportDiagnostic(Diagnostic.Create(Rule, creationOp.Syntax.GetLocation()));
|
||||
}
|
||||
}
|
||||
76
Robust.Analyzers/PrototypeNetSerializableAnalyzer.cs
Normal file
76
Robust.Analyzers/PrototypeNetSerializableAnalyzer.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class PrototypeNetSerializableAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string PrototypeInterfaceType = "Robust.Shared.Prototypes.IPrototype";
|
||||
private const string NetSerializableAttributeType = "Robust.Shared.Serialization.NetSerializableAttribute";
|
||||
|
||||
public static readonly DiagnosticDescriptor RuleNetSerializable = new(
|
||||
Diagnostics.IdPrototypeNetSerializable,
|
||||
"Prototypes should not be [NetSerializable]",
|
||||
"Type {0} is a prototype and marked as [NetSerializable]. Prototypes should not be directly sent over the network, send their IDs instead.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
|
||||
public static readonly DiagnosticDescriptor RuleSerializable = new(
|
||||
Diagnostics.IdPrototypeSerializable,
|
||||
"Prototypes should not be [Serializable]",
|
||||
"Type {0} is a prototype and marked as [Serializable]. Prototypes should not be directly sent over the network, send their IDs instead.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [
|
||||
RuleNetSerializable,
|
||||
RuleSerializable
|
||||
];
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.EnableConcurrentExecution();
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
|
||||
context.RegisterCompilationStartAction(static ctx =>
|
||||
{
|
||||
var prototypeInterface = ctx.Compilation.GetTypeByMetadataName(PrototypeInterfaceType);
|
||||
var netSerializableAttribute = ctx.Compilation.GetTypeByMetadataName(NetSerializableAttributeType);
|
||||
|
||||
if (prototypeInterface == null || netSerializableAttribute == null)
|
||||
return;
|
||||
|
||||
ctx.RegisterSymbolAction(symbolContext => CheckClass(prototypeInterface, netSerializableAttribute, symbolContext), SymbolKind.NamedType);
|
||||
});
|
||||
}
|
||||
|
||||
private static void CheckClass(
|
||||
INamedTypeSymbol prototypeInterface,
|
||||
INamedTypeSymbol netSerializableAttribute,
|
||||
SymbolAnalysisContext symbolContext)
|
||||
{
|
||||
if (symbolContext.Symbol is not INamedTypeSymbol symbol)
|
||||
return;
|
||||
|
||||
if (!TypeSymbolHelper.ImplementsInterface(symbol, prototypeInterface))
|
||||
return;
|
||||
|
||||
if (AttributeHelper.HasAttribute(symbol, netSerializableAttribute, out _))
|
||||
{
|
||||
symbolContext.ReportDiagnostic(
|
||||
Diagnostic.Create(RuleNetSerializable, symbol.Locations[0], symbol.ToDisplayString()));
|
||||
}
|
||||
|
||||
if (symbol.IsSerializable)
|
||||
{
|
||||
symbolContext.ReportDiagnostic(
|
||||
Diagnostic.Create(RuleSerializable, symbol.Locations[0], symbol.ToDisplayString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,9 +162,10 @@ namespace Robust.Client.WebView.Cef
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOpen => _data != null;
|
||||
public bool IsLoading => _data?.Browser.IsLoading ?? false;
|
||||
|
||||
public void EnteredTree()
|
||||
public void StartBrowser()
|
||||
{
|
||||
DebugTools.AssertNull(_data);
|
||||
|
||||
@@ -195,7 +196,7 @@ namespace Robust.Client.WebView.Cef
|
||||
_data = new LiveData(texture, client, browser, renderer);
|
||||
}
|
||||
|
||||
public void ExitedTree()
|
||||
public void CloseBrowser()
|
||||
{
|
||||
DebugTools.AssertNotNull(_data);
|
||||
|
||||
|
||||
51
Robust.Client.WebView/Cef/WebViewManagerCef.Lock.cs
Normal file
51
Robust.Client.WebView/Cef/WebViewManagerCef.Lock.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Robust.Client.Utility;
|
||||
|
||||
namespace Robust.Client.WebView.Cef;
|
||||
|
||||
internal sealed partial class WebViewManagerCef
|
||||
{
|
||||
private const string BaseCacheName = "cef_cache";
|
||||
private const string LockFileName = "robust.lock";
|
||||
private FileStream? _lockFileStream;
|
||||
private const int MaxAttempts = 15; // This probably shouldn't be a cvar because the only reason you'd need it change for legit just botting the game.
|
||||
|
||||
private string FindAndLockCacheDirectory()
|
||||
{
|
||||
var rootDir = Path.Combine(UserDataDir.GetRootUserDataDir(_gameController), BaseCacheName);
|
||||
|
||||
for (var i = 0; i < MaxAttempts; i++)
|
||||
{
|
||||
var cacheDirPath = Path.Combine(rootDir, i.ToString());
|
||||
|
||||
if (TryLockCacheDir(i, cacheDirPath))
|
||||
return cacheDirPath;
|
||||
}
|
||||
|
||||
throw new Exception("Unable to locate available CEF cache directory!");
|
||||
}
|
||||
|
||||
private bool TryLockCacheDir(int attempt, string path)
|
||||
{
|
||||
_sawmill.Verbose($"Trying to lock cache directory {attempt}");
|
||||
|
||||
// Does not fail if directory already exists.
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
var lockFilePath = Path.Combine(path, LockFileName);
|
||||
|
||||
try
|
||||
{
|
||||
var file = File.Open(lockFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
|
||||
_lockFileStream = file;
|
||||
_sawmill.Debug($"Successfully locked CEF cache directory {attempt}");
|
||||
return true;
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_sawmill.Error($"Failed to lock cache directory {attempt}: {ex}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,12 +61,9 @@ namespace Robust.Client.WebView.Cef
|
||||
if (cefResourcesPath == null)
|
||||
throw new InvalidOperationException("Unable to locate cef_resources directory!");
|
||||
|
||||
var cachePath = "";
|
||||
if (_resourceManager.UserData is WritableDirProvider userData)
|
||||
{
|
||||
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
cachePath = Path.Combine(rootDir, "cef_cache", "0");
|
||||
}
|
||||
var remoteDebugPort = _cfg.GetCVar(WCVars.WebRemoteDebugPort);
|
||||
|
||||
var cachePath = FindAndLockCacheDirectory();
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
@@ -76,7 +73,7 @@ namespace Robust.Client.WebView.Cef
|
||||
BrowserSubprocessPath = subProcessPath,
|
||||
LocalesDirPath = Path.Combine(cefResourcesPath, "locales"),
|
||||
ResourcesDirPath = cefResourcesPath,
|
||||
RemoteDebuggingPort = 9222,
|
||||
RemoteDebuggingPort = remoteDebugPort,
|
||||
CookieableSchemesList = "usr,res",
|
||||
CachePath = cachePath,
|
||||
};
|
||||
|
||||
@@ -81,11 +81,13 @@ namespace Robust.Client.WebView.Headless
|
||||
|
||||
private sealed class WebViewControlImplDummy : DummyBase, IWebViewControlImpl
|
||||
{
|
||||
public void EnteredTree()
|
||||
public bool IsOpen => false;
|
||||
|
||||
public void StartBrowser()
|
||||
{
|
||||
}
|
||||
|
||||
public void ExitedTree()
|
||||
public void CloseBrowser()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@ namespace Robust.Client.WebView
|
||||
/// </summary>
|
||||
internal interface IWebViewControlImpl : IWebViewControl
|
||||
{
|
||||
void EnteredTree();
|
||||
void ExitedTree();
|
||||
public bool IsOpen { get; }
|
||||
|
||||
void StartBrowser();
|
||||
void CloseBrowser();
|
||||
void MouseMove(GUIMouseMoveEventArgs args);
|
||||
void MouseExited();
|
||||
void MouseWheel(GUIMouseWheelEventArgs args);
|
||||
|
||||
@@ -26,4 +26,16 @@ public static class WCVars
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> WebHeadless =
|
||||
CVarDef.Create("web.headless", false, CVar.CLIENTONLY);
|
||||
|
||||
#if TOOLS
|
||||
private const int DefaultRemoteDebugPort = 9222;
|
||||
#else
|
||||
private const int DefaultRemoteDebugPort = 0;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// If not 0, the port number used for Chromium's remote debugging.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> WebRemoteDebugPort =
|
||||
CVarDef.Create("web.remote_debug_port", DefaultRemoteDebugPort, CVar.CLIENTONLY);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Robust.Client.WebView
|
||||
[Dependency] private readonly IWebViewManagerInternal _webViewManager = default!;
|
||||
|
||||
private readonly IWebViewControlImpl _controlImpl;
|
||||
private bool _alwaysActive;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Url
|
||||
@@ -22,6 +23,21 @@ namespace Robust.Client.WebView
|
||||
set => _controlImpl.Url = value;
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool AlwaysActive
|
||||
{
|
||||
get => _alwaysActive;
|
||||
set
|
||||
{
|
||||
_alwaysActive = value;
|
||||
|
||||
if (_alwaysActive && !_controlImpl.IsOpen)
|
||||
_controlImpl.StartBrowser();
|
||||
else if (!_alwaysActive && _controlImpl.IsOpen && !IsInsideTree)
|
||||
_controlImpl.CloseBrowser();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables] public bool IsLoading => _controlImpl.IsLoading;
|
||||
|
||||
public WebViewControl()
|
||||
@@ -39,14 +55,16 @@ namespace Robust.Client.WebView
|
||||
{
|
||||
base.EnteredTree();
|
||||
|
||||
_controlImpl.EnteredTree();
|
||||
if (!_controlImpl.IsOpen)
|
||||
_controlImpl.StartBrowser();
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
_controlImpl.ExitedTree();
|
||||
if (!_alwaysActive)
|
||||
_controlImpl.CloseBrowser();
|
||||
}
|
||||
|
||||
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
|
||||
|
||||
@@ -3,8 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Maths;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Animations
|
||||
{
|
||||
@@ -56,8 +54,14 @@ namespace Robust.Client.Animations
|
||||
}
|
||||
else
|
||||
{
|
||||
var next = KeyFrames[nextKeyFrame];
|
||||
|
||||
// Get us a scale 0 -> 1 here.
|
||||
var t = playingTime / KeyFrames[nextKeyFrame].KeyTime;
|
||||
var t = playingTime / next.KeyTime;
|
||||
|
||||
// Apply easing to time parameter, if one was specified
|
||||
if (next.Easing != null)
|
||||
t = next.Easing(t);
|
||||
|
||||
switch (InterpolationMode)
|
||||
{
|
||||
@@ -122,9 +126,9 @@ namespace Robust.Client.Animations
|
||||
case Vector2 vector2:
|
||||
return Vector2Helpers.InterpolateCubic((Vector2) preA, vector2, (Vector2) b, (Vector2) postB, t);
|
||||
case Vector3 vector3:
|
||||
return Vector3.InterpolateCubic((Vector3) preA, vector3, (Vector3) b, (Vector3) postB, t);
|
||||
return VectorHelpers.InterpolateCubic((Vector3) preA, vector3, (Vector3) b, (Vector3) postB, t);
|
||||
case Vector4 vector4:
|
||||
return Vector4.InterpolateCubic((Vector4) preA, vector4, (Vector4) b, (Vector4) postB, t);
|
||||
return VectorHelpers.InterpolateCubic((Vector4) preA, vector4, (Vector4) b, (Vector4) postB, t);
|
||||
case float f:
|
||||
return MathHelper.InterpolateCubic((float) preA, f, (float) b, (float) postB, t);
|
||||
case double d:
|
||||
@@ -149,10 +153,20 @@ namespace Robust.Client.Animations
|
||||
/// </summary>
|
||||
public readonly float KeyTime;
|
||||
|
||||
public KeyFrame(object value, float keyTime)
|
||||
/// <summary>
|
||||
/// An easing function to apply when interpolating to this keyframe's value.
|
||||
/// Modifies the time parameter (0..1) of the interpolation between the previous keyframe and this one.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see cref="Easings"/> for examples of easing functions, or provide your own.
|
||||
/// </remarks>
|
||||
public readonly Func<float, float>? Easing;
|
||||
|
||||
public KeyFrame(object value, float keyTime, Func<float, float>? easing = null)
|
||||
{
|
||||
Value = value;
|
||||
KeyTime = keyTime;
|
||||
Easing = easing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Audio.Sources;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
@@ -145,7 +144,7 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
private static void RemoveEfx((int sourceHandle, int filterHandle) handles)
|
||||
{
|
||||
if (handles.filterHandle != 0)
|
||||
EFX.DeleteFilter(handles.filterHandle);
|
||||
ALC.EFX.DeleteFilter(handles.filterHandle);
|
||||
}
|
||||
|
||||
private void _checkAlcError(ALDevice device,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Audio.Effects;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
@@ -372,13 +372,13 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var parentUid = xform.ParentUid;
|
||||
Vector2 worldPos;
|
||||
component.Volume = component.Params.Volume;
|
||||
|
||||
// Handle grid audio differently by using grid position.
|
||||
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
|
||||
{
|
||||
var parentUid = xform.ParentUid;
|
||||
worldPos = _maps.GetGridPosition(parentUid);
|
||||
}
|
||||
else
|
||||
@@ -412,7 +412,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
else
|
||||
{
|
||||
var occlusion = GetOcclusion(listener, delta, distance, entity);
|
||||
var occlusion = GetOcclusion(listener, delta, distance, parentUid);
|
||||
component.Occlusion = occlusion;
|
||||
}
|
||||
|
||||
@@ -420,11 +420,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
component.Position = worldPos;
|
||||
|
||||
// Make race cars go NYYEEOOOOOMMMMM
|
||||
if (_physicsQuery.TryGetComponent(entity, out var physicsComp))
|
||||
if (_physicsQuery.TryGetComponent(parentUid, out var physicsComp))
|
||||
{
|
||||
// This actually gets the tracked entity's xform & iterates up though the parents for the second time. Bit
|
||||
// inefficient.
|
||||
var velocity = _physics.GetMapLinearVelocity(entity, physicsComp, xform);
|
||||
var velocity = _physics.GetMapLinearVelocity(parentUid, physicsComp);
|
||||
component.Velocity = velocity;
|
||||
}
|
||||
}
|
||||
@@ -589,6 +589,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
var playing = CreateAndStartPlayingStream(audioParams, specifier, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, new EntityCoordinates(entity, Vector2.Zero));
|
||||
|
||||
// Since we're playing the sound immediately in the middle of a tick, we need to force ProcessStream -now-
|
||||
// to set occlusion/position/velocity etc
|
||||
// otherwise predicted positional sounds will sound very incorrect in several possible ways (e#5802, e#6175) until the next tick
|
||||
ProcessStream(playing.Entity, playing.Component, Transform(playing.Entity), GetListenerCoordinates());
|
||||
|
||||
return playing;
|
||||
}
|
||||
|
||||
@@ -632,6 +637,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
var playing = CreateAndStartPlayingStream(audioParams, specifier, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, coordinates);
|
||||
|
||||
// see PlayEntity for why this is necessary
|
||||
ProcessStream(playing.Entity, playing.Component, Transform(playing.Entity), GetListenerCoordinates());
|
||||
|
||||
return playing;
|
||||
}
|
||||
|
||||
@@ -714,8 +723,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
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.
|
||||
ApplyAudioParams(comp.Params, comp);
|
||||
source.StartPlaying();
|
||||
return (entity, comp);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -15,16 +16,16 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
|
||||
public AudioEffect(IAudioInternal manager)
|
||||
{
|
||||
Handle = EFX.GenEffect();
|
||||
Handle = ALC.EFX.GenEffect();
|
||||
_master = manager;
|
||||
EFX.Effect(Handle, EffectInteger.EffectType, (int) EffectType.EaxReverb);
|
||||
ALC.EFX.Effect(Handle, EffectInteger.EffectType, (int) EffectType.EaxReverb);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != 0)
|
||||
{
|
||||
EFX.DeleteEffect(Handle);
|
||||
ALC.EFX.DeleteEffect(Handle);
|
||||
Handle = 0;
|
||||
}
|
||||
}
|
||||
@@ -43,14 +44,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDensity, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDensity, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDensity, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDensity, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -61,14 +62,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDiffusion, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDiffusion, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDiffusion, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDiffusion, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -79,14 +80,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbGain, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbGain, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbGain, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbGain, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -97,14 +98,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbGainHF, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbGainHF, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbGainHF, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbGainHF, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -115,14 +116,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbGainLF, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbGainLF, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbGainLF, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbGainLF, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -133,14 +134,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayTime, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayTime, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDecayTime, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDecayTime, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -151,14 +152,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayHFRatio, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayHFRatio, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDecayHFRatio, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDecayHFRatio, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -169,14 +170,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayLFRatio, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayLFRatio, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbDecayLFRatio, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbDecayLFRatio, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -187,14 +188,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsGain, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsGain, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsGain, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsGain, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -205,14 +206,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsDelay, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsDelay, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsDelay, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsDelay, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -223,7 +224,7 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
var value = EFX.GetEffect(Handle, EffectVector3.EaxReverbReflectionsPan);
|
||||
var value = ALC.EFX.GetEffect(Handle, EffectVector3.EaxReverbReflectionsPan);
|
||||
_master._checkAlError();
|
||||
return new Vector3(value.X, value.Z, value.Y);
|
||||
}
|
||||
@@ -231,7 +232,7 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
{
|
||||
_checkDisposed();
|
||||
var openVec = new OpenTK.Mathematics.Vector3(value.X, value.Y, value.Z);
|
||||
EFX.Effect(Handle, EffectVector3.EaxReverbReflectionsPan, ref openVec);
|
||||
ALC.EFX.Effect(Handle, EffectVector3.EaxReverbReflectionsPan, ref openVec);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -242,14 +243,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbGain, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbGain, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbGain, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbGain, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -260,14 +261,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbDelay, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbDelay, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbDelay, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbDelay, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -278,7 +279,7 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
var value = EFX.GetEffect(Handle, EffectVector3.EaxReverbLateReverbPan);
|
||||
var value = ALC.EFX.GetEffect(Handle, EffectVector3.EaxReverbLateReverbPan);
|
||||
_master._checkAlError();
|
||||
return new Vector3(value.X, value.Z, value.Y);
|
||||
}
|
||||
@@ -286,7 +287,7 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
{
|
||||
_checkDisposed();
|
||||
var openVec = new OpenTK.Mathematics.Vector3(value.X, value.Y, value.Z);
|
||||
EFX.Effect(Handle, EffectVector3.EaxReverbLateReverbPan, ref openVec);
|
||||
ALC.EFX.Effect(Handle, EffectVector3.EaxReverbLateReverbPan, ref openVec);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -297,14 +298,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoTime, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoTime, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbEchoTime, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbEchoTime, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -315,14 +316,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoDepth, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoDepth, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbEchoDepth, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbEchoDepth, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -333,14 +334,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationTime, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationTime, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbModulationTime, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbModulationTime, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -351,14 +352,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationDepth, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationDepth, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbModulationDepth, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbModulationDepth, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -369,14 +370,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -387,14 +388,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbHFReference, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbHFReference, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbHFReference, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbHFReference, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -405,14 +406,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbLFReference, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbLFReference, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbLFReference, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbLFReference, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -423,14 +424,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, value);
|
||||
ALC.EFX.Effect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
@@ -441,14 +442,14 @@ internal sealed class AudioEffect : IAudioEffect
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.GetEffect(Handle, EffectInteger.EaxReverbDecayHFLimit, out var value);
|
||||
ALC.EFX.GetEffect(Handle, EffectInteger.EaxReverbDecayHFLimit, out var value);
|
||||
_master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
EFX.Effect(Handle, EffectInteger.EaxReverbDecayHFLimit, value);
|
||||
ALC.EFX.Effect(Handle, EffectInteger.EaxReverbDecayHFLimit, value);
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
|
||||
namespace Robust.Client.Audio.Effects;
|
||||
@@ -6,13 +6,13 @@ namespace Robust.Client.Audio.Effects;
|
||||
/// <inheritdoc />
|
||||
internal sealed class AuxiliaryAudio : IAuxiliaryAudio
|
||||
{
|
||||
internal int Handle = EFX.GenAuxiliaryEffectSlot();
|
||||
internal int Handle = ALC.EFX.GenAuxiliaryEffectSlot();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != -1)
|
||||
{
|
||||
EFX.DeleteAuxiliaryEffectSlot(Handle);
|
||||
ALC.EFX.DeleteAuxiliaryEffectSlot(Handle);
|
||||
Handle = -1;
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,11 @@ internal sealed class AuxiliaryAudio : IAuxiliaryAudio
|
||||
{
|
||||
if (effect is AudioEffect audEffect)
|
||||
{
|
||||
EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, audEffect.Handle);
|
||||
ALC.EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, audEffect.Handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, 0);
|
||||
ALC.EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,4 +213,6 @@ public interface IMidiRenderer : IDisposable
|
||||
/// Actually disposes of this renderer. Do NOT use outside the MIDI thread.
|
||||
/// </summary>
|
||||
internal void InternalDispose();
|
||||
|
||||
byte MinVolume { get; set; }
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
[Dependency] private readonly IRuntimeLog _runtime = default!;
|
||||
|
||||
private AudioSystem _audioSys = default!;
|
||||
private SharedPhysicsSystem _broadPhaseSystem = default!;
|
||||
private SharedPhysicsSystem _physics = default!;
|
||||
private SharedTransformSystem _xformSystem = default!;
|
||||
|
||||
public IReadOnlyList<IMidiRenderer> Renderers
|
||||
@@ -81,7 +81,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
private Thread? _midiThread;
|
||||
private ISawmill _midiSawmill = default!;
|
||||
private float _gain = 0f;
|
||||
private bool _volumeDirty = true;
|
||||
private bool _gainDirty = true;
|
||||
|
||||
// Not reliable until Fluidsynth is initialized!
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
@@ -96,7 +96,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
return;
|
||||
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, clamped);
|
||||
_volumeDirty = true;
|
||||
_gainDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,8 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
"/usr/share/sounds/sf2/TimGM6mb.sf2",
|
||||
};
|
||||
|
||||
private static readonly string WindowsSoundfont = $@"{Environment.GetEnvironmentVariable("SystemRoot")}\system32\drivers\gm.dls";
|
||||
private static readonly string WindowsSoundfont =
|
||||
$@"{Environment.GetEnvironmentVariable("SystemRoot")}\system32\drivers\gm.dls";
|
||||
|
||||
private const string OsxSoundfont =
|
||||
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
|
||||
@@ -145,11 +146,13 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
if (FluidsynthInitialized || _failedInitialize) return;
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
|
||||
{
|
||||
_gain = value;
|
||||
_volumeDirty = true;
|
||||
}, true);
|
||||
_cfgMan.OnValueChanged(CVars.MidiVolume,
|
||||
value =>
|
||||
{
|
||||
_gain = value;
|
||||
_gainDirty = true;
|
||||
},
|
||||
true);
|
||||
|
||||
_midiSawmill = _logger.GetSawmill("midi");
|
||||
#if DEBUG
|
||||
@@ -167,13 +170,15 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
// not a directory, preserve the old file and create an actual directory
|
||||
else if (!_resourceManager.UserData.IsDir(CustomSoundfontDirectory))
|
||||
{
|
||||
_resourceManager.UserData.Rename(CustomSoundfontDirectory, CustomSoundfontDirectory.WithName(CustomSoundfontDirectory.Filename + ".old"));
|
||||
_resourceManager.UserData.Rename(CustomSoundfontDirectory,
|
||||
CustomSoundfontDirectory.WithName(CustomSoundfontDirectory.Filename + ".old"));
|
||||
_resourceManager.UserData.CreateDir(CustomSoundfontDirectory);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
NFluidsynth.Logger.SetLoggerMethod(_loggerDelegate); // Will cause a safe DllNotFoundException if not available.
|
||||
NFluidsynth.Logger
|
||||
.SetLoggerMethod(_loggerDelegate); // Will cause a safe DllNotFoundException if not available.
|
||||
|
||||
_settings = new Settings();
|
||||
_settings["synth.sample-rate"].DoubleValue = 44100;
|
||||
@@ -193,7 +198,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
//_settings["synth.verbose"].IntValue = 1; // Useful for debugging.
|
||||
|
||||
var midiParallel = _cfgMan.GetCVar(CVars.MidiParallelism);
|
||||
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int)(Math.Log2(midiParallel) * 2048), 1, 65535);
|
||||
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int) (Math.Log2(midiParallel) * 2048), 1, 65535);
|
||||
_settings["synth.cpu-cores"].IntValue = Math.Clamp(midiParallel, 1, 256);
|
||||
|
||||
_midiSawmill.Debug($"Synth Cores: {_settings["synth.cpu-cores"].IntValue}");
|
||||
@@ -219,7 +224,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
};
|
||||
|
||||
_audioSys = _entityManager.EntitySysManager.GetEntitySystem<AudioSystem>();
|
||||
_broadPhaseSystem = _entityManager.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>();
|
||||
_physics = _entityManager.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>();
|
||||
_xformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
_entityManager.GetEntityQuery<PhysicsComponent>();
|
||||
_entityManager.GetEntityQuery<TransformComponent>();
|
||||
@@ -263,7 +268,8 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
soundfontLoader.SetCallbacks(_soundfontLoaderCallbacks);
|
||||
|
||||
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _audio, _taskManager, _midiSawmill);
|
||||
var renderer =
|
||||
new MidiRenderer(_settings!, soundfontLoader, mono, this, _audio, _taskManager, _midiSawmill);
|
||||
|
||||
LoadSoundFontSetup(renderer);
|
||||
|
||||
@@ -273,6 +279,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
_renderers.Add(renderer);
|
||||
}
|
||||
|
||||
return renderer;
|
||||
}
|
||||
finally
|
||||
@@ -309,99 +316,23 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
_updateSemaphore.Release();
|
||||
|
||||
_volumeDirty = false;
|
||||
_gainDirty = false;
|
||||
}
|
||||
|
||||
private void UpdateRenderer(IMidiRenderer renderer, MapCoordinates listener)
|
||||
{
|
||||
// TODO: This should be sharing more code with AudioSystem.
|
||||
try
|
||||
{
|
||||
if (renderer.Disposed)
|
||||
return;
|
||||
|
||||
if (_volumeDirty)
|
||||
{
|
||||
renderer.Source.Gain = Gain;
|
||||
}
|
||||
|
||||
if (!renderer.Mono)
|
||||
{
|
||||
renderer.Source.Global = true;
|
||||
return;
|
||||
}
|
||||
|
||||
MapCoordinates mapPos;
|
||||
|
||||
if (renderer.TrackingEntity is {} trackedEntity && !_entityManager.Deleted(trackedEntity))
|
||||
{
|
||||
renderer.TrackingCoordinates = _xformSystem.GetMapCoordinates(renderer.TrackingEntity.Value);
|
||||
|
||||
// Pause it if the attached entity is paused.
|
||||
if (_entityManager.IsPaused(renderer.TrackingEntity))
|
||||
{
|
||||
renderer.Source.Pause();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (renderer.TrackingCoordinates == null)
|
||||
{
|
||||
renderer.Source.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
mapPos = renderer.TrackingCoordinates.Value;
|
||||
|
||||
// If it's on a different map then just mute it, not pause.
|
||||
if (mapPos.MapId == MapId.Nullspace || mapPos.MapId != listener.MapId)
|
||||
{
|
||||
renderer.Source.Gain = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
// Was previously muted maybe so try unmuting it?
|
||||
if (renderer.Source.Gain == 0f)
|
||||
{
|
||||
renderer.Source.Gain = Gain;
|
||||
}
|
||||
|
||||
var worldPos = mapPos.Position;
|
||||
var delta = worldPos - listener.Position;
|
||||
var distance = delta.Length();
|
||||
|
||||
// Update position
|
||||
// Out of range so just clip it for us.
|
||||
if (distance > renderer.Source.MaxDistance)
|
||||
{
|
||||
// Still keeps the source playing, just with no volume.
|
||||
renderer.Source.Gain = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
// Same imprecision suppression as audiosystem.
|
||||
if (distance > 0f && distance < 0.01f)
|
||||
{
|
||||
worldPos = listener.Position;
|
||||
delta = Vector2.Zero;
|
||||
distance = 0f;
|
||||
}
|
||||
|
||||
renderer.Source.Position = worldPos;
|
||||
|
||||
// Update velocity (doppler).
|
||||
if (!_entityManager.Deleted(renderer.TrackingEntity))
|
||||
{
|
||||
var velocity = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity.Value);
|
||||
renderer.Source.Velocity = velocity;
|
||||
}
|
||||
if (!renderer.Source.Global)
|
||||
UpdateLocalRenderer(renderer, listener);
|
||||
else
|
||||
{
|
||||
renderer.Source.Velocity = Vector2.Zero;
|
||||
}
|
||||
|
||||
// Update occlusion
|
||||
var occlusion = _audioSys.GetOcclusion(listener, delta, distance, renderer.TrackingEntity);
|
||||
renderer.Source.Occlusion = occlusion;
|
||||
UpdateGlobalRenderer(renderer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -409,6 +340,58 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLocalRenderer(IMidiRenderer renderer, MapCoordinates listener)
|
||||
{
|
||||
if (_entityManager.Deleted(renderer.TrackingEntity) || _entityManager.IsPaused(renderer.TrackingEntity))
|
||||
{
|
||||
renderer.Source.Gain = 0f;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
MapCoordinates mapCoords = _xformSystem.GetMapCoordinates(renderer.TrackingEntity.Value);
|
||||
renderer.TrackingCoordinates = mapCoords;
|
||||
|
||||
if (mapCoords.MapId == MapId.Nullspace || mapCoords.MapId != listener.MapId)
|
||||
{
|
||||
renderer.Source.Gain = 0f;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 mapPosition = mapCoords.Position;
|
||||
Vector2 listenerDelta = mapPosition - listener.Position;
|
||||
var listenerDeltaLength = listenerDelta.Length();
|
||||
|
||||
if (listenerDeltaLength > renderer.Source.MaxDistance)
|
||||
{
|
||||
renderer.Source.Gain = 0f;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (listenerDeltaLength is > 0f and < 0.01f)
|
||||
{
|
||||
mapPosition = listener.Position;
|
||||
listenerDelta = Vector2.Zero;
|
||||
listenerDeltaLength = 0f;
|
||||
}
|
||||
|
||||
if (_gainDirty || renderer.Source.Gain == 0f)
|
||||
renderer.Source.Gain = Gain;
|
||||
|
||||
renderer.Source.Position = mapPosition;
|
||||
renderer.Source.Velocity = _physics.GetMapLinearVelocity(renderer.TrackingEntity.Value);
|
||||
renderer.Source.Occlusion =
|
||||
_audioSys.GetOcclusion(listener, listenerDelta, listenerDeltaLength, renderer.TrackingEntity);
|
||||
}
|
||||
|
||||
private void UpdateGlobalRenderer(IMidiRenderer renderer)
|
||||
{
|
||||
if (_gainDirty)
|
||||
renderer.Source.Gain = Gain;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main method for the thread rendering the midi audio.
|
||||
/// </summary>
|
||||
@@ -428,7 +411,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
if (!renderer.Disposed)
|
||||
{
|
||||
if (renderer.Master is { Disposed: true })
|
||||
if (renderer.Master is {Disposed: true})
|
||||
renderer.Master = null;
|
||||
|
||||
renderer.Render();
|
||||
|
||||
@@ -214,6 +214,11 @@ internal sealed partial class MidiRenderer : IMidiRenderer
|
||||
[ViewVariables]
|
||||
public BitArray FilteredChannels { get; } = new(RobustMidiEvent.MaxChannels);
|
||||
|
||||
[ViewVariables]
|
||||
public byte MinVolume { get => _minVolume; set => _minVolume = value; }
|
||||
|
||||
private byte _minVolume;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public byte? VelocityOverride { get; set; } = null;
|
||||
|
||||
@@ -539,14 +544,7 @@ internal sealed partial class MidiRenderer : IMidiRenderer
|
||||
if (velocity <= 0)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
_synth.NoteOn(channel, key, velocity);
|
||||
}
|
||||
catch (FluidSynthInteropException e)
|
||||
{
|
||||
_midiSawmill.Error($"CH:{channel} KEY:{key} VEL:{velocity} {e.ToStringBetter()}");
|
||||
}
|
||||
_synth.TryNoteOn(channel, key, velocity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,19 +572,32 @@ internal sealed partial class MidiRenderer : IMidiRenderer
|
||||
{
|
||||
case RobustMidiCommand.NoteOff:
|
||||
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = 0;
|
||||
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
|
||||
break;
|
||||
_synth.TryNoteOff(midiEvent.Channel, midiEvent.Key);
|
||||
|
||||
break;
|
||||
case RobustMidiCommand.NoteOn:
|
||||
// Velocity 0 *can* represent a NoteOff event.
|
||||
var velocity = midiEvent.Velocity;
|
||||
if (velocity == 0)
|
||||
{
|
||||
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = 0;
|
||||
_synth.TryNoteOn(midiEvent.Channel, midiEvent.Key, velocity);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (FilteredChannels[midiEvent.Channel])
|
||||
break;
|
||||
|
||||
var velocity = VelocityOverride ?? midiEvent.Velocity;
|
||||
if (MinVolume > 0)
|
||||
velocity = (byte)Math.Floor(MathHelper.Lerp(MinVolume, 127, (float)velocity / 127));
|
||||
|
||||
velocity = VelocityOverride ?? velocity;
|
||||
|
||||
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = velocity;
|
||||
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, velocity);
|
||||
break;
|
||||
_synth.TryNoteOn(midiEvent.Channel, midiEvent.Key, velocity);
|
||||
|
||||
break;
|
||||
case RobustMidiCommand.AfterTouch:
|
||||
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = midiEvent.Value;
|
||||
_synth.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -77,7 +76,7 @@ internal sealed class AudioSource : BaseAudioSource
|
||||
else
|
||||
{
|
||||
if (FilterHandle != 0)
|
||||
EFX.DeleteFilter(FilterHandle);
|
||||
ALC.EFX.DeleteFilter(FilterHandle);
|
||||
|
||||
AL.DeleteSource(SourceHandle);
|
||||
Master.RemoveAudioSource(SourceHandle);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Audio.Effects;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
@@ -82,9 +81,9 @@ public abstract class BaseAudioSource : IAudioSource
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
var state = AL.GetSourceState(SourceHandle);
|
||||
var state = AL.GetSource(SourceHandle, ALGetSourcei.SourceState);
|
||||
Master._checkAlError();
|
||||
return state == ALSourceState.Playing;
|
||||
return state == (int)ALSourceState.Playing;
|
||||
}
|
||||
set
|
||||
{
|
||||
@@ -362,11 +361,11 @@ public abstract class BaseAudioSource : IAudioSource
|
||||
|
||||
if (audio is AuxiliaryAudio impAudio)
|
||||
{
|
||||
EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, impAudio.Handle, 0, 0);
|
||||
ALC.EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, impAudio.Handle, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, 0, 0, 0);
|
||||
ALC.EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, 0, 0, 0);
|
||||
}
|
||||
|
||||
Master._checkAlError();
|
||||
@@ -376,12 +375,12 @@ public abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
if (FilterHandle == 0)
|
||||
{
|
||||
FilterHandle = EFX.GenFilter();
|
||||
EFX.Filter(FilterHandle, FilterInteger.FilterType, (int) FilterType.Lowpass);
|
||||
FilterHandle = ALC.EFX.GenFilter();
|
||||
ALC.EFX.Filter(FilterHandle, FilterInteger.FilterType, (int) FilterType.Lowpass);
|
||||
}
|
||||
|
||||
EFX.Filter(FilterHandle, FilterFloat.LowpassGain, gain);
|
||||
EFX.Filter(FilterHandle, FilterFloat.LowpassGainHF, cutoff);
|
||||
ALC.EFX.Filter(FilterHandle, FilterFloat.LowpassGain, gain);
|
||||
ALC.EFX.Filter(FilterHandle, FilterFloat.LowpassGainHF, cutoff);
|
||||
AL.Source(SourceHandle, ALSourcei.EfxDirectFilter, FilterHandle);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
|
||||
namespace Robust.Client.Audio.Sources;
|
||||
@@ -37,9 +36,9 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
var state = AL.GetSourceState(SourceHandle);
|
||||
var state = AL.GetSource(SourceHandle, ALGetSourcei.SourceState);
|
||||
_master._checkAlError();
|
||||
return state == ALSourceState.Playing;
|
||||
return state == (int)ALSourceState.Playing;
|
||||
}
|
||||
set
|
||||
{
|
||||
@@ -84,7 +83,7 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
else
|
||||
{
|
||||
if (FilterHandle != 0)
|
||||
EFX.DeleteFilter(FilterHandle);
|
||||
ALC.EFX.DeleteFilter(FilterHandle);
|
||||
|
||||
AL.DeleteSource(SourceHandle);
|
||||
AL.DeleteBuffers(BufferHandles);
|
||||
|
||||
@@ -144,6 +144,7 @@ namespace Robust.Client
|
||||
deps.Register<IViewVariablesManager, ClientViewVariablesManager>();
|
||||
deps.Register<IClientViewVariablesManager, ClientViewVariablesManager>();
|
||||
deps.Register<IClientViewVariablesManagerInternal, ClientViewVariablesManager>();
|
||||
deps.Register<IViewVariableControlFactory, ViewVariableControlFactory>();
|
||||
deps.Register<IClientConGroupController, ClientConGroupController>();
|
||||
deps.Register<IScriptClient, ScriptClient>();
|
||||
deps.Register<IRobustSerializer, ClientRobustSerializer>();
|
||||
|
||||
@@ -71,7 +71,7 @@ internal sealed class ClientNetConfigurationManager : NetConfigurationManager, I
|
||||
// Actually set the CVar
|
||||
base.SetCVar(name, value, force);
|
||||
|
||||
if ((flags & CVar.REPLICATED) == 0)
|
||||
if ((flags & CVar.REPLICATED) == 0 || !NetManager.IsConnected)
|
||||
return;
|
||||
|
||||
var msg = new MsgConVars();
|
||||
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.ContentPack;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
#if DEBUG
|
||||
#if TOOLS
|
||||
internal sealed class DumpMetadataMembersCommand : LocalizedCommands
|
||||
{
|
||||
public override string Command => "dmetamem";
|
||||
|
||||
124
Robust.Client/Console/Commands/UITestCommand.TabWrapContainer.cs
Normal file
124
Robust.Client/Console/Commands/UITestCommand.TabWrapContainer.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Maths;
|
||||
using ItemJustification = Robust.Client.UserInterface.Controls.WrapContainer.ItemJustification;
|
||||
|
||||
namespace Robust.Client.Console.Commands;
|
||||
|
||||
internal sealed partial class UITestControl
|
||||
{
|
||||
private sealed class TabWrapContainer : Control
|
||||
{
|
||||
private readonly CheckBox _equalSizeBox;
|
||||
private readonly CheckBox _reverseBox;
|
||||
private readonly OptionButton _axisButton;
|
||||
private readonly OptionButton _justifyButton;
|
||||
private readonly LineEdit _separationEdit;
|
||||
private readonly LineEdit _crossSeparationEdit;
|
||||
|
||||
public TabWrapContainer()
|
||||
{
|
||||
var container = new WrapContainer
|
||||
{
|
||||
MouseFilter = MouseFilterMode.Stop,
|
||||
VerticalExpand = true,
|
||||
};
|
||||
|
||||
var random = new Random(3005);
|
||||
|
||||
for (var i = 0; i < 35; i++)
|
||||
{
|
||||
var val = random.Next(1, 16);
|
||||
|
||||
var text = string.Create(val, 0, (span, _) => span.Fill('O'));
|
||||
container.AddChild(new Button { Text = text });
|
||||
}
|
||||
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
Children =
|
||||
{
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 4,
|
||||
Children =
|
||||
{
|
||||
(_equalSizeBox = new CheckBox
|
||||
{
|
||||
Text = nameof(WrapContainer.EqualSize)
|
||||
}),
|
||||
(_reverseBox = new CheckBox
|
||||
{
|
||||
Text = nameof(WrapContainer.Reverse)
|
||||
}),
|
||||
(_axisButton = new OptionButton()),
|
||||
(_justifyButton = new OptionButton()),
|
||||
(_separationEdit = new LineEdit
|
||||
{
|
||||
PlaceHolder = "Separation",
|
||||
SetWidth = 100,
|
||||
}),
|
||||
(_crossSeparationEdit = new LineEdit
|
||||
{
|
||||
PlaceHolder = "Cross Separation",
|
||||
SetWidth = 100,
|
||||
})
|
||||
}
|
||||
},
|
||||
new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat { BackgroundColor = Color.Black },
|
||||
Children =
|
||||
{
|
||||
container
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
_axisButton.AddItem(nameof(Axis.Horizontal), (int)Axis.Horizontal);
|
||||
_axisButton.AddItem(nameof(Axis.HorizontalReverse), (int)Axis.HorizontalReverse);
|
||||
_axisButton.AddItem(nameof(Axis.Vertical), (int)Axis.Vertical);
|
||||
_axisButton.AddItem(nameof(Axis.VerticalReverse), (int)Axis.VerticalReverse);
|
||||
|
||||
_axisButton.OnItemSelected += args =>
|
||||
{
|
||||
_axisButton.SelectId(args.Id);
|
||||
container.LayoutAxis = (Axis)args.Id;
|
||||
};
|
||||
|
||||
_justifyButton.AddItem(nameof(ItemJustification.Begin), (int)ItemJustification.Begin);
|
||||
_justifyButton.AddItem(nameof(ItemJustification.Center), (int)ItemJustification.Center);
|
||||
_justifyButton.AddItem(nameof(ItemJustification.End), (int)ItemJustification.End);
|
||||
|
||||
_justifyButton.OnItemSelected += args =>
|
||||
{
|
||||
_justifyButton.SelectId(args.Id);
|
||||
container.Justification = (ItemJustification)args.Id;
|
||||
};
|
||||
|
||||
_equalSizeBox.OnPressed += _ => container.EqualSize = _equalSizeBox.Pressed;
|
||||
_reverseBox.OnPressed += _ => container.Reverse = _reverseBox.Pressed;
|
||||
|
||||
_separationEdit.OnTextChanged += args =>
|
||||
{
|
||||
if (!int.TryParse(args.Text, out var sep))
|
||||
sep = 0;
|
||||
|
||||
container.SeparationOverride = sep;
|
||||
};
|
||||
|
||||
_crossSeparationEdit.OnTextChanged += args =>
|
||||
{
|
||||
if (!int.TryParse(args.Text, out var sep))
|
||||
sep = 0;
|
||||
|
||||
container.CrossSeparationOverride = sep;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -44,7 +42,10 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
|
||||
var progressBar = new ProgressBar { MaxValue = 10, Value = 5 };
|
||||
vBox.AddChild(progressBar);
|
||||
|
||||
var optionButton = new OptionButton();
|
||||
var optionButton = new OptionButton
|
||||
{
|
||||
ToolTip = "This button has a tooltip. Spooky!"
|
||||
};
|
||||
optionButton.AddItem("Honk");
|
||||
optionButton.AddItem("Foo");
|
||||
optionButton.AddItem("Bar");
|
||||
@@ -154,6 +155,8 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
|
||||
|
||||
_sprite = new TabSpriteView();
|
||||
_tabContainer.AddChild(_sprite);
|
||||
_tabContainer.AddChild(TabCursorShapes());
|
||||
_tabContainer.AddChild(new TabWrapContainer { Name = nameof(Tab.WrapContainer) });
|
||||
}
|
||||
|
||||
public void OnClosed()
|
||||
@@ -210,6 +213,53 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
|
||||
return label;
|
||||
}
|
||||
|
||||
private Control TabCursorShapes()
|
||||
{
|
||||
var box = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
};
|
||||
var styleBox = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = Color.Black
|
||||
};
|
||||
foreach (var cursorName in Enum.GetNames<CursorShape>())
|
||||
{
|
||||
// Go over names due to duplicate definitions in the enum.
|
||||
var cursor = Enum.Parse<CursorShape>(cursorName);
|
||||
// Wow was I bad at API design.
|
||||
if (cursor == CursorShape.Custom)
|
||||
continue;
|
||||
|
||||
var panel = new PanelContainer
|
||||
{
|
||||
PanelOverride = styleBox,
|
||||
DefaultCursorShape = cursor,
|
||||
MouseFilter = MouseFilterMode.Stop,
|
||||
MinHeight = 30,
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = cursorName,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
Margin = new Thickness(4)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
box.AddChild(panel);
|
||||
}
|
||||
|
||||
return new ScrollContainer
|
||||
{
|
||||
Children = { box },
|
||||
VScrollEnabled = true,
|
||||
HScrollEnabled = false,
|
||||
Name = nameof(Tab.TabCursorShapes),
|
||||
};
|
||||
}
|
||||
|
||||
public void SelectTab(Tab tab)
|
||||
{
|
||||
_tabContainer.CurrentTab = (int)tab;
|
||||
@@ -226,32 +276,14 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
|
||||
TextEdit = 6,
|
||||
RichText = 7,
|
||||
SpriteView = 8,
|
||||
TabCursorShapes = 9,
|
||||
WrapContainer = 10,
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UITestCommand : LocalizedCommands
|
||||
internal abstract class BaseUITestCommand : LocalizedCommands
|
||||
{
|
||||
public override string Command => "uitest";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var window = new DefaultWindow { MinSize = new(800, 600) };
|
||||
var control = new UITestControl();
|
||||
window.OnClose += control.OnClosed;
|
||||
window.Contents.AddChild(control);
|
||||
|
||||
window.OpenCentered();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UITest2Command : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiMgr = default!;
|
||||
|
||||
public override string Command => "uitest2";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public sealed override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length > 1)
|
||||
{
|
||||
@@ -272,18 +304,10 @@ internal sealed class UITest2Command : LocalizedCommands
|
||||
control.SelectTab(tab);
|
||||
}
|
||||
|
||||
var window = _clyde.CreateWindow(new WindowCreateParameters
|
||||
{
|
||||
Title = Loc.GetString("cmd-uitest2-title"),
|
||||
});
|
||||
|
||||
var root = _uiMgr.CreateWindowRoot(window);
|
||||
window.DisposeOnClose = true;
|
||||
window.RequestClosed += _ => control.OnClosed();
|
||||
root.AddChild(control);
|
||||
CreateWindow(control);
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
public sealed override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
@@ -294,4 +318,35 @@ internal sealed class UITest2Command : LocalizedCommands
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
|
||||
protected abstract void CreateWindow(UITestControl control);
|
||||
}
|
||||
|
||||
internal sealed class UITestCommand : BaseUITestCommand
|
||||
{
|
||||
public override string Command => "uitest";
|
||||
|
||||
protected override void CreateWindow(UITestControl control)
|
||||
{
|
||||
var window = new DefaultWindow { MinSize = new(800, 600) };
|
||||
window.OnClose += control.OnClosed;
|
||||
window.Contents.AddChild(control);
|
||||
window.OpenCentered();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UITest2Command : BaseUITestCommand
|
||||
{
|
||||
public override string Command => "uitest2";
|
||||
|
||||
protected override void CreateWindow(UITestControl control)
|
||||
{
|
||||
var window = new OSWindow
|
||||
{
|
||||
Title = Loc.GetString("cmd-uitest2-title"),
|
||||
};
|
||||
window.AddChild(control);
|
||||
window.Closed += control.OnClosed;
|
||||
window.Show();
|
||||
}
|
||||
}
|
||||
|
||||
44
Robust.Client/Console/Commands/ViewportDebugCommands.cs
Normal file
44
Robust.Client/Console/Commands/ViewportDebugCommands.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
#if TOOLS
|
||||
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Console.Commands;
|
||||
|
||||
internal sealed class ViewportClearAllCachedCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
|
||||
public string Command => "vp_clear_all_cached";
|
||||
public string Description => "Fires IClydeViewport.ClearCachedResources on all viewports";
|
||||
public string Help => "";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_clyde.ViewportsClearAllCached();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ViewportTestFinalizeCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
public string Command => "vp_test_finalize";
|
||||
public string Description => "Creates a viewport, renders it once, then leaks it (finalizes it).";
|
||||
public string Help => "";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var vp = _clyde.CreateViewport(new Vector2i(1920, 1080), nameof(ViewportTestFinalizeCommand));
|
||||
vp.Eye = _eyeManager.CurrentEye;
|
||||
|
||||
vp.Render();
|
||||
|
||||
// Leak it.
|
||||
}
|
||||
}
|
||||
|
||||
#endif // TOOLS
|
||||
@@ -85,7 +85,7 @@ namespace Robust.Client.Console
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
Result = result;
|
||||
var compl = new FormattedMessage();
|
||||
var dim = Color.FromHsl((0f, 0f, 0.8f, 1f));
|
||||
var dim = Color.FromHsl(new Vector4(0f, 0f, 0.8f, 1f));
|
||||
|
||||
// warning: ew ahead
|
||||
string basen = "default";
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace Robust.Client.Debugging
|
||||
|
||||
foreach (var ent in _mapSystem.GetAnchoredEntities(gridUid, grid, spot))
|
||||
{
|
||||
if (TryComp<MetaDataComponent>(ent, out var meta))
|
||||
if (TryComp(ent, out MetaDataComponent? meta))
|
||||
{
|
||||
text.AppendLine($"uid: {ent}, {meta.EntityName}");
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using SDL3;
|
||||
|
||||
namespace Robust.Client
|
||||
{
|
||||
@@ -93,6 +94,8 @@ namespace Robust.Client
|
||||
|
||||
public void Run(DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
|
||||
{
|
||||
_displayMode = mode;
|
||||
|
||||
if (!StartupSystemSplash(options, logHandlerFactory))
|
||||
{
|
||||
_logger.Fatal("Failed to start game controller!");
|
||||
|
||||
@@ -110,6 +110,8 @@ namespace Robust.Client
|
||||
|
||||
private ResourceManifestData? _resourceManifest;
|
||||
|
||||
private DisplayMode _displayMode;
|
||||
|
||||
public void SetCommandLineArgs(CommandLineArgs args)
|
||||
{
|
||||
_commandLineArgs = args;
|
||||
@@ -273,6 +275,9 @@ namespace Robust.Client
|
||||
}
|
||||
};
|
||||
|
||||
_configurationManager.OnValueChanged(CVars.DisplayMaxFPS, _ => UpdateVsyncConfig());
|
||||
_configurationManager.OnValueChanged(CVars.DisplayVSync, _ => UpdateVsyncConfig(), invokeImmediately: true);
|
||||
|
||||
_clyde.Ready();
|
||||
|
||||
if (_resourceManifest!.AutoConnect &&
|
||||
@@ -709,6 +714,30 @@ namespace Robust.Client
|
||||
}
|
||||
|
||||
|
||||
private void UpdateVsyncConfig()
|
||||
{
|
||||
if (_displayMode == DisplayMode.Headless)
|
||||
return;
|
||||
|
||||
var vsync = _configurationManager.GetCVar(CVars.DisplayVSync);
|
||||
var maxFps = Math.Clamp(_configurationManager.GetCVar(CVars.DisplayMaxFPS), 0, 10_000);
|
||||
|
||||
_clyde.VsyncEnabled = vsync;
|
||||
|
||||
if (_mainLoop == null)
|
||||
return;
|
||||
|
||||
if (vsync || maxFps == 0)
|
||||
{
|
||||
_mainLoop.SleepMode = SleepMode.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
_mainLoop.SleepMode = SleepMode.Limit;
|
||||
_mainLoop.LimitMinFrameTime = TimeSpan.FromSeconds(1.0 / maxFps);
|
||||
}
|
||||
}
|
||||
|
||||
internal enum DisplayMode : byte
|
||||
{
|
||||
Headless,
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace Robust.Client.GameObjects
|
||||
internal event Action? AfterStartup;
|
||||
internal event Action? AfterShutdown;
|
||||
|
||||
private readonly Queue<EntityUid> _queuedPredictedDeletions = new();
|
||||
private readonly HashSet<EntityUid> _queuedPredictedDeletionsSet = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SetupNetworking();
|
||||
@@ -213,6 +216,34 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
using (histogram?.WithLabels("PredictedQueueDel").NewTimer())
|
||||
{
|
||||
while (_queuedPredictedDeletions.TryDequeue(out var uid))
|
||||
{
|
||||
if (!MetaQuery.TryGetComponentInternal(uid, out var meta))
|
||||
continue;
|
||||
|
||||
if (meta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
continue;
|
||||
|
||||
var xform = TransformQuery.GetComponentInternal(uid);
|
||||
if (meta.NetEntity.IsClientSide())
|
||||
{
|
||||
DeleteEntity(uid, meta, xform);
|
||||
}
|
||||
else
|
||||
{
|
||||
_xforms.DetachEntity(uid, xform, meta, null);
|
||||
// base call bypasses IGameTiming.InPrediction check
|
||||
// This is pretty janky and there should be a way for the client to dirty an entity outside of prediction
|
||||
// TODO PREDICTION
|
||||
base.Dirty(uid, xform, meta);
|
||||
}
|
||||
}
|
||||
|
||||
_queuedPredictedDeletionsSet.Clear();
|
||||
}
|
||||
|
||||
base.TickUpdate(frameTime, noPredictions, histogram);
|
||||
}
|
||||
|
||||
@@ -317,18 +348,23 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PredictedQueueDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
|
||||
{
|
||||
if (IsQueuedForDeletion(ent.Owner)
|
||||
|| !MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|
||||
|| ent.Comp1.EntityLifeStage >= EntityLifeStage.Terminating
|
||||
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
|
||||
{
|
||||
return;
|
||||
}
|
||||
public override bool IsQueuedForDeletion(EntityUid uid)
|
||||
=> QueuedDeletionsSet.Contains(uid) || _queuedPredictedDeletions.Contains(uid);
|
||||
|
||||
if (ent.Comp1.NetEntity.IsClientSide())
|
||||
/// <inheritdoc />
|
||||
public override void PredictedQueueDeleteEntity(Entity<MetaDataComponent?> ent)
|
||||
{
|
||||
// Some UIs get disposed after entity-manager has shut down and already deleted all entities.
|
||||
if (!Started)
|
||||
return;
|
||||
|
||||
if (IsQueuedForDeletion(ent.Owner))
|
||||
return;
|
||||
|
||||
if (!MetaQuery.Resolve(ent.Owner, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
if (ent.Comp.NetEntity.IsClientSide())
|
||||
{
|
||||
// client-side QueueDeleteEntity re-fetches MetadataComp and checks IsClientSide().
|
||||
// base call to skip that.
|
||||
@@ -337,7 +373,10 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
else
|
||||
{
|
||||
_xforms.DetachEntity(ent.Owner, ent.Comp2);
|
||||
if (!_queuedPredictedDeletionsSet.Add(ent.Owner))
|
||||
return;
|
||||
|
||||
_queuedPredictedDeletions.Enqueue(ent.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@ using Robust.Shared.ViewVariables;
|
||||
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
||||
using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
using SysVec4 = System.Numerics.Vector4;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
@@ -296,6 +294,16 @@ namespace Robust.Client.GameObjects
|
||||
LocalMatrix = Matrix3Helpers.CreateTransform(in offset, in rotation, in scale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If false, this will prevent any of this sprite's animated layers from looping their animation.
|
||||
/// This will set <see cref="Layer.AutoAnimated"/> whenever any layer's animation finishes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this is false, this effectively overrides each layer's own <see cref="Layer.Loop"/>.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public bool Loop = true;
|
||||
|
||||
/// <summary>
|
||||
/// Update this sprite component to visibly match the current state of other at the time
|
||||
/// this is called. Does not keep them perpetually in sync.
|
||||
@@ -603,6 +611,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
layer.RenderingStrategy = layerDatum.RenderingStrategy ?? layer.RenderingStrategy;
|
||||
layer.Cycle = layerDatum.Cycle;
|
||||
layer.Loop = layerDatum.Loop;
|
||||
|
||||
layer.Color = layerDatum.Color ?? layer.Color;
|
||||
layer._rotation = layerDatum.Rotation ?? layer._rotation;
|
||||
@@ -1159,6 +1168,15 @@ namespace Robust.Client.GameObjects
|
||||
/// </remarks>
|
||||
[ViewVariables] public bool Cycle;
|
||||
|
||||
/// <summary>
|
||||
/// If false, this will prevent the layer's animation from looping.
|
||||
/// This will set <see cref="AutoAnimated"/> to false once the animation finishes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This may be overriden by the parent's loop property.
|
||||
/// </remarks>
|
||||
[ViewVariables] public bool Loop = true;
|
||||
|
||||
// TODO SPRITE ACCESS
|
||||
internal RSI.State? _actualState;
|
||||
[ViewVariables] public RSI.State? ActualState => _actualState;
|
||||
@@ -1338,6 +1356,8 @@ namespace Robust.Client.GameObjects
|
||||
DirOffset = toClone.DirOffset;
|
||||
_autoAnimated = toClone._autoAnimated;
|
||||
RenderingStrategy = toClone.RenderingStrategy;
|
||||
Cycle = toClone.Cycle;
|
||||
Loop = toClone.Loop;
|
||||
if (toClone.CopyToShaderParameters is { } copyToShaderParameters)
|
||||
CopyToShaderParameters = new CopyToShaderParameters(copyToShaderParameters);
|
||||
}
|
||||
@@ -1665,17 +1685,25 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
internal void AdvanceFrameAnimation(RSI.State state)
|
||||
{
|
||||
// Can't advance frames without more than 1 delay which is already checked above.
|
||||
var delayCount = state.DelayCount;
|
||||
|
||||
while (AnimationTimeLeft < 0)
|
||||
{
|
||||
if (Reversed)
|
||||
{
|
||||
AnimationFrame -= 1;
|
||||
|
||||
// Animation finished, do we cycle back to positive or reset.
|
||||
if (AnimationFrame < 0)
|
||||
{
|
||||
if (!Loop || !_parent.Loop)
|
||||
{
|
||||
// stop at first frame
|
||||
AnimationFrame = 0;
|
||||
AnimationTimeLeft = 0;
|
||||
AutoAnimated = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Cycle)
|
||||
{
|
||||
AnimationFrame = 1;
|
||||
@@ -1693,9 +1721,17 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
AnimationFrame += 1;
|
||||
|
||||
// Animation finished, do we reverse or reset.
|
||||
if (AnimationFrame >= delayCount)
|
||||
{
|
||||
if (!Loop || !_parent.Loop)
|
||||
{
|
||||
// stop at last frame
|
||||
AnimationFrame = delayCount - 1;
|
||||
AnimationTimeLeft = 0;
|
||||
AutoAnimated = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Cycle)
|
||||
{
|
||||
AnimationFrame = delayCount - 2;
|
||||
@@ -1713,6 +1749,7 @@ namespace Robust.Client.GameObjects
|
||||
AnimationTimeLeft += state.GetDelay(AnimationFrame);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.ResourceManagement;
|
||||
@@ -9,13 +10,14 @@ namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class MapSystem : SharedMapSystem
|
||||
{
|
||||
protected override MapId GetNextMapId()
|
||||
[Pure]
|
||||
internal override MapId GetNextMapId()
|
||||
{
|
||||
// Client-side map entities use negative map Ids to avoid conflict with server-side maps.
|
||||
var id = new MapId(--LastMapId);
|
||||
var id = new MapId(LastMapId - 1);
|
||||
while (MapExists(id) || UsedIds.Contains(id))
|
||||
{
|
||||
id = new MapId(--LastMapId);
|
||||
id = new MapId(id.Value - 1);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace Robust.Client.GameObjects
|
||||
component.Enabled = state.Enabled;
|
||||
component.Offset = state.Offset;
|
||||
component.Softness = state.Softness;
|
||||
component.Falloff = state.Falloff;
|
||||
component.CurveFactor = state.CurveFactor;
|
||||
component.CastShadows = state.CastShadows;
|
||||
component.Energy = state.Energy;
|
||||
component.Radius = state.Radius;
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class ScaleVisualsSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ScaleVisualsComponent, AppearanceChangeEvent>(OnChangeData);
|
||||
}
|
||||
|
||||
private void OnChangeData(EntityUid uid, ScaleVisualsComponent component, ref AppearanceChangeEvent ev)
|
||||
{
|
||||
if (!ev.AppearanceData.TryGetValue(ScaleVisuals.Scale, out var scale) ||
|
||||
ev.Sprite == null) return;
|
||||
|
||||
var vecScale = (Vector2)scale;
|
||||
|
||||
// Set it directly because prediction may call this multiple times.
|
||||
ev.Sprite.Scale = vecScale;
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public sealed partial class SpriteSystem
|
||||
bounds = bounds.Union(GetLocalBounds(layer));
|
||||
}
|
||||
|
||||
sprite.Comp._bounds = bounds;
|
||||
sprite.Comp._bounds = bounds.Scale(sprite.Comp.Scale);
|
||||
sprite.Comp.BoundsDirty = false;
|
||||
return sprite.Comp._bounds;
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ public sealed partial class SpriteSystem
|
||||
|
||||
target.Comp.RenderOrder = source.Comp.RenderOrder;
|
||||
target.Comp.GranularLayersRendering = source.Comp.GranularLayersRendering;
|
||||
target.Comp.Loop = source.Comp.Loop;
|
||||
|
||||
DirtyBounds(target!);
|
||||
_tree.QueueTreeUpdate(target!);
|
||||
|
||||
@@ -7,8 +7,6 @@ using Robust.Shared.Graphics.RSI;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.GameObjects.SpriteComponent;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
using SysVec4 = System.Numerics.Vector4;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
@@ -157,7 +155,7 @@ public sealed partial class SpriteSystem
|
||||
// Negative color modulation values are by the default shader to disable light shading.
|
||||
// Specifically we set colour = - 1 - colour
|
||||
// This is good enough to ensure that non-negative values become negative & is trivially invertible.
|
||||
layerColor = new(new SysVec4(-1) - layerColor.RGBA);
|
||||
layerColor = new(new Vector4(-1) - layerColor.RGBA);
|
||||
}
|
||||
|
||||
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);
|
||||
|
||||
@@ -220,24 +220,34 @@ Had full state: {LastFullState != null}"
|
||||
{
|
||||
var compState = change.State;
|
||||
|
||||
if (compState is IComponentDeltaState delta
|
||||
&& compData.TryGetValue(change.NetID, out var old)) // May fail if relying on implicit data
|
||||
if (compState is not IComponentDeltaState delta)
|
||||
{
|
||||
DebugTools.Assert(old is not IComponentDeltaState, "last state is not a full state");
|
||||
|
||||
if (cloneDelta)
|
||||
{
|
||||
compState = delta.CreateNewFullState(old!);
|
||||
}
|
||||
else
|
||||
{
|
||||
delta.ApplyToFullState(old!);
|
||||
compState = old;
|
||||
}
|
||||
DebugTools.Assert(compState is not IComponentDeltaState, "newly constructed state is not a full state");
|
||||
compData[change.NetID] = compState;
|
||||
continue;
|
||||
}
|
||||
|
||||
compData[change.NetID] = compState;
|
||||
if (!compData.TryGetValue(change.NetID, out var old))
|
||||
{
|
||||
// Either the server needs to ensure that the initial state it sends to a client is a full
|
||||
// state, or the client needs to be able to construct an implicit full state (i.e., get-state
|
||||
// code needs to be in shared code).
|
||||
//
|
||||
// Without this, the client won't be able to reset predicted changes made to this component.
|
||||
DebugTools.Assert("Received delta state without having received or constructed an implicit full state");
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(old is not IComponentDeltaState, "last state is not a full state");
|
||||
|
||||
if (!cloneDelta)
|
||||
{
|
||||
delta.ApplyToFullState(old!);
|
||||
continue;
|
||||
}
|
||||
|
||||
var newFull = delta.CreateNewFullState(old!);
|
||||
compData[change.NetID] = newFull;
|
||||
DebugTools.Assert(newFull is not IComponentDeltaState, "constructed state is not a full state");
|
||||
}
|
||||
|
||||
if (entityState.NetComponents == null)
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
internal sealed class NetInterpOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
@@ -32,7 +34,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_lookup = lookup;
|
||||
_shader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
|
||||
_shader = _prototypeManager.Index(UnshadedShader).Instance();
|
||||
_container = _entityManager.System<SharedContainerSystem>();
|
||||
_xform = _entityManager.System<SharedTransformSystem>();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Log;
|
||||
#if WINDOWS
|
||||
using TerraFX.Interop.Windows;
|
||||
using TerraFX.Interop.DirectX;
|
||||
#endif
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -13,6 +17,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void InitGLContextManager()
|
||||
{
|
||||
CheckForceCompatMode();
|
||||
|
||||
// Advanced GL contexts currently disabled due to lack of testing etc.
|
||||
if (OperatingSystem.IsWindows() && _cfg.GetCVar(CVars.DisplayAngle))
|
||||
{
|
||||
@@ -55,6 +61,74 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_glContext = new GLContextWindow(this);
|
||||
}
|
||||
|
||||
private void CheckForceCompatMode()
|
||||
{
|
||||
#if WINDOWS
|
||||
// Qualcomm (Snapdragon/Adreno) devices have broken OpenGL drivers on Windows.
|
||||
|
||||
if (CheckIsQualcommDevice())
|
||||
{
|
||||
_sawmillOgl.Info("We appear to be on a Qualcomm device. Enabling compat mode due to broken OpenGL driver");
|
||||
_cfg.OverrideDefault(CVars.DisplayCompat, true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if WINDOWS
|
||||
private static unsafe bool CheckIsQualcommDevice()
|
||||
{
|
||||
// Ideally we would check the OpenGL driver instead... but OpenGL is terrible so that's impossible.
|
||||
// Let's just check with DXGI instead.
|
||||
|
||||
IDXGIFactory1* dxgiFactory;
|
||||
ThrowIfFailed(
|
||||
nameof(DirectX.CreateDXGIFactory1),
|
||||
DirectX.CreateDXGIFactory1(Windows.__uuidof<IDXGIFactory1>(), (void**) &dxgiFactory));
|
||||
|
||||
try
|
||||
{
|
||||
uint idx = 0;
|
||||
IDXGIAdapter* adapter;
|
||||
while (dxgiFactory->EnumAdapters(idx, &adapter) != DXGI.DXGI_ERROR_NOT_FOUND)
|
||||
{
|
||||
try
|
||||
{
|
||||
DXGI_ADAPTER_DESC desc;
|
||||
ThrowIfFailed("GetDesc", adapter->GetDesc(&desc));
|
||||
|
||||
var descString = ((ReadOnlySpan<char>)desc.Description).TrimEnd('\0');
|
||||
if (descString.Contains("qualcomm", StringComparison.OrdinalIgnoreCase) ||
|
||||
descString.Contains("snapdragon", StringComparison.OrdinalIgnoreCase) ||
|
||||
descString.Contains("adreno", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
adapter->Release();
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
dxgiFactory->Release();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ThrowIfFailed(string methodName, HRESULT hr)
|
||||
{
|
||||
if (Windows.FAILED(hr))
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private struct GLContextSpec
|
||||
{
|
||||
public int Major;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
@@ -121,6 +121,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderNow(IRenderTarget renderTarget, Action<IRenderHandle> callback)
|
||||
{
|
||||
ClearRenderState();
|
||||
|
||||
_renderHandle.RenderInRenderTarget(
|
||||
renderTarget,
|
||||
() =>
|
||||
{
|
||||
callback(_renderHandle);
|
||||
},
|
||||
null);
|
||||
}
|
||||
|
||||
private void RenderSingleWorldOverlay(Overlay overlay, Viewport vp, OverlaySpace space, in Box2 worldBox, in Box2Rotated worldBounds)
|
||||
{
|
||||
// Check that entity manager has started.
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
|
||||
@@ -18,7 +18,6 @@ using Robust.Shared.Graphics;
|
||||
using static Robust.Shared.GameObjects.OccluderComponent;
|
||||
using Robust.Shared.Utility;
|
||||
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -452,6 +451,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var lastPower = float.NaN;
|
||||
var lastColor = new Color(float.NaN, float.NaN, float.NaN, float.NaN);
|
||||
var lastSoftness = float.NaN;
|
||||
var lastFalloff = float.NaN;
|
||||
var lastCurveFactor = float.NaN;
|
||||
Texture? lastMask = null;
|
||||
|
||||
using (_prof.Group("Draw Lights"))
|
||||
@@ -505,6 +506,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
lightShader.SetUniformMaybe("lightSoftness", lastSoftness);
|
||||
}
|
||||
|
||||
if (!MathHelper.CloseToPercent(lastFalloff, component.Falloff))
|
||||
{
|
||||
lastFalloff = component.Falloff;
|
||||
lightShader.SetUniformMaybe("lightFalloff", lastFalloff);
|
||||
}
|
||||
|
||||
if (!MathHelper.CloseToPercent(lastCurveFactor, component.CurveFactor))
|
||||
{
|
||||
lastCurveFactor = component.CurveFactor;
|
||||
lightShader.SetUniformMaybe("lightCurveFactor", lastCurveFactor);
|
||||
}
|
||||
|
||||
lightShader.SetUniformMaybe("lightCenter", lightPos);
|
||||
lightShader.SetUniformMaybe("lightIndex",
|
||||
component.CastShadows ? (i + 0.5f) / ShadowTexture.Height : -1);
|
||||
|
||||
@@ -209,6 +209,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var pressure = estPixSize * size.X * size.Y;
|
||||
|
||||
var handle = AllocRid();
|
||||
var renderTarget = new RenderTexture(size, textureObject, this, handle);
|
||||
var data = new LoadedRenderTarget
|
||||
{
|
||||
IsWindow = false,
|
||||
@@ -220,10 +221,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
MemoryPressure = pressure,
|
||||
ColorFormat = format.ColorFormat,
|
||||
SampleParameters = sampleParameters,
|
||||
Instance = new WeakReference<RenderTargetBase>(renderTarget),
|
||||
Name = name,
|
||||
};
|
||||
|
||||
//GC.AddMemoryPressure(pressure);
|
||||
var renderTarget = new RenderTexture(size, textureObject, this, handle);
|
||||
_renderTargets.Add(handle, data);
|
||||
return renderTarget;
|
||||
}
|
||||
@@ -301,10 +303,22 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class LoadedRenderTarget
|
||||
public IEnumerable<(RenderTargetBase, LoadedRenderTarget)> GetLoadedRenderTextures()
|
||||
{
|
||||
foreach (var loaded in _renderTargets.Values)
|
||||
{
|
||||
if (!loaded.Instance.TryGetTarget(out var instance))
|
||||
continue;
|
||||
|
||||
yield return (instance, loaded);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class LoadedRenderTarget
|
||||
{
|
||||
public bool IsWindow;
|
||||
public WindowId WindowId;
|
||||
public string? Name;
|
||||
|
||||
public Vector2i Size;
|
||||
public bool IsSrgb;
|
||||
@@ -325,9 +339,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public long MemoryPressure;
|
||||
|
||||
public TextureSampleParameters? SampleParameters;
|
||||
|
||||
public required WeakReference<RenderTargetBase> Instance;
|
||||
}
|
||||
|
||||
private abstract class RenderTargetBase : IRenderTarget
|
||||
internal abstract class RenderTargetBase : IRenderTarget
|
||||
{
|
||||
protected readonly Clyde Clyde;
|
||||
private bool _disposed;
|
||||
@@ -389,7 +405,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RenderTexture : RenderTargetBase, IRenderTexture
|
||||
internal sealed class RenderTexture : RenderTargetBase, IRenderTexture
|
||||
{
|
||||
public RenderTexture(Vector2i size, ClydeTexture texture, Clyde clyde, ClydeHandle handle)
|
||||
: base(clyde, handle)
|
||||
|
||||
@@ -11,8 +11,6 @@ using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -541,7 +539,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
case Matrix3x2 matrix3:
|
||||
program.SetUniform(name, matrix3);
|
||||
break;
|
||||
case Matrix4 matrix4:
|
||||
case Matrix4x4 matrix4:
|
||||
program.SetUniform(name, matrix4);
|
||||
break;
|
||||
case ClydeTexture clydeTexture:
|
||||
|
||||
@@ -10,8 +10,6 @@ using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -528,7 +526,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
data.Parameters[name] = value;
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, in Matrix4 value)
|
||||
private protected override void SetParameterImpl(string name, in Matrix4x4 value)
|
||||
{
|
||||
var data = Parent._shaderInstances[Handle];
|
||||
data.ParametersDirty = true;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
@@ -15,10 +16,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private readonly Dictionary<ClydeHandle, WeakReference<Viewport>> _viewports =
|
||||
new();
|
||||
|
||||
private long _nextViewportId = 1;
|
||||
|
||||
private readonly ConcurrentQueue<ViewportDisposeData> _viewportDisposeQueue = new();
|
||||
|
||||
private Viewport CreateViewport(Vector2i size, TextureSampleParameters? sampleParameters = default, string? name = null)
|
||||
{
|
||||
var handle = AllocRid();
|
||||
var viewport = new Viewport(handle, name, this)
|
||||
var viewport = new Viewport(_nextViewportId++, handle, name, this)
|
||||
{
|
||||
Size = size,
|
||||
RenderTarget = CreateRenderTarget(size,
|
||||
@@ -59,28 +64,43 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void FlushViewportDispose()
|
||||
{
|
||||
// Free of allocations unless a dead viewport is found.
|
||||
List<ClydeHandle>? toRemove = null;
|
||||
foreach (var (handle, viewportRef) in _viewports)
|
||||
while (_viewportDisposeQueue.TryDequeue(out var data))
|
||||
{
|
||||
if (!viewportRef.TryGetTarget(out _))
|
||||
{
|
||||
toRemove ??= new List<ClydeHandle>();
|
||||
toRemove.Add(handle);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var remove in toRemove)
|
||||
{
|
||||
_viewports.Remove(remove);
|
||||
DisposeViewport(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeViewport(ViewportDisposeData disposeData)
|
||||
{
|
||||
_clydeSawmill.Warning($"Viewport {disposeData.Id} got leaked");
|
||||
|
||||
_viewports.Remove(disposeData.Handle);
|
||||
if (disposeData.ClearEvent is not { } clearEvent)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
clearEvent(disposeData.ClearEventData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_clydeSawmill.Error($"Caught exception while disposing viewport: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
#if TOOLS
|
||||
public void ViewportsClearAllCached()
|
||||
{
|
||||
foreach (var vpRef in _viewports.Values)
|
||||
{
|
||||
if (!vpRef.TryGetTarget(out var vp))
|
||||
continue;
|
||||
|
||||
vp.FireClear();
|
||||
}
|
||||
}
|
||||
#endif // TOOLS
|
||||
|
||||
private sealed class Viewport : IClydeViewport
|
||||
{
|
||||
private readonly ClydeHandle _handle;
|
||||
@@ -106,17 +126,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public string? Name { get; }
|
||||
|
||||
public Viewport(ClydeHandle handle, string? name, Clyde clyde)
|
||||
public Viewport(long id, ClydeHandle handle, string? name, Clyde clyde)
|
||||
{
|
||||
Name = name;
|
||||
_handle = handle;
|
||||
_clyde = clyde;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public Vector2i Size { get; set; }
|
||||
public event Action<ClearCachedViewportResourcesEvent>? ClearCachedResources;
|
||||
public Color? ClearColor { get; set; } = Color.Black;
|
||||
public Vector2 RenderScale { get; set; } = Vector2.One;
|
||||
public bool AutomaticRender { get; set; }
|
||||
public long Id { get; }
|
||||
|
||||
void IClydeViewport.Render()
|
||||
{
|
||||
@@ -186,20 +209,56 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_clyde.RenderOverlaysDirect(this, control, handle, OverlaySpace.ScreenSpace, viewportBounds);
|
||||
}
|
||||
|
||||
~Viewport()
|
||||
{
|
||||
_clyde._viewportDisposeQueue.Enqueue(DisposeData(referenceSelf: false));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
RenderTarget.Dispose();
|
||||
LightRenderTarget.Dispose();
|
||||
WallMaskRenderTarget.Dispose();
|
||||
WallBleedIntermediateRenderTarget1.Dispose();
|
||||
WallBleedIntermediateRenderTarget2.Dispose();
|
||||
|
||||
_clyde._viewports.Remove(_handle);
|
||||
_clyde.DisposeViewport(DisposeData(referenceSelf: false));
|
||||
}
|
||||
|
||||
private ViewportDisposeData DisposeData(bool referenceSelf)
|
||||
{
|
||||
return new ViewportDisposeData
|
||||
{
|
||||
Handle = _handle,
|
||||
Id = Id,
|
||||
ClearEvent = ClearCachedResources,
|
||||
ClearEventData = MakeClearEvent(referenceSelf)
|
||||
};
|
||||
}
|
||||
|
||||
private ClearCachedViewportResourcesEvent MakeClearEvent(bool referenceSelf)
|
||||
{
|
||||
return new ClearCachedViewportResourcesEvent(Id, referenceSelf ? this : null);
|
||||
}
|
||||
|
||||
public void FireClear()
|
||||
{
|
||||
ClearCachedResources?.Invoke(MakeClearEvent(referenceSelf: true));
|
||||
}
|
||||
|
||||
IRenderTexture IClydeViewport.RenderTarget => RenderTarget;
|
||||
IRenderTexture IClydeViewport.LightRenderTarget => LightRenderTarget;
|
||||
public IEye? Eye { get; set; }
|
||||
}
|
||||
|
||||
private sealed class ViewportDisposeData
|
||||
{
|
||||
public ClydeHandle Handle;
|
||||
public long Id;
|
||||
public Action<ClearCachedViewportResourcesEvent>? ClearEvent;
|
||||
public ClearCachedViewportResourcesEvent ClearEventData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.Input;
|
||||
@@ -101,6 +102,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
_windowingThread = Thread.CurrentThread;
|
||||
|
||||
// Default to SDL3 on ARM64. GLFW is not feature complete there (lacking file dialog implementation)
|
||||
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
_cfg.SetCVar(CVars.DisplayWindowingApi, "sdl3");
|
||||
|
||||
var windowingApi = _cfg.GetCVar(CVars.DisplayWindowingApi);
|
||||
IWindowingImpl winImpl;
|
||||
|
||||
@@ -349,15 +354,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_windowHandles.Add(reg.Handle);
|
||||
|
||||
var rtId = AllocRid();
|
||||
var renderTarget = new RenderWindow(this, rtId);
|
||||
_renderTargets.Add(rtId, new LoadedRenderTarget
|
||||
{
|
||||
Size = reg.FramebufferSize,
|
||||
IsWindow = true,
|
||||
WindowId = reg.Id,
|
||||
IsSrgb = true
|
||||
IsSrgb = true,
|
||||
Instance = new WeakReference<RenderTargetBase>(renderTarget),
|
||||
});
|
||||
|
||||
reg.RenderTarget = new RenderWindow(this, rtId);
|
||||
reg.RenderTarget = renderTarget;
|
||||
|
||||
_glContext!.WindowCreated(glSpec, reg);
|
||||
}
|
||||
@@ -374,6 +381,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (reg.IsDisposed)
|
||||
return;
|
||||
|
||||
_sawmillWin.Debug($"Destroying window {reg.Id}");
|
||||
|
||||
reg.IsDisposed = true;
|
||||
|
||||
_glContext!.WindowDestroyed(reg);
|
||||
@@ -398,10 +407,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_glContext?.SwapAllBuffers();
|
||||
}
|
||||
|
||||
private void VSyncChanged(bool newValue)
|
||||
public bool VsyncEnabled
|
||||
{
|
||||
_vSync = newValue;
|
||||
_glContext?.UpdateVSync();
|
||||
get => _vSync;
|
||||
set
|
||||
{
|
||||
if (_vSync == value)
|
||||
return;
|
||||
|
||||
_vSync = value;
|
||||
_glContext?.UpdateVSync();
|
||||
}
|
||||
}
|
||||
|
||||
private void WindowModeChanged(int mode)
|
||||
|
||||
@@ -114,7 +114,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_proto.PrototypesReloaded += OnProtoReload;
|
||||
|
||||
_cfg.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
|
||||
_cfg.OnValueChanged(CVars.DisplayVSync, VSyncChanged, true);
|
||||
_cfg.OnValueChanged(CVars.DisplayWindowMode, WindowModeChanged, true);
|
||||
_cfg.OnValueChanged(CVars.LightResolutionScale, LightResolutionScaleChanged, true);
|
||||
_cfg.OnValueChanged(CVars.MaxShadowcastingLights, MaxShadowcastingLightsChanged, true);
|
||||
@@ -128,7 +127,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// macOS cannot.
|
||||
if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux())
|
||||
_cfg.OverrideDefault(CVars.DisplayThreadWindowApi, true);
|
||||
|
||||
#if MACOS
|
||||
// Trust macOS to not need threaded window blitting.
|
||||
// (threaded window blitting is a workaround to avoid having to frequently MakeCurrent() on Windows, as it is broken).
|
||||
_cfg.OverrideDefault(CVars.DisplayThreadWindowBlit, false);
|
||||
#endif
|
||||
_threadWindowBlit = _cfg.GetCVar(CVars.DisplayThreadWindowBlit);
|
||||
_threadWindowApi = _cfg.GetCVar(CVars.DisplayThreadWindowApi);
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@ using Robust.Shared.Timing;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -36,6 +34,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public bool IsFocused => true;
|
||||
private readonly List<IClydeWindow> _windows = new();
|
||||
private int _nextWindowId = 2;
|
||||
private long _nextViewportId = 1;
|
||||
|
||||
public ShaderInstance InstanceShader(ShaderSourceResource handle, bool? light = null, ShaderBlendMode? blend = null)
|
||||
{
|
||||
@@ -77,6 +76,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return [];
|
||||
}
|
||||
|
||||
public IEnumerable<(Clyde.RenderTargetBase, Clyde.LoadedRenderTarget)> GetLoadedRenderTextures()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public ClydeDebugLayers DebugLayers { get; set; }
|
||||
|
||||
public string GetKeyName(Keyboard.Key key) => string.Empty;
|
||||
@@ -242,7 +246,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public IClydeViewport CreateViewport(Vector2i size, TextureSampleParameters? sampleParameters,
|
||||
string? name = null)
|
||||
{
|
||||
return new Viewport(size);
|
||||
return new Viewport(_nextViewportId++, size);
|
||||
}
|
||||
|
||||
public IEnumerable<IClydeMonitor> EnumerateMonitors()
|
||||
@@ -309,6 +313,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public IFileDialogManagerImplementation? FileDialogImpl => null;
|
||||
|
||||
public bool VsyncEnabled { get; set; }
|
||||
|
||||
#if TOOLS
|
||||
public void ViewportsClearAllCached()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
#endif // TOOLS
|
||||
|
||||
public void RenderNow(IRenderTarget renderTarget, Action<IRenderHandle> callback)
|
||||
{
|
||||
}
|
||||
|
||||
private sealed class DummyCursor : ICursor
|
||||
{
|
||||
public void Dispose()
|
||||
@@ -398,7 +415,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, in Matrix4 value)
|
||||
private protected override void SetParameterImpl(string name, in Matrix4x4 value)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -484,15 +501,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private sealed class Viewport : IClydeViewport
|
||||
{
|
||||
public Viewport(Vector2i size)
|
||||
public Viewport(long id, Vector2i size)
|
||||
{
|
||||
Size = size;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ClearCachedResources?.Invoke(new ClearCachedViewportResourcesEvent(Id, null));
|
||||
}
|
||||
|
||||
public long Id { get; }
|
||||
|
||||
public IRenderTexture RenderTarget { get; } =
|
||||
new DummyRenderTexture(Vector2i.One, new DummyTexture(Vector2i.One));
|
||||
|
||||
@@ -501,6 +522,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public IEye? Eye { get; set; }
|
||||
public Vector2i Size { get; }
|
||||
public event Action<ClearCachedViewportResourcesEvent>? ClearCachedResources;
|
||||
public Color? ClearColor { get; set; } = Color.Black;
|
||||
public Vector2 RenderScale { get; set; }
|
||||
public bool AutomaticRender { get; set; }
|
||||
|
||||
@@ -102,6 +102,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
var data = _windowData[reg.Id];
|
||||
data.BlitDoneEvent?.Set();
|
||||
// Set events so blit thread properly wakes up and notices it needs to shut down.
|
||||
data.BlitStartEvent?.Set();
|
||||
|
||||
_windowData.Remove(reg.Id);
|
||||
}
|
||||
@@ -326,11 +328,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
reg.RenderTexture?.Dispose();
|
||||
|
||||
reg.RenderTexture = Clyde.CreateRenderTarget(reg.Reg.FramebufferSize, new RenderTargetFormatParameters
|
||||
{
|
||||
ColorFormat = RenderTargetColorFormat.Rgba8Srgb,
|
||||
HasDepthStencil = true
|
||||
});
|
||||
reg.RenderTexture = Clyde.CreateRenderTarget(
|
||||
reg.Reg.FramebufferSize,
|
||||
new RenderTargetFormatParameters
|
||||
{
|
||||
ColorFormat = RenderTargetColorFormat.Rgba8Srgb,
|
||||
HasDepthStencil = true
|
||||
},
|
||||
name: $"{reg.Reg.Id}-RenderTexture");
|
||||
// Necessary to correctly sync multi-context blitting.
|
||||
reg.RenderTexture.MakeGLFence = true;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ using System.Runtime.CompilerServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -277,20 +275,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, in Matrix4 matrix, bool transpose=true)
|
||||
public void SetUniform(string uniformName, in Matrix4x4 matrix, bool transpose=true)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, matrix, transpose);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void SetUniformDirect(int uniformId, in Matrix4 value, bool transpose=true)
|
||||
private unsafe void SetUniformDirect(int uniformId, in Matrix4x4 value, bool transpose=true)
|
||||
{
|
||||
Matrix4 tmpTranspose = value;
|
||||
Matrix4x4 tmpTranspose = value;
|
||||
if (transpose)
|
||||
{
|
||||
// transposition not supported on GLES2, & no access to _hasGLES
|
||||
tmpTranspose.Transpose();
|
||||
tmpTranspose = Matrix4x4.Transpose(value);
|
||||
}
|
||||
GL.UniformMatrix4(uniformId, 1, false, (float*) &tmpTranspose);
|
||||
_clyde.CheckGlError();
|
||||
@@ -551,7 +549,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(string uniformName, in Matrix4 value, bool transpose=true)
|
||||
public void SetUniformMaybe(string uniformName, in Matrix4x4 value, bool transpose=true)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
|
||||
@@ -128,6 +128,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
AddStandardCursor(StandardCursorShape.Hand, CursorShape.Hand);
|
||||
AddStandardCursor(StandardCursorShape.HResize, CursorShape.HResize);
|
||||
AddStandardCursor(StandardCursorShape.VResize, CursorShape.VResize);
|
||||
AddStandardCursor(StandardCursorShape.Progress, CursorShape.Arrow);
|
||||
AddStandardCursor(StandardCursorShape.NWSEResize, CursorShape.Crosshair);
|
||||
AddStandardCursor(StandardCursorShape.NESWResize, CursorShape.Crosshair);
|
||||
AddStandardCursor(StandardCursorShape.Move, CursorShape.Crosshair);
|
||||
AddStandardCursor(StandardCursorShape.NotAllowed, CursorShape.Arrow);
|
||||
AddStandardCursor(StandardCursorShape.NWResize, CursorShape.Crosshair);
|
||||
AddStandardCursor(StandardCursorShape.NResize, CursorShape.VResize);
|
||||
AddStandardCursor(StandardCursorShape.NEResize, CursorShape.Crosshair);
|
||||
AddStandardCursor(StandardCursorShape.EResize, CursorShape.HResize);
|
||||
AddStandardCursor(StandardCursorShape.SEResize, CursorShape.Crosshair);
|
||||
AddStandardCursor(StandardCursorShape.SResize, CursorShape.VResize);
|
||||
AddStandardCursor(StandardCursorShape.SWResize, CursorShape.Crosshair);
|
||||
AddStandardCursor(StandardCursorShape.WResize, CursorShape.HResize);
|
||||
}
|
||||
|
||||
private sealed class CursorImpl : ICursor
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
/* SDL3-CS - C# Bindings for SDL3
|
||||
*
|
||||
* Copyright (c) 2024 Colin Jackson
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied warranty.
|
||||
* In no event will the authors be held liable for any damages arising from
|
||||
* the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software in a
|
||||
* product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
*
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
*
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*
|
||||
* Colin "cryy22" Jackson <c@cryy22.art>
|
||||
*
|
||||
*/
|
||||
@@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SDL3;
|
||||
|
||||
public static partial class SDL
|
||||
{
|
||||
// Extensions to SDL3-CS that aren't part of the main library.
|
||||
|
||||
[LibraryImport(nativeLibName)]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
public static unsafe partial void SDL_SetLogOutputFunction(delegate* unmanaged[Cdecl] <void*, int, SDL_LogPriority, byte*, void> callback, void* userdata);
|
||||
|
||||
[LibraryImport(nativeLibName)]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
public static unsafe partial SDLBool SDL_AddEventWatch(delegate* unmanaged[Cdecl] <void*, SDL_Event*, byte> filter, void* userdata);
|
||||
|
||||
[LibraryImport(nativeLibName)]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
public static unsafe partial void SDL_RemoveEventWatch(delegate* unmanaged[Cdecl] <void*, SDL_Event*, byte> filter, void* userdata);
|
||||
|
||||
[LibraryImport(nativeLibName, StringMarshalling = StringMarshalling.Utf8)]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
public static unsafe partial void SDL_ShowFileDialogWithProperties(int type, delegate* unmanaged[Cdecl]<void*, byte**, int, void> callback, void* userdata, uint properties);
|
||||
|
||||
[LibraryImport(nativeLibName, EntryPoint = "SDL_WaitEvent")]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
public static partial SDLBool SDL_WaitEventRef(ref SDL_Event @event);
|
||||
|
||||
public const byte SDL_BUTTON_LEFT = 1;
|
||||
public const byte SDL_BUTTON_MIDDLE = 2;
|
||||
public const byte SDL_BUTTON_RIGHT = 3;
|
||||
public const byte SDL_BUTTON_X1 = 4;
|
||||
public const byte SDL_BUTTON_X2 = 5;
|
||||
|
||||
public const int SDL_GL_CONTEXT_PROFILE_CORE = 0x0001;
|
||||
public const int SDL_GL_CONTEXT_PROFILE_COMPATIBILITY = 0x0002;
|
||||
public const int SDL_GL_CONTEXT_PROFILE_ES = 0x0004;
|
||||
|
||||
public const int SDL_GL_CONTEXT_DEBUG_FLAG = 0x0001;
|
||||
public const int SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG = 0x0002;
|
||||
public const int SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG = 0x0004;
|
||||
public const int SDL_GL_CONTEXT_RESET_ISOLATION_FLAG = 0x0008;
|
||||
|
||||
public const int SDL_FILEDIALOG_OPENFILE = 0;
|
||||
public const int SDL_FILEDIALOG_SAVEFILE = 1;
|
||||
public const int SDL_FILEDIALOG_OPENFOLDER = 2;
|
||||
|
||||
public const string SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER = "SDL.filedialog.nfilters";
|
||||
public const string SDL_PROP_FILE_DIALOG_FILTERS_POINTER = "SDL.filedialog.filters";
|
||||
|
||||
public static int SDL_VERSIONNUM_MAJOR(int version) => version / 1000000;
|
||||
public static int SDL_VERSIONNUM_MINOR(int version) => version / 1000 % 1000;
|
||||
public static int SDL_VERSIONNUM_MICRO(int version) => version % 1000;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -94,6 +94,19 @@ internal partial class Clyde
|
||||
Add(StandardCursorShape.Hand, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_POINTER);
|
||||
Add(StandardCursorShape.HResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_EW_RESIZE);
|
||||
Add(StandardCursorShape.VResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NS_RESIZE);
|
||||
Add(StandardCursorShape.Progress, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_PROGRESS);
|
||||
Add(StandardCursorShape.NWSEResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NWSE_RESIZE);
|
||||
Add(StandardCursorShape.NESWResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NESW_RESIZE);
|
||||
Add(StandardCursorShape.Move, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_MOVE);
|
||||
Add(StandardCursorShape.NotAllowed, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NOT_ALLOWED);
|
||||
Add(StandardCursorShape.NWResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NW_RESIZE);
|
||||
Add(StandardCursorShape.NResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_N_RESIZE);
|
||||
Add(StandardCursorShape.NEResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NE_RESIZE);
|
||||
Add(StandardCursorShape.EResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_E_RESIZE);
|
||||
Add(StandardCursorShape.SEResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SE_RESIZE);
|
||||
Add(StandardCursorShape.SResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_S_RESIZE);
|
||||
Add(StandardCursorShape.SWResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SW_RESIZE);
|
||||
Add(StandardCursorShape.WResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_W_RESIZE);
|
||||
|
||||
void Add(StandardCursorShape shape, SDL.SDL_SystemCursor sysCursor)
|
||||
{
|
||||
|
||||
@@ -81,6 +81,11 @@ internal partial class Clyde
|
||||
case EventQuit:
|
||||
ProcessEventQuit();
|
||||
break;
|
||||
#if MACOS
|
||||
case EventWindowDestroyed:
|
||||
ProcessEventWindowDestroyed();
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
_sawmill.Error($"Unknown SDL3 event type: {evb.GetType().Name}");
|
||||
break;
|
||||
@@ -255,5 +260,15 @@ internal partial class Clyde
|
||||
{
|
||||
_clyde.SendInputModeChanged();
|
||||
}
|
||||
|
||||
#if MACOS
|
||||
private void ProcessEventWindowDestroyed()
|
||||
{
|
||||
// For some reason, on macOS, closing a secondary window
|
||||
// causes the GL context on the primary thread to crap itself.
|
||||
// Rebinding it seems to fix it.
|
||||
GLMakeContextCurrent(_clyde._mainWindow);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,10 @@ internal partial class Clyde
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Giving a parent window is required to avoid the file dialog being blocking on macOS.
|
||||
var mainWindow = (Sdl3WindowReg)_clyde._mainWindow!;
|
||||
SDL.SDL_SetPointerProperty(props, SDL.SDL_PROP_FILE_DIALOG_WINDOW_POINTER, mainWindow.Sdl3Window);
|
||||
|
||||
var task = ShowFileDialogWithProperties(type, props);
|
||||
|
||||
SDL.SDL_DestroyProperties(props);
|
||||
|
||||
@@ -278,5 +278,9 @@ internal partial class Clyde
|
||||
private sealed class EventKeyMapChanged : EventBase;
|
||||
|
||||
private sealed class EventQuit : EventBase;
|
||||
|
||||
#if MACOS
|
||||
private sealed class EventWindowDestroyed : EventBase;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@ using Robust.Shared.Maths;
|
||||
using SDL3;
|
||||
using TerraFX.Interop.Windows;
|
||||
using TerraFX.Interop.Xlib;
|
||||
#if WINDOWS
|
||||
using BOOL = TerraFX.Interop.Windows.BOOL;
|
||||
using Windows = TerraFX.Interop.Windows.Windows;
|
||||
#endif
|
||||
using GLAttr = SDL3.SDL.SDL_GLAttr;
|
||||
using X11Window = TerraFX.Interop.Xlib.Window;
|
||||
|
||||
@@ -142,9 +144,12 @@ internal partial class Clyde
|
||||
});
|
||||
}
|
||||
|
||||
private static void WinThreadWinDestroy(CmdWinDestroy cmd)
|
||||
private void WinThreadWinDestroy(CmdWinDestroy cmd)
|
||||
{
|
||||
SDL.SDL_DestroyWindow(cmd.Window);
|
||||
#if MACOS
|
||||
SendEvent(new EventWindowDestroyed());
|
||||
#endif
|
||||
}
|
||||
|
||||
private (nint window, nint context) CreateSdl3WindowForRenderer(
|
||||
@@ -461,6 +466,7 @@ internal partial class Clyde
|
||||
var reg = (Sdl3WindowReg)window;
|
||||
var windowPtr = WinPtr(reg);
|
||||
|
||||
#if WINDOWS
|
||||
// On Windows, SwapBuffers does not correctly sync to the DWM compositor.
|
||||
// This means OpenGL vsync is effectively broken by default on Windows.
|
||||
// We manually sync via DwmFlush(). GLFW does this automatically, SDL3 does not.
|
||||
@@ -473,7 +479,7 @@ internal partial class Clyde
|
||||
var dwmFlush = false;
|
||||
var swapInterval = 0;
|
||||
|
||||
if (OperatingSystem.IsWindows() && !reg.Fullscreen && reg.SwapInterval > 0)
|
||||
if (!reg.Fullscreen && reg.SwapInterval > 0)
|
||||
{
|
||||
BOOL compositing;
|
||||
// 6.2 is Windows 8
|
||||
@@ -492,9 +498,12 @@ internal partial class Clyde
|
||||
swapInterval = reg.SwapInterval;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//_sawmill.Debug($"Swapping: {window.Id} @ {_clyde._gameTiming.CurFrame}");
|
||||
SDL.SDL_GL_SwapWindow(windowPtr);
|
||||
|
||||
#if WINDOWS
|
||||
if (dwmFlush)
|
||||
{
|
||||
var i = swapInterval;
|
||||
@@ -505,6 +514,7 @@ internal partial class Clyde
|
||||
|
||||
SDL.SDL_GL_SetSwapInterval(swapInterval);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public uint? WindowGetX11Id(WindowReg window)
|
||||
@@ -547,17 +557,18 @@ internal partial class Clyde
|
||||
|
||||
public void TextInputSetRect(WindowReg reg, UIBox2i rect, int cursor)
|
||||
{
|
||||
var ratio = ((Sdl3WindowReg)reg).PixelRatio;
|
||||
SendCmd(new CmdTextInputSetRect
|
||||
{
|
||||
Window = WinPtr(reg),
|
||||
Rect = new SDL.SDL_Rect
|
||||
{
|
||||
x = rect.Left,
|
||||
y = rect.Top,
|
||||
w = rect.Width,
|
||||
h = rect.Height
|
||||
x = (int)(rect.Left / ratio.X),
|
||||
y = (int)(rect.Top / ratio.Y),
|
||||
w = (int)(rect.Width / ratio.X),
|
||||
h = (int)(rect.Height / ratio.Y)
|
||||
},
|
||||
Cursor = cursor
|
||||
Cursor = (int)(cursor / ratio.X)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,10 @@ internal partial class Clyde
|
||||
// https://github.com/libsdl-org/SDL/issues/11813
|
||||
SDL.SDL_SetHint(SDL.SDL_HINT_WINDOWS_GAMEINPUT, "0");
|
||||
|
||||
#if MACOS
|
||||
SDL.SDL_SetHint(SDL.SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH, "1");
|
||||
#endif
|
||||
|
||||
var res = SDL.SDL_Init(SDL.SDL_InitFlags.SDL_INIT_VIDEO | SDL.SDL_InitFlags.SDL_INIT_EVENTS);
|
||||
if (!res)
|
||||
{
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace Robust.Client.Graphics
|
||||
|
||||
Texture GetStockTexture(ClydeStockTexture stockTexture);
|
||||
IEnumerable<(Clyde.Clyde.ClydeTexture, Clyde.Clyde.LoadedTexture)> GetLoadedTextures();
|
||||
IEnumerable<(Clyde.Clyde.RenderTargetBase, Clyde.Clyde.LoadedRenderTarget)> GetLoadedRenderTextures();
|
||||
|
||||
ClydeDebugLayers DebugLayers { get; set; }
|
||||
|
||||
@@ -72,5 +73,20 @@ namespace Robust.Client.Graphics
|
||||
void RunOnWindowThread(Action action);
|
||||
|
||||
IFileDialogManagerImplementation? FileDialogImpl { get; }
|
||||
|
||||
bool VsyncEnabled { get; set; }
|
||||
|
||||
// Viewports
|
||||
|
||||
#if TOOLS
|
||||
|
||||
/// <summary>
|
||||
/// Fires <see cref="IClydeViewport.ClearCachedResources"/> on all viewports. For debugging.
|
||||
/// </summary>
|
||||
void ViewportsClearAllCached();
|
||||
|
||||
#endif // TOOLS
|
||||
|
||||
void RenderNow(IRenderTarget renderTarget, Action<IRenderHandle> callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
public interface IClydeViewport : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// A unique ID for this viewport. No other viewport with this ID can ever exist in the app lifetime.
|
||||
/// </summary>
|
||||
long Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The render target that is rendered to when rendering this viewport.
|
||||
/// </summary>
|
||||
@@ -22,6 +27,16 @@ namespace Robust.Client.Graphics
|
||||
IEye? Eye { get; set; }
|
||||
Vector2i Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the viewport indicates that any cached rendering resources (e.g. render targets)
|
||||
/// should be purged.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This event is raised if the viewport is disposed (manually or via finalization).
|
||||
/// However, code should expect this event to be raised at any time, even if the viewport is not disposed fully.
|
||||
/// </remarks>
|
||||
event Action<ClearCachedViewportResourcesEvent> ClearCachedResources;
|
||||
|
||||
/// <summary>
|
||||
/// Color to clear the render target to before rendering. If null, no clearing will happen.
|
||||
/// </summary>
|
||||
@@ -85,4 +100,23 @@ namespace Robust.Client.Graphics
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds);
|
||||
}
|
||||
|
||||
public struct ClearCachedViewportResourcesEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IClydeViewport.Id"/> of the viewport.
|
||||
/// </summary>
|
||||
public readonly long ViewportId;
|
||||
|
||||
/// <summary>
|
||||
/// The viewport itself. This is not available if the viewport was disposed.
|
||||
/// </summary>
|
||||
public readonly IClydeViewport? Viewport;
|
||||
|
||||
internal ClearCachedViewportResourcesEvent(long viewportId, IClydeViewport? viewport)
|
||||
{
|
||||
ViewportId = viewportId;
|
||||
Viewport = viewport;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -169,7 +167,7 @@ namespace Robust.Client.Graphics
|
||||
SetParameterImpl(name, value);
|
||||
}
|
||||
|
||||
public void SetParameter(string name, in Matrix4 value)
|
||||
public void SetParameter(string name, in Matrix4x4 value)
|
||||
{
|
||||
EnsureAlive();
|
||||
EnsureMutable();
|
||||
@@ -236,7 +234,7 @@ namespace Robust.Client.Graphics
|
||||
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, in Matrix4x4 value);
|
||||
private protected abstract void SetParameterImpl(string name, Texture value);
|
||||
private protected abstract void SetStencilImpl(StencilParameters value);
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -222,7 +220,7 @@ namespace Robust.Client.Graphics
|
||||
case Matrix3x2 i:
|
||||
instance.SetParameter(key, i);
|
||||
break;
|
||||
case Matrix4 i:
|
||||
case Matrix4x4 i:
|
||||
instance.SetParameter(key, i);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
IBeam,
|
||||
|
||||
/// <summary>
|
||||
/// Alias for <see cref="IBeam"/>.
|
||||
/// </summary>
|
||||
Text = IBeam,
|
||||
|
||||
/// <summary>
|
||||
/// The crosshair shape. Used when dragging and dropping.
|
||||
/// </summary>
|
||||
@@ -25,16 +30,135 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
Hand,
|
||||
|
||||
/// <summary>
|
||||
/// Alias for <see cref="Hand"/>
|
||||
/// </summary>
|
||||
Pointer = Hand,
|
||||
|
||||
/// <summary>
|
||||
/// The horizontal resize shape. Used when mousing over something that can be horizontally resized.
|
||||
/// </summary>
|
||||
HResize,
|
||||
|
||||
/// <summary>
|
||||
/// Alias for <see cref="EWResize"/>
|
||||
/// </summary>
|
||||
EWResize = HResize,
|
||||
|
||||
/// <summary>
|
||||
/// The vertical resize shape. Used when mousing over something that can be vertically resized.
|
||||
/// </summary>
|
||||
VResize,
|
||||
|
||||
/// <summary>
|
||||
/// Alias for <see cref="VResize"/>.
|
||||
/// </summary>
|
||||
NSResize = VResize,
|
||||
|
||||
/// <summary>
|
||||
/// Program is busy doing something.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This cursor is not always available and may be substituted.
|
||||
/// </remarks>
|
||||
Progress,
|
||||
|
||||
/// <summary>
|
||||
/// Diagonal resize shape for northwest-southeast resizing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This cursor is not always available and may be substituted.
|
||||
/// </remarks>
|
||||
NWSEResize,
|
||||
|
||||
/// <summary>
|
||||
/// Diagonal resize shape for northeast-southwest resizing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This cursor is not always available and may be substituted.
|
||||
/// </remarks>
|
||||
NESWResize,
|
||||
|
||||
/// <summary>
|
||||
/// 4-way arrow move icon.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This cursor is not always available and may be substituted.
|
||||
/// </remarks>
|
||||
Move,
|
||||
|
||||
/// <summary>
|
||||
/// An action is not allowed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This cursor is not always available and may be substituted.
|
||||
/// </remarks>
|
||||
NotAllowed,
|
||||
|
||||
/// <summary>
|
||||
/// One-directional resize to the northwest.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This cursor is not always available and may be substituted.
|
||||
/// </remarks>
|
||||
NWResize,
|
||||
|
||||
/// <summary>
|
||||
/// One-directional resize to the north.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This cursor is not always available and may be substituted.
|
||||
/// </remarks>
|
||||
NResize,
|
||||
|
||||
/// <summary>
|
||||
/// One-directional resize to the northeast.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This cursor is not always available and may be substituted.
|
||||
/// </remarks>
|
||||
NEResize,
|
||||
|
||||
/// <summary>
|
||||
/// One-directional resize to the east.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This cursor is not always available and may be substituted.
|
||||
/// </remarks>
|
||||
EResize,
|
||||
|
||||
/// <summary>
|
||||
/// One-directional resize to the southeast.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This cursor is not always available and may be substituted.
|
||||
/// </remarks>
|
||||
SEResize,
|
||||
|
||||
/// <summary>
|
||||
/// One-directional resize to the south.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This cursor is not always available and may be substituted.
|
||||
/// </remarks>
|
||||
SResize,
|
||||
|
||||
/// <summary>
|
||||
/// One-directional resize to the southwest.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This cursor is not always available and may be substituted.
|
||||
/// </remarks>
|
||||
SWResize,
|
||||
|
||||
/// <summary>
|
||||
/// One-directional resize to the west.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This cursor is not always available and may be substituted.
|
||||
/// </remarks>
|
||||
WResize,
|
||||
|
||||
/// <summary>
|
||||
/// Not a real value
|
||||
/// </summary>
|
||||
|
||||
@@ -197,6 +197,13 @@ namespace Robust.Client.Input
|
||||
locId += "-linux";
|
||||
}
|
||||
|
||||
#if MACOS
|
||||
if (key == Key.Alt)
|
||||
{
|
||||
locId += "-mac";
|
||||
}
|
||||
#endif
|
||||
|
||||
if (loc.TryGetString(locId, out var name))
|
||||
return name;
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -18,6 +21,10 @@ namespace Robust.Client.Placement
|
||||
PlacementMode? CurrentMode { get; set; }
|
||||
PlacementInformation? CurrentPermission { get; set; }
|
||||
|
||||
IEntityManager EntityManager { get; }
|
||||
IEyeManager EyeManager { get; }
|
||||
IMapManager MapManager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The direction to spawn the entity in (presently exposed for EntitySpawnWindow UI)
|
||||
/// </summary>
|
||||
@@ -49,5 +56,15 @@ namespace Robust.Client.Placement
|
||||
void ToggleEraserHijacked(PlacementHijack hijack);
|
||||
|
||||
void FrameUpdate(FrameEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// The name of the placement mode option to just use the default for the selected entity.
|
||||
/// </summary>
|
||||
const string DefaultModeName = "Default";
|
||||
|
||||
/// <summary>
|
||||
/// An array of the names of all available placement modes.
|
||||
/// </summary>
|
||||
string[] AllModeNames { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Map;
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Map;
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user