mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
170 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdc8832e4c | ||
|
|
033205f7df | ||
|
|
b2ab349e1c | ||
|
|
6fea24f474 | ||
|
|
dbde8023ed | ||
|
|
83279ff285 | ||
|
|
14439784dd | ||
|
|
80cad0cd8f | ||
|
|
b15d960c69 | ||
|
|
cd59027089 | ||
|
|
015bf3318b | ||
|
|
3f19d25018 | ||
|
|
fe19ff9bd5 | ||
|
|
c95b4320cf | ||
|
|
4755cb5747 | ||
|
|
7bc0ffb711 | ||
|
|
e49515956a | ||
|
|
f3a3f564e1 | ||
|
|
8b7fbfa646 | ||
|
|
bb4c4ed302 | ||
|
|
c9009342b6 | ||
|
|
3a337e4842 | ||
|
|
e788325cdb | ||
|
|
9a0e3b6b02 | ||
|
|
37eabbabc2 | ||
|
|
ab775af7cd | ||
|
|
8ac5fc58d2 | ||
|
|
37c7aa544e | ||
|
|
7542b1ca16 | ||
|
|
8235bd8478 | ||
|
|
1657a49c1c | ||
|
|
669b515ce6 | ||
|
|
8478e62a3e | ||
|
|
034728258c | ||
|
|
b0fec0fd76 | ||
|
|
665294bee8 | ||
|
|
4b04081749 | ||
|
|
d3a9199b8e | ||
|
|
6fb9ff7554 | ||
|
|
feb9e1db69 | ||
|
|
613705613b | ||
|
|
80a053c0a9 | ||
|
|
657455dae0 | ||
|
|
8ae35e12ee | ||
|
|
4e2c0e431b | ||
|
|
9c41f19eaf | ||
|
|
f75ce13f00 | ||
|
|
ac45a0a64b | ||
|
|
a8a73e28f4 | ||
|
|
e5983a9ec1 | ||
|
|
b7fa39d8cc | ||
|
|
3c30ed749c | ||
|
|
eb1a2ae9b4 | ||
|
|
ee0c31a8c3 | ||
|
|
4ab61b840a | ||
|
|
df29fa438a | ||
|
|
a3756c29bd | ||
|
|
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 | ||
|
|
ebce0daa1b | ||
|
|
c876eb1f4c | ||
|
|
1037fc735e | ||
|
|
d5df765467 | ||
|
|
93cf9f4227 | ||
|
|
d2977e2a63 | ||
|
|
a3f0ea19c4 | ||
|
|
d9032b8757 | ||
|
|
cba6e37f9f | ||
|
|
90ec9a80c9 | ||
|
|
7eaf2f590b | ||
|
|
0439ea9893 |
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.3" />
|
||||
<PackageVersion Include="Robust.Natives.Zstd" Version="0.1.1-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" />
|
||||
@@ -57,9 +58,13 @@
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.0" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.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="SpaceWizards.Fontconfig.Interop" Version="1.0.0" />
|
||||
<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" />
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</When>
|
||||
<Otherwise>
|
||||
<PropertyGroup>
|
||||
<DefineConstants>$(DefineConstants);LINUX;UNIX</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);LINUX;UNIX;FREEDESKTOP</DefineConstants>
|
||||
</PropertyGroup>
|
||||
</Otherwise>
|
||||
</Choose>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -31,5 +31,6 @@
|
||||
<Python>python3</Python>
|
||||
<Python Condition="'$(ActualOS)' == 'Windows'">py -3</Python>
|
||||
<UseSystemSqlite Condition="'$(TargetOS)' == 'FreeBSD'">True</UseSystemSqlite>
|
||||
<IsFreedesktop Condition="'$(TargetOS)' == 'FreeBSD' Or '$(TargetOS)' == 'Linux'">True</IsFreedesktop>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
238
RELEASE-NOTES.md
238
RELEASE-NOTES.md
@@ -54,6 +54,244 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 268.0.2
|
||||
|
||||
|
||||
## 268.0.1
|
||||
|
||||
|
||||
## 268.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Events that are raised via `IEventBus.RaiseComponentEvent()` now **must** be annotated with the `ComponentEventAttribute`.
|
||||
* By default, events annotated with this attribute can **only** be raised via `IEventBus.RaiseComponentEvent()`. This can be configured via `ComponentEventAttribute.Exclusive`
|
||||
* StartCollide and EndCollide events are now buffered until the end of physics substeps instead of being raised during the CollideContacts step. EndCollide events are double-buffered and any new ones raised while the events are being dispatched will now go out on the next tick / substep.
|
||||
|
||||
### New features
|
||||
|
||||
* Added `IUserInterfaceManager.ControlSawmill` and `Control.Log` properties so that controls can easily use logging without using static methods.
|
||||
|
||||
|
||||
## 267.4.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added two new custom yaml serializers `CustomListSerializer` and `CustomArraySerializer`.
|
||||
* CVars defined in `[CVarDefs]` can now be private or internal.
|
||||
* Added config rollback system to `IConfigurationManager`. This enables CVars to be snapshot and rolled back, even in the event of client crash.
|
||||
* `OptionButton` now has a `Filterable` property that gives it a text box to filter options.
|
||||
* Added `FontTagHijackHolder` to replace fonts resolved by `FontTag`.
|
||||
* Sandbox:
|
||||
* Exposed `System.Reflection.Metadata.MetadataUpdateHandlerAttribute`.
|
||||
* Exposed more overloads on `StringBuilder`.
|
||||
* The engine can now load system fonts.
|
||||
* At the moment only available on Windows.
|
||||
* See `ISystemFontManager` for API.
|
||||
* The client now display a loading screen during startup.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `Menu` and `NumpadDecimal` key codes on SDL3.
|
||||
* client-side predicted entity deletion ( `EntityManager.PredictedQueueDeleteEntity`) now behaves more like it does on the server. In particular, entities will be deleted on the same tick after all system have been updated. Previously, it would process deletions at the beginning of the next tick.
|
||||
* Fix modifying `Label.FontOverride` not causing a layout update.
|
||||
* Controls created by rich-text tags now get arranged to a proper size.
|
||||
* Fix `OutputPanel` scrollbar breaking if a style update changes the font size.
|
||||
|
||||
### Other
|
||||
|
||||
* ComponentNameSerializer will now ignore any components that have been ignored via `IComponentFactory.RegisterIgnore`.
|
||||
* Add pure to some SharedTransformSystem methods.
|
||||
* Significantly optimised collision detection in SharedBroadphaseSystem.
|
||||
* `Control.Stylesheet` does not do any work if assigning the value it already has.
|
||||
* XAML hot reload now JITs UIs when first opened rather than doing every single one at client startup. This reduces dev startup overhead significantly and probably helps with memory usage too.
|
||||
|
||||
### Internal
|
||||
|
||||
* The `dmetamem` command now sorts its output, and doesn't output to log anymore to avoid output interleaving.
|
||||
|
||||
|
||||
## 267.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* Sandbox:
|
||||
* Added `System.DateOnly` and `System.TimeOnly`.
|
||||
* `MapId`, `MapCoordinates`, and `EntityCoordinates` are now yaml serialisable
|
||||
* The base component tree lookup system has new methods including several new `QueryAabb()` overloads that take in a collection and various new `IntersectRay()` overloads that should replace `IntersectRayWithPredicate`.
|
||||
* Added `OccluderSystem.InRangeUnoccluded()` for checking for occluders that lie between two points.
|
||||
* `LocalizedCommands` now pass the command name as an argument to the localized help text.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `MapLoaderSystem.SerializeEntitiesRecursive()` not properly serialising when given multiple root entities (e.g., multiple maps)
|
||||
* Fixed yaml hot reloading throwing invalid path exceptions.
|
||||
* The `EntityManager.CreateEntityUninitialized` overload that uses MapCoordinates now actually attaches entities to a grid if one is present at those coordinates, as was stated in it's documentation.
|
||||
* Fixed physics joint relays not being properly updated when an entity is removed from a container.
|
||||
|
||||
### Other
|
||||
|
||||
* Updated natives again to attempt to fix issues caused by the previous update.
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
@@ -1,16 +1,16 @@
|
||||
# Loc strings for various entity state & client-side PVS related commands
|
||||
|
||||
cmd-reset-ent-help = Usage: resetent <Entity UID>
|
||||
cmd-reset-ent-desc = Reset an entity to the most recently received server state. This will also reset entities that have been detached to null-space.
|
||||
cmd-reset-ent-help = Usage: {$command} <Entity UID>
|
||||
cmd-reset-ent-desc = Reset an entity to the most recently received server state. This will also reset entities that have been detached to null-space.
|
||||
|
||||
cmd-reset-all-ents-help = Usage: resetallents
|
||||
cmd-reset-all-ents-desc = Resets all entities to the most recently received server state. This only impacts entities that have not been detached to null-space.
|
||||
cmd-reset-all-ents-help = Usage: {$command}
|
||||
cmd-reset-all-ents-desc = Resets all entities to the most recently received server state. This only impacts entities that have not been detached to null-space.
|
||||
|
||||
cmd-detach-ent-help = Usage: detachent <Entity UID>
|
||||
cmd-detach-ent-help = Usage: {$command} <Entity UID>
|
||||
cmd-detach-ent-desc = Detach an entity to null-space, as if it had left PVS range.
|
||||
|
||||
cmd-local-delete-help = Usage: localdelete <Entity UID>
|
||||
cmd-local-delete-help = Usage: {$command} <Entity UID>
|
||||
cmd-local-delete-desc = Deletes an entity. Unlike the normal delete command, this is CLIENT-SIDE. Unless the entity is a client-side entity, this will likely cause errors.
|
||||
|
||||
cmd-full-state-reset-help = Usage: fullstatereset
|
||||
cmd-full-state-reset-help = Usage: {$command}
|
||||
cmd-full-state-reset-desc = Discards any entity state information and requests a full-state from the server.
|
||||
|
||||
@@ -23,8 +23,8 @@ cmd-error-dir-not-found = Could not find directory: {$dir}.
|
||||
cmd-failure-no-attached-entity = There is no entity attached to this shell.
|
||||
|
||||
## 'help' command
|
||||
cmd-help-desc = Display general help or help text for a specific command
|
||||
cmd-help-help = Usage: help [command name]
|
||||
cmd-help-desc = Display general help or help text for a specific command.
|
||||
cmd-help-help = Usage: {$command} [command name]
|
||||
When no command name is provided, displays general-purpose help text. If a command name is provided, displays help text for that command.
|
||||
|
||||
cmd-help-no-args = To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'. To search for commands, use 'list <filter>'.
|
||||
@@ -35,7 +35,7 @@ cmd-help-arg-cmdname = [command name]
|
||||
|
||||
## 'cvar' command
|
||||
cmd-cvar-desc = Gets or sets a CVar.
|
||||
cmd-cvar-help = Usage: cvar <name | ?> [value]
|
||||
cmd-cvar-help = Usage: {$command} <name | ?> [value]
|
||||
If a value is passed, the value is parsed and stored as the new value of the CVar.
|
||||
If not, the current value of the CVar is displayed.
|
||||
Use 'cvar ?' to get a list of all registered CVars.
|
||||
@@ -49,14 +49,14 @@ cmd-cvar-value-hidden = <value hidden>
|
||||
|
||||
## 'cvar_subs' command
|
||||
cmd-cvar_subs-desc = Lists the OnValueChanged subscriptions for a CVar.
|
||||
cmd-cvar_subs-help = Usage: cvar_subs <name>
|
||||
cmd-cvar_subs-help = Usage: {$command} <name>
|
||||
|
||||
cmd-cvar_subs-invalid-args = Must provide exactly one argument.
|
||||
cmd-cvar_subs-arg-name = <name>
|
||||
|
||||
## 'list' command
|
||||
cmd-list-desc = Lists available commands, with optional search filter
|
||||
cmd-list-help = Usage: list [filter]
|
||||
cmd-list-desc = Lists available commands, with optional search filter.
|
||||
cmd-list-help = Usage: {$command} [filter]
|
||||
Lists all available commands. If an argument is provided, it will be used to filter commands by name.
|
||||
|
||||
cmd-list-heading = SIDE NAME DESC{"\u000A"}-------------------------{"\u000A"}
|
||||
@@ -64,13 +64,13 @@ cmd-list-heading = SIDE NAME DESC{"\u000A"}-------------------------{
|
||||
cmd-list-arg-filter = [filter]
|
||||
|
||||
## '>' command, aka remote exec
|
||||
cmd-remoteexec-desc = Executes server-side commands
|
||||
cmd-remoteexec-desc = Executes server-side commands.
|
||||
cmd-remoteexec-help = Usage: > <command> [arg] [arg] [arg...]
|
||||
Executes a command on the server. This is necessary if a command with the same name exists on the client, as simply running the command would run the client command first.
|
||||
|
||||
## 'gc' command
|
||||
cmd-gc-desc = Run the GC (Garbage Collector)
|
||||
cmd-gc-help = Usage: gc [generation]
|
||||
cmd-gc-desc = Run the GC (Garbage Collector).
|
||||
cmd-gc-help = Usage: {$command} [generation]
|
||||
Uses GC.Collect() to execute the Garbage Collector.
|
||||
If an argument is provided, it is parsed as a GC generation number and GC.Collect(int) is used.
|
||||
Use the 'gfc' command to do an LOH-compacting full GC.
|
||||
@@ -79,13 +79,13 @@ cmd-gc-arg-generation = [generation]
|
||||
|
||||
## 'gcf' command
|
||||
cmd-gcf-desc = Run the GC, fully, compacting LOH and everything.
|
||||
cmd-gcf-help = Usage: gcf
|
||||
cmd-gcf-help = Usage: {$command}
|
||||
Does a full GC.Collect(2, GCCollectionMode.Forced, true, true) while also compacting LOH.
|
||||
This will probably lock up for hundreds of milliseconds, be warned.
|
||||
|
||||
## 'gc_mode' command
|
||||
cmd-gc_mode-desc = Change/Read the GC Latency mode
|
||||
cmd-gc_mode-help = Usage: gc_mode [type]
|
||||
cmd-gc_mode-desc = Change/Read the GC Latency mode.
|
||||
cmd-gc_mode-help = Usage: {$command} [type]
|
||||
If no argument is provided, returns the current GC latency mode.
|
||||
If an argument is passed, it is parsed as GCLatencyMode and set as the GC latency mode.
|
||||
|
||||
@@ -98,8 +98,8 @@ cmd-gc_mode-result = resulting gc latency mode: { $mode }
|
||||
cmd-gc_mode-arg-type = [type]
|
||||
|
||||
## 'mem' command
|
||||
cmd-mem-desc = Prints managed memory info
|
||||
cmd-mem-help = Usage: mem
|
||||
cmd-mem-desc = Prints managed memory info.
|
||||
cmd-mem-help = Usage: {$command}
|
||||
|
||||
cmd-mem-report = Heap Size: { TOSTRING($heapSize, "N0") }
|
||||
Total Allocated: { TOSTRING($totalAllocated, "N0") }
|
||||
@@ -108,26 +108,26 @@ cmd-mem-report = Heap Size: { TOSTRING($heapSize, "N0") }
|
||||
cmd-physics-overlay = {$overlay} is not a recognised overlay
|
||||
|
||||
## 'lsasm' command
|
||||
cmd-lsasm-desc = Lists loaded assemblies by load context
|
||||
cmd-lsasm-desc = Lists loaded assemblies by load context.
|
||||
cmd-lsasm-help = Usage: lsasm
|
||||
|
||||
## 'exec' command
|
||||
cmd-exec-desc = Executes a script file from the game's writeable user data
|
||||
cmd-exec-help = Usage: exec <fileName>
|
||||
cmd-exec-desc = Executes a script file from the game's writeable user data.
|
||||
cmd-exec-help = Usage: {$command} <fileName>
|
||||
Each line in the file is executed as a single command, unless it starts with a #
|
||||
|
||||
cmd-exec-arg-filename = <fileName>
|
||||
|
||||
## 'dump_net_comps' command
|
||||
cmd-dump_net_comps-desc = Prints the table of networked components.
|
||||
cmd-dump_net_comps-help = Usage: dump_net-comps
|
||||
cmd-dump_net_comps-help = Usage: {$command}
|
||||
|
||||
cmd-dump_net_comps-error-writeable = Registration still writeable, network ids have not been generated.
|
||||
cmd-dump_net_comps-header = Networked Component Registrations:
|
||||
|
||||
## 'dump_event_tables' command
|
||||
cmd-dump_event_tables-desc = Prints directed event tables for an entity.
|
||||
cmd-dump_event_tables-help = Usage: dump_event_tables <entityUid>
|
||||
cmd-dump_event_tables-help = Usage: {$command} <entityUid>
|
||||
|
||||
cmd-dump_event_tables-missing-arg-entity = Missing entity argument
|
||||
cmd-dump_event_tables-error-entity = Invalid entity
|
||||
@@ -135,7 +135,7 @@ cmd-dump_event_tables-arg-entity = <entityUid>
|
||||
|
||||
## 'monitor' command
|
||||
cmd-monitor-desc = Toggles a debug monitor in the F3 menu.
|
||||
cmd-monitor-help = Usage: monitor <name>
|
||||
cmd-monitor-help = Usage: {$command} <name>
|
||||
Possible monitors are: { $monitors }
|
||||
You can also use the special values "-all" and "+all" to hide or show all monitors, respectively.
|
||||
|
||||
@@ -148,13 +148,13 @@ cmd-monitor-plus-all-hint = Shows all monitors
|
||||
|
||||
## 'setambientlight' command
|
||||
cmd-set-ambient-light-desc = Allows you to set the ambient light for the specified map, in SRGB.
|
||||
cmd-set-ambient-light-help = setambientlight [mapid] [r g b a]
|
||||
cmd-set-ambient-light-help = Usage: {$command} [mapid] [r g b a]
|
||||
cmd-set-ambient-light-parse = Unable to parse args as a byte values for a color.
|
||||
|
||||
## Mapping commands
|
||||
|
||||
cmd-savemap-desc = Serializes a map to disk. Will not save a post-init map unless forced.
|
||||
cmd-savemap-help = savemap <MapID> <Path> [force]
|
||||
cmd-savemap-help = Usage: {$command} <MapID> <Path> [force]
|
||||
cmd-savemap-not-exist = Target map does not exist.
|
||||
cmd-savemap-init-warning = Attempted to save a post-init map without forcing the save.
|
||||
cmd-savemap-attempt = Attempting to save map {$mapId} to {$path}.
|
||||
@@ -165,7 +165,7 @@ cmd-hint-savemap-path = <Path>
|
||||
cmd-hint-savemap-force = [bool]
|
||||
|
||||
cmd-loadmap-desc = Loads a map from disk into the game.
|
||||
cmd-loadmap-help = loadmap <MapID> <Path> [x] [y] [rotation] [consistentUids]
|
||||
cmd-loadmap-help = Usage: {$command} <MapID> <Path> [x] [y] [rotation] [consistentUids]
|
||||
cmd-loadmap-nullspace = You cannot load into map 0.
|
||||
cmd-loadmap-exists = Map {$mapId} already exists.
|
||||
cmd-loadmap-success = Map {$mapId} has been loaded from {$path}.
|
||||
@@ -180,73 +180,74 @@ cmd-hint-savebp-id = <Grid EntityID>
|
||||
## 'flushcookies' command
|
||||
# Note: the flushcookies command is from Robust.Client.WebView, it's not in the main engine code.
|
||||
|
||||
cmd-flushcookies-desc = Flush CEF cookie storage to disk
|
||||
cmd-flushcookies-help = This ensure cookies are properly saved to disk in the event of unclean shutdowns.
|
||||
cmd-flushcookies-desc = Flush CEF cookie storage to disk.
|
||||
cmd-flushcookies-help = Usage: {$command}
|
||||
This ensure cookies are properly saved to disk in the event of unclean shutdowns.
|
||||
Note that the actual operation is asynchronous.
|
||||
|
||||
cmd-ldrsc-desc = Pre-caches a resource.
|
||||
cmd-ldrsc-help = Usage: ldrsc <path> <type>
|
||||
cmd-ldrsc-help = Usage: {$command} <path> <type>
|
||||
|
||||
cmd-rldrsc-desc = Reloads a resource.
|
||||
cmd-rldrsc-help = Usage: rldrsc <path> <type>
|
||||
cmd-rldrsc-help = Usage: {$command} <path> <type>
|
||||
|
||||
cmd-gridtc-desc = Gets the tile count of a grid.
|
||||
cmd-gridtc-help = Usage: gridtc <gridId>
|
||||
cmd-gridtc-help = Usage: {$command} <gridId>
|
||||
|
||||
|
||||
# Client-side commands
|
||||
cmd-guidump-desc = Dump GUI tree to /guidump.txt in user data.
|
||||
cmd-guidump-help = Usage: guidump
|
||||
cmd-guidump-help = Usage: {$command}
|
||||
|
||||
cmd-uitest-desc = Open a dummy UI testing window
|
||||
cmd-uitest-help = Usage: uitest
|
||||
cmd-uitest-desc = Open a dummy UI testing window.
|
||||
cmd-uitest-help = Usage: {$command}
|
||||
|
||||
## 'uitest2' command
|
||||
cmd-uitest2-desc = Opens a UI control testing OS window
|
||||
cmd-uitest2-help = Usage: uitest2 <tab>
|
||||
cmd-uitest2-desc = Opens a UI control testing OS window.
|
||||
cmd-uitest2-help = Usage: {$command} <tab>
|
||||
cmd-uitest2-arg-tab = <tab>
|
||||
cmd-uitest2-error-args = Expected at most one argument
|
||||
cmd-uitest2-error-tab = Invalid tab: '{$value}'
|
||||
cmd-uitest2-title = UITest2
|
||||
|
||||
|
||||
cmd-setclipboard-desc = Sets the system clipboard
|
||||
cmd-setclipboard-help = Usage: setclipboard <text>
|
||||
cmd-setclipboard-desc = Sets the system clipboard.
|
||||
cmd-setclipboard-help = Usage: {$command} <text>
|
||||
|
||||
cmd-getclipboard-desc = Gets the system clipboard
|
||||
cmd-getclipboard-help = Usage: Getclipboard
|
||||
cmd-getclipboard-desc = Gets the system clipboard.
|
||||
cmd-getclipboard-help = Usage: {$command}
|
||||
|
||||
cmd-togglelight-desc = Toggles light rendering.
|
||||
cmd-togglelight-help = Usage: togglelight
|
||||
cmd-togglelight-help = Usage: {$command}
|
||||
|
||||
cmd-togglefov-desc = Toggles fov for client.
|
||||
cmd-togglefov-help = Usage: togglefov
|
||||
cmd-togglefov-help = Usage: {$command}
|
||||
|
||||
cmd-togglehardfov-desc = Toggles hard fov for client. (for debugging space-station-14#2353)
|
||||
cmd-togglehardfov-help = Usage: togglehardfov
|
||||
cmd-togglehardfov-help = Usage: {$command}
|
||||
|
||||
cmd-toggleshadows-desc = Toggles shadow rendering.
|
||||
cmd-toggleshadows-help = Usage: toggleshadows
|
||||
cmd-toggleshadows-help = Usage: {$command}
|
||||
|
||||
cmd-togglelightbuf-desc = Toggles lighting rendering. This includes shadows but not FOV.
|
||||
cmd-togglelightbuf-help = Usage: togglelightbuf
|
||||
cmd-togglelightbuf-help = Usage: {$command}
|
||||
|
||||
cmd-chunkinfo-desc = Gets info about a chunk under your mouse cursor.
|
||||
cmd-chunkinfo-help = Usage: chunkinfo
|
||||
cmd-chunkinfo-help = Usage: {$command}
|
||||
|
||||
cmd-rldshader-desc = Reloads all shaders.
|
||||
cmd-rldshader-help = Usage: rldshader
|
||||
cmd-rldshader-help = Usage: {$command}
|
||||
|
||||
cmd-cldbglyr-desc = Toggle fov and light debug layers.
|
||||
cmd-cldbglyr-help= Usage: cldbglyr <layer>: Toggle <layer>
|
||||
cmd-cldbglyr-help= Usage: {$command} <layer>: Toggle <layer>
|
||||
cldbglyr: Turn all Layers off
|
||||
|
||||
cmd-key-info-desc = Keys key info for a key.
|
||||
cmd-key-info-help = Usage: keyinfo <Key>
|
||||
cmd-key-info-help = Usage: {$command} <Key>
|
||||
|
||||
## 'bind' command
|
||||
cmd-bind-desc = Binds an input key combination to an input command.
|
||||
cmd-bind-help = Usage: bind { cmd-bind-arg-key } { cmd-bind-arg-mode } { cmd-bind-arg-command }
|
||||
cmd-bind-help = Usage: {$command} { cmd-bind-arg-key } { cmd-bind-arg-mode } { cmd-bind-arg-command }
|
||||
Note that this DOES NOT automatically save bindings.
|
||||
Use the 'svbind' command to save binding configuration.
|
||||
|
||||
@@ -255,316 +256,322 @@ cmd-bind-arg-mode = <BindMode>
|
||||
cmd-bind-arg-command = <InputCommand>
|
||||
|
||||
cmd-net-draw-interp-desc = Toggles the debug drawing of the network interpolation.
|
||||
cmd-net-draw-interp-help = Usage: net_draw_interp
|
||||
cmd-net-draw-interp-help = Usage: {$command}
|
||||
|
||||
cmd-net-watch-ent-desc = Dumps all network updates for an EntityId to the console.
|
||||
cmd-net-watch-ent-help = Usage: net_watchent <0|EntityUid>
|
||||
cmd-net-watch-ent-help = Usage: {$command} <0|EntityUid>
|
||||
|
||||
cmd-net-refresh-desc = Requests a full server state.
|
||||
cmd-net-refresh-help = Usage: net_refresh
|
||||
cmd-net-refresh-help = Usage: {$command}
|
||||
|
||||
cmd-net-entity-report-desc = Toggles the net entity report panel.
|
||||
cmd-net-entity-report-help = Usage: net_entityreport
|
||||
cmd-net-entity-report-help = Usage: {$command}
|
||||
|
||||
cmd-fill-desc = Fill up the console for debugging.
|
||||
cmd-fill-help = Fills the console with some nonsense for debugging.
|
||||
cmd-fill-help = Usage: {$command}
|
||||
Fills the console with some nonsense for debugging.
|
||||
|
||||
cmd-cls-desc = Clears the console.
|
||||
cmd-cls-help = Clears the debug console of all messages.
|
||||
cmd-cls-help = Usage: {$command}
|
||||
Clears the debug console of all messages.
|
||||
|
||||
cmd-sendgarbage-desc = Sends garbage to the server.
|
||||
cmd-sendgarbage-help = The server will reply with 'no u'
|
||||
cmd-sendgarbage-help = Usage: {$command}
|
||||
The server will reply with 'no u'
|
||||
|
||||
cmd-loadgrid-desc = Loads a grid from a file into an existing map.
|
||||
cmd-loadgrid-help = loadgrid <MapID> <Path> [x y] [rotation] [storeUids]
|
||||
cmd-loadgrid-help = Usage: {$command} <MapID> <Path> [x y] [rotation] [storeUids]
|
||||
|
||||
cmd-loc-desc = Prints the absolute location of the player's entity to console.
|
||||
cmd-loc-help = loc
|
||||
cmd-loc-help = Usage: {$command}
|
||||
|
||||
cmd-tpgrid-desc = Teleports a grid to a new location.
|
||||
cmd-tpgrid-help = tpgrid <gridId> <X> <Y> [<MapId>]
|
||||
cmd-tpgrid-help = Usage: {$command} <gridId> <X> <Y> [<MapId>]
|
||||
|
||||
cmd-rmgrid-desc = Removes a grid from a map. You cannot remove the default grid.
|
||||
cmd-rmgrid-help = rmgrid <gridId>
|
||||
cmd-rmgrid-help = Usage: {$command} <gridId>
|
||||
|
||||
cmd-mapinit-desc = Runs map init on a map.
|
||||
cmd-mapinit-help = mapinit <mapID>
|
||||
cmd-mapinit-help = Usage: {$command} <mapID>
|
||||
|
||||
cmd-lsmap-desc = Lists maps.
|
||||
cmd-lsmap-help = lsmap
|
||||
cmd-lsmap-help = Usage: {$command}
|
||||
|
||||
cmd-lsgrid-desc = Lists grids.
|
||||
cmd-lsgrid-help = lsgrid
|
||||
cmd-lsgrid-help = Usage: {$command}
|
||||
|
||||
cmd-addmap-desc = Adds a new empty map to the round. If the mapID already exists, this command does nothing.
|
||||
cmd-addmap-help = addmap <mapID> [pre-init]
|
||||
cmd-addmap-help = Usage: {$command} <mapID> [pre-init]
|
||||
|
||||
cmd-rmmap-desc = Removes a map from the world. You cannot remove nullspace.
|
||||
cmd-rmmap-help = rmmap <mapId>
|
||||
cmd-rmmap-help = Usage: {$command} <mapId>
|
||||
|
||||
cmd-savegrid-desc = Serializes a grid to disk.
|
||||
cmd-savegrid-help = savegrid <gridID> <Path>
|
||||
cmd-savegrid-help = Usage: {$command} <gridID> <Path>
|
||||
|
||||
cmd-testbed-desc = Loads a physics testbed on the specified map.
|
||||
cmd-testbed-help = testbed <mapid> <test>
|
||||
cmd-testbed-help = Usage: {$command} <mapid> <test>
|
||||
|
||||
## 'flushcookies' command
|
||||
# Note: the flushcookies command is from Robust.Client.WebView, it's not in the main engine code.
|
||||
|
||||
## 'addcomp' command
|
||||
cmd-addcomp-desc = Adds a component to an entity.
|
||||
cmd-addcomp-help = addcomp <uid> <componentName>
|
||||
cmd-addcomp-help = Usage: {$command} <uid> <componentName>
|
||||
cmd-addcompc-desc = Adds a component to an entity on the client.
|
||||
cmd-addcompc-help = addcompc <uid> <componentName>
|
||||
cmd-addcompc-help = Usage: {$command} <uid> <componentName>
|
||||
|
||||
## 'rmcomp' command
|
||||
cmd-rmcomp-desc = Removes a component from an entity.
|
||||
cmd-rmcomp-help = rmcomp <uid> <componentName>
|
||||
cmd-rmcomp-help = Usage: {$command} <uid> <componentName>
|
||||
cmd-rmcompc-desc = Removes a component from an entity on the client.
|
||||
cmd-rmcompc-help = rmcomp <uid> <componentName>
|
||||
cmd-rmcompc-help = Usage: {$command} <uid> <componentName>
|
||||
|
||||
## 'addview' command
|
||||
cmd-addview-desc = Allows you to subscribe to an entity's view for debugging purposes.
|
||||
cmd-addview-help = addview <entityUid>
|
||||
cmd-addview-help = Usage: {$command} <entityUid>
|
||||
cmd-addviewc-desc = Allows you to subscribe to an entity's view for debugging purposes.
|
||||
cmd-addviewc-help = addview <entityUid>
|
||||
cmd-addviewc-help = Usage: {$command} <entityUid>
|
||||
|
||||
## 'removeview' command
|
||||
cmd-removeview-desc = Allows you to unsubscribe to an entity's view for debugging purposes.
|
||||
cmd-removeview-help = removeview <entityUid>
|
||||
cmd-removeview-help = Usage: {$command} <entityUid>
|
||||
|
||||
## 'loglevel' command
|
||||
cmd-loglevel-desc = Changes the log level for a provided sawmill.
|
||||
cmd-loglevel-help = Usage: loglevel <sawmill> <level>
|
||||
cmd-loglevel-help = Usage: {$command} <sawmill> <level>
|
||||
sawmill: A label prefixing log messages. This is the one you're setting the level for.
|
||||
level: The log level. Must match one of the values of the LogLevel enum.
|
||||
|
||||
cmd-testlog-desc = Writes a test log to a sawmill.
|
||||
cmd-testlog-help = Usage: testlog <sawmill> <level> <message>
|
||||
cmd-testlog-help = Usage: {$command} <sawmill> <level> <message>
|
||||
sawmill: A label prefixing the logged message.
|
||||
level: The log level. Must match one of the values of the LogLevel enum.
|
||||
message: The message to be logged. Wrap this in double quotes if you want to use spaces.
|
||||
|
||||
## 'vv' command
|
||||
cmd-vv-desc = Opens View Variables.
|
||||
cmd-vv-help = Usage: vv <entity ID|IoC interface name|SIoC interface name>
|
||||
cmd-vv-help = Usage: {$command} <entity ID|IoC interface name|SIoC interface name>
|
||||
|
||||
## 'showvelocities' command
|
||||
cmd-showvelocities-desc = Displays your angular and linear velocities.
|
||||
cmd-showvelocities-help = Usage: showvelocities
|
||||
cmd-showvelocities-help = Usage: {$command}
|
||||
|
||||
## 'setinputcontext' command
|
||||
cmd-setinputcontext-desc = Sets the active input context.
|
||||
cmd-setinputcontext-help = Usage: setinputcontext <context>
|
||||
cmd-setinputcontext-help = Usage: {$command} <context>
|
||||
|
||||
## 'forall' command
|
||||
cmd-forall-desc = Runs a command over all entities with a given component.
|
||||
cmd-forall-help = Usage: forall <bql query> do <command...>
|
||||
cmd-forall-help = Usage: {$command} <bql query> do <command...>
|
||||
|
||||
## 'delete' command
|
||||
cmd-delete-desc = Deletes the entity with the specified ID.
|
||||
cmd-delete-help = delete <entity UID>
|
||||
cmd-delete-help = Usage: {$command} <entity UID>
|
||||
|
||||
# System commands
|
||||
cmd-showtime-desc = Shows the server time.
|
||||
cmd-showtime-help = showtime
|
||||
cmd-showtime-help = Usage: {$command}
|
||||
|
||||
cmd-restart-desc = Gracefully restarts the server (not just the round).
|
||||
cmd-restart-help = restart
|
||||
cmd-restart-help = Usage: {$command}
|
||||
|
||||
cmd-shutdown-desc = Gracefully shuts down the server.
|
||||
cmd-shutdown-help = shutdown
|
||||
cmd-shutdown-help = Usage: {$command}
|
||||
|
||||
cmd-saveconfig-desc = Saves the server configuration to the config file.
|
||||
cmd-saveconfig-help = saveconfig
|
||||
cmd-saveconfig-help = Usage: {$command}
|
||||
|
||||
cmd-netaudit-desc = Prints into about NetMsg security.
|
||||
cmd-netaudit-help = netaudit
|
||||
cmd-netaudit-help = Usage: {$command}
|
||||
|
||||
# Player commands
|
||||
cmd-tp-desc = Teleports a player to any location in the round.
|
||||
cmd-tp-help = tp <x> <y> [<mapID>]
|
||||
cmd-tp-help = Usage: {$command} <x> <y> [<mapID>]
|
||||
|
||||
cmd-tpto-desc = Teleports the current player or the specified players/entities to the location of the first player/entity.
|
||||
cmd-tpto-help = tpto <username|uid> [username|NetEntity]...
|
||||
cmd-tpto-help = Usage: {$command} <username|uid> [username|NetEntity]...
|
||||
cmd-tpto-destination-hint = destination (NetEntity or username)
|
||||
cmd-tpto-victim-hint = entity to teleport (NetEntity or username)
|
||||
cmd-tpto-parse-error = Cant resolve entity or player: {$str}
|
||||
|
||||
cmd-listplayers-desc = Lists all players currently connected.
|
||||
cmd-listplayers-help = listplayers
|
||||
cmd-listplayers-help = Usage: {$command}
|
||||
|
||||
cmd-kick-desc = Kicks a connected player out of the server, disconnecting them.
|
||||
cmd-kick-help = kick <PlayerIndex> [<Reason>]
|
||||
cmd-kick-help = Usage: {$command} <PlayerIndex> [<Reason>]
|
||||
|
||||
# Spin command
|
||||
cmd-spin-desc = Causes an entity to spin. Default entity is the attached player's parent.
|
||||
cmd-spin-help = spin velocity [drag] [entityUid]
|
||||
cmd-spin-help = Usage: {$command} velocity [drag] [entityUid]
|
||||
|
||||
# Localization command
|
||||
cmd-rldloc-desc = Reloads localization (client & server).
|
||||
cmd-rldloc-help = Usage: rldloc
|
||||
cmd-rldloc-help = Usage: {$command}
|
||||
|
||||
# Debug entity controls
|
||||
cmd-spawn-desc = Spawns an entity with specific type.
|
||||
cmd-spawn-help = spawn <prototype> OR spawn <prototype> <relative entity ID> OR spawn <prototype> <x> <y>
|
||||
cmd-spawn-help = Usage: {$command} <prototype> | {$command} <prototype> <relative entity ID> | {$command} <prototype> <x> <y>
|
||||
cmd-cspawn-desc = Spawns a client-side entity with specific type at your feet.
|
||||
cmd-cspawn-help = cspawn <entity type>
|
||||
cmd-cspawn-help = Usage: {$command} <entity type>
|
||||
|
||||
cmd-dumpentities-desc = Dump entity list.
|
||||
cmd-dumpentities-help = Dumps entity list of UIDs and prototype.
|
||||
cmd-dumpentities-help = Usage: {$command}
|
||||
Dumps entity list of UIDs and prototype.
|
||||
|
||||
cmd-getcomponentregistration-desc = Gets component registration information.
|
||||
cmd-getcomponentregistration-help = Usage: getcomponentregistration <componentName>
|
||||
cmd-getcomponentregistration-help = Usage: {$command} <componentName>
|
||||
|
||||
cmd-showrays-desc = Toggles debug drawing of physics rays. An integer for <raylifetime> must be provided.
|
||||
cmd-showrays-help = Usage: showrays <raylifetime>
|
||||
cmd-showrays-help = Usage: {$command} <raylifetime>
|
||||
|
||||
cmd-disconnect-desc = Immediately disconnect from the server and go back to the main menu.
|
||||
cmd-disconnect-help = Usage: disconnect
|
||||
cmd-disconnect-help = Usage: {$command}
|
||||
|
||||
cmd-entfo-desc = Displays verbose diagnostics for an entity.
|
||||
cmd-entfo-help = Usage: entfo <entityuid>
|
||||
cmd-entfo-help = Usage: {$command} <entityuid>
|
||||
The entity UID can be prefixed with 'c' to convert it to a client entity UID.
|
||||
|
||||
cmd-fuck-desc = Throws an exception
|
||||
cmd-fuck-help = Usage: fuck
|
||||
cmd-fuck-desc = Throws an exception.
|
||||
cmd-fuck-help = Usage: {$command}
|
||||
|
||||
cmd-showpos-desc = Show the position of all entities on the screen.
|
||||
cmd-showpos-help = Usage: showpos
|
||||
cmd-showpos-help = Usage: {$command}
|
||||
|
||||
cmd-showrot-desc = Show the rotation of all entities on the screen.
|
||||
cmd-showrot-help = Usage: showrot
|
||||
cmd-showrot-help = Usage: {$command}
|
||||
|
||||
cmd-showvel-desc = Show the local velocity of all entites on the screen.
|
||||
cmd-showvel-help = Usage: showvel
|
||||
cmd-showvel-help = Usage: {$command}
|
||||
|
||||
cmd-showangvel-desc = Show the angular velocity of all entities on the screen.
|
||||
cmd-showangvel-help = Usage: showangvel
|
||||
cmd-showangvel-help = Usage: {$command}
|
||||
|
||||
cmd-sggcell-desc = Lists entities on a snap grid cell.
|
||||
cmd-sggcell-help = Usage: sggcell <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.
|
||||
cmd-sggcell-help = Usage: {$command} <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.
|
||||
|
||||
cmd-overrideplayername-desc = Changes the name used when attempting to connect to the server.
|
||||
cmd-overrideplayername-help = Usage: overrideplayername <name>
|
||||
cmd-overrideplayername-help = Usage: {$command} <name>
|
||||
|
||||
cmd-showanchored-desc = Shows anchored entities on a particular tile
|
||||
cmd-showanchored-help = Usage: showanchored
|
||||
cmd-showanchored-desc = Shows anchored entities on a particular tile.
|
||||
cmd-showanchored-help = Usage: {$command}
|
||||
|
||||
cmd-dmetamem-desc = Dumps a type's members in a format suitable for the sandbox configuration file.
|
||||
cmd-dmetamem-help = Usage: dmetamem <type>
|
||||
cmd-dmetamem-help = Usage: {$command} <type>
|
||||
|
||||
cmd-launchauth-desc = Load authentication tokens from launcher data to aid in testing of live servers.
|
||||
cmd-launchauth-help = Usage: launchauth <account name>
|
||||
cmd-launchauth-help = Usage: {$command} <account name>
|
||||
|
||||
cmd-lightbb-desc = Toggles whether to show light bounding boxes.
|
||||
cmd-lightbb-help = Usage: lightbb
|
||||
cmd-lightbb-help = Usage: {$command}
|
||||
|
||||
cmd-monitorinfo-desc = Monitors info
|
||||
cmd-monitorinfo-help = Usage: monitorinfo <id>
|
||||
cmd-monitorinfo-desc = Monitors info.
|
||||
cmd-monitorinfo-help = Usage: {$command} <id>
|
||||
|
||||
cmd-setmonitor-desc = Set monitor
|
||||
cmd-setmonitor-help = Usage: setmonitor <id>
|
||||
cmd-setmonitor-desc = Set monitor.
|
||||
cmd-setmonitor-help = Usage: {$command} <id>
|
||||
|
||||
cmd-physics-desc = Shows a debug physics overlay. The arg supplied specifies the overlay.
|
||||
cmd-physics-help = Usage: physics <aabbs / com / contactnormals / contactpoints / distance / joints / shapeinfo / shapes>
|
||||
cmd-physics-help = Usage: {$command} <aabbs / com / contactnormals / contactpoints / distance / joints / shapeinfo / shapes>
|
||||
|
||||
cmd-hardquit-desc = Kills the game client instantly.
|
||||
cmd-hardquit-help = Kills the game client instantly, leaving no traces. No telling the server goodbye.
|
||||
cmd-hardquit-help = Usage: {$command}
|
||||
Kills the game client instantly, leaving no traces. No telling the server goodbye.
|
||||
|
||||
cmd-quit-desc = Shuts down the game client gracefully.
|
||||
cmd-quit-help = Properly shuts down the game client, notifying the connected server and such.
|
||||
cmd-quit-help = Usage: {$command}
|
||||
Properly shuts down the game client, notifying the connected server and such.
|
||||
|
||||
cmd-csi-desc = Opens a C# interactive console.
|
||||
cmd-csi-help = Usage: csi
|
||||
cmd-csi-help = Usage: {$command}
|
||||
|
||||
cmd-scsi-desc = Opens a C# interactive console on the server.
|
||||
cmd-scsi-help = Usage: scsi
|
||||
cmd-scsi-help = Usage: {$command}
|
||||
|
||||
cmd-watch-desc = Opens a variable watch window.
|
||||
cmd-watch-help = Usage: watch
|
||||
cmd-watch-help = Usage: {$command}
|
||||
|
||||
cmd-showspritebb-desc = Toggle whether sprite bounds are shown
|
||||
cmd-showspritebb-help = Usage: showspritebb
|
||||
cmd-showspritebb-desc = Toggle whether sprite bounds are shown.
|
||||
cmd-showspritebb-help = Usage: {$command}
|
||||
|
||||
cmd-togglelookup-desc = Shows / hides entitylookup bounds via an overlay.
|
||||
cmd-togglelookup-help = Usage: togglelookup
|
||||
cmd-togglelookup-help = Usage: {$command}
|
||||
|
||||
cmd-net_entityreport-desc = Toggles the net entity report panel.
|
||||
cmd-net_entityreport-help = Usage: net_entityreport
|
||||
cmd-net_entityreport-help = Usage: {$command}
|
||||
|
||||
cmd-net_refresh-desc = Requests a full server state.
|
||||
cmd-net_refresh-help = Usage: net_refresh
|
||||
cmd-net_refresh-help = Usage: {$command}
|
||||
|
||||
cmd-net_graph-desc = Toggles the net statistics panel.
|
||||
cmd-net_graph-help = Usage: net_graph
|
||||
cmd-net_graph-help = Usage: {$command}
|
||||
|
||||
cmd-net_watchent-desc = Dumps all network updates for an EntityId to the console.
|
||||
cmd-net_watchent-help = Usage: net_watchent <0|EntityUid>
|
||||
cmd-net_watchent-help = Usage: {$command} <0|EntityUid>
|
||||
|
||||
cmd-net_draw_interp-desc = Toggles the debug drawing of the network interpolation.
|
||||
cmd-net_draw_interp-help = Usage: net_draw_interp <0|EntityUid>
|
||||
cmd-net_draw_interp-help = Usage: {$command} <0|EntityUid>
|
||||
|
||||
cmd-vram-desc = Displays video memory usage statics by the game.
|
||||
cmd-vram-help = Usage: vram
|
||||
cmd-vram-help = Usage: {$command}
|
||||
|
||||
cmd-showislands-desc = Shows the current physics bodies involved in each physics island.
|
||||
cmd-showislands-help = Usage: showislands
|
||||
cmd-showislands-help = Usage: {$command}
|
||||
|
||||
cmd-showgridnodes-desc = Shows the nodes for grid split purposes.
|
||||
cmd-showgridnodes-help = Usage: showgridnodes
|
||||
cmd-showgridnodes-help = Usage: {$command}
|
||||
|
||||
cmd-profsnap-desc = Make a profiling snapshot.
|
||||
cmd-profsnap-help = Usage: profsnap
|
||||
cmd-profsnap-help = Usage: {$command}
|
||||
|
||||
cmd-devwindow-desc = Dev Window
|
||||
cmd-devwindow-help = Usage: devwindow
|
||||
cmd-devwindow-desc = Dev Window.
|
||||
cmd-devwindow-help = Usage: {$command}
|
||||
|
||||
cmd-scene-desc = Immediately changes the UI scene/state.
|
||||
cmd-scene-help = Usage: scene <className>
|
||||
cmd-scene-help = Usage: {$command} <className>
|
||||
|
||||
cmd-szr_stats-desc = Report serializer statistics.
|
||||
cmd-szr_stats-help = Usage: szr_stats
|
||||
cmd-szr_stats-help = Usage: {$command}
|
||||
|
||||
cmd-hwid-desc = Returns the current HWID (HardWare ID).
|
||||
cmd-hwid-help = Usage: hwid
|
||||
cmd-hwid-help = Usage: {$command}
|
||||
|
||||
cmd-vvread-desc = Retrieve a path's value using VV (View Variables).
|
||||
cmd-vvread-help = Usage: vvread <path>
|
||||
cmd-vvread-help = Usage: {$command} <path>
|
||||
|
||||
cmd-vvwrite-desc = Modify a path's value using VV (View Variables).
|
||||
cmd-vvwrite-help = Usage: vvwrite <path>
|
||||
cmd-vvwrite-help = Usage: {$command} <path>
|
||||
|
||||
cmd-vvinvoke-desc = Invoke/Call a path with arguments using VV.
|
||||
cmd-vvinvoke-help = Usage: vvinvoke <path> [arguments...]
|
||||
cmd-vvinvoke-help = Usage: {$command} <path> [arguments...]
|
||||
|
||||
cmd-dump_dependency_injectors-desc = Dump IoCManager's dependency injector cache.
|
||||
cmd-dump_dependency_injectors-help = Usage: dump_dependency_injectors
|
||||
cmd-dump_dependency_injectors-help = Usage: {$command}
|
||||
cmd-dump_dependency_injectors-total-count = Total count: { $total }
|
||||
|
||||
cmd-dump_netserializer_type_map-desc = Dump NetSerializer's type map and serializer hash.
|
||||
cmd-dump_netserializer_type_map-help = Usage: dump_netserializer_type_map
|
||||
cmd-dump_netserializer_type_map-help = Usage: {$command}
|
||||
|
||||
cmd-hub_advertise_now-desc = Immediately advertise to the master hub server
|
||||
cmd-hub_advertise_now-help = Usage: hub_advertise_now
|
||||
cmd-hub_advertise_now-desc = Immediately advertise to the master hub server.
|
||||
cmd-hub_advertise_now-help = Usage: {$command}
|
||||
|
||||
cmd-echo-desc = Echo arguments back to the console
|
||||
cmd-echo-help = Usage: echo "<message>"
|
||||
cmd-echo-desc = Echo arguments back to the console.
|
||||
cmd-echo-help = Usage: {$command} "<message>"
|
||||
|
||||
## 'vfs_ls' command
|
||||
cmd-vfs_ls-desc = List directory contents in the VFS.
|
||||
cmd-vfs_ls-help = Usage: vfs_list <path>
|
||||
cmd-vfs_ls-help = Usage: {$command} <path>
|
||||
Example:
|
||||
vfs_list /Assemblies
|
||||
|
||||
cmd-vfs_ls-err-args = Need exactly 1 argument.
|
||||
cmd-vfs_ls-hint-path = <path>
|
||||
|
||||
cmd-reloadtiletextures-desc = Reloads the tile texture atlas to allow hot reloading tile sprites
|
||||
cmd-reloadtiletextures-help = Usage: reloadtiletextures
|
||||
cmd-reloadtiletextures-desc = Reloads the tile texture atlas to allow hot reloading tile sprites.
|
||||
cmd-reloadtiletextures-help = Usage: {$command}
|
||||
|
||||
cmd-audio_length-desc = Shows the length of an audio file
|
||||
cmd-audio_length-help = Usage: audio_length { cmd-audio_length-arg-file-name }
|
||||
cmd-audio_length-help = Usage: {$command} { cmd-audio_length-arg-file-name }
|
||||
cmd-audio_length-arg-file-name = <file name>
|
||||
|
||||
## PVS
|
||||
@@ -573,7 +580,9 @@ cmd-pvs-override-info-empty = Entity {$nuid} has no PVS overrides.
|
||||
cmd-pvs-override-info-global = Entity {$nuid} has a global override.
|
||||
cmd-pvs-override-info-clients = Entity {$nuid} has a session override for {$clients}.
|
||||
|
||||
cmd-localization_set_culture-desc = Set DefaultCulture for the client LocalizationManager
|
||||
cmd-localization_set_culture-help = Usage: localization_set_culture <cultureName>
|
||||
cmd-localization_set_culture-desc = Set DefaultCulture for the client LocalizationManager.
|
||||
cmd-localization_set_culture-help = Usage: {$command} <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]
|
||||
|
||||
@@ -8,3 +8,5 @@ color-selector-sliders-alpha = A
|
||||
|
||||
color-selector-sliders-rgb = RGB
|
||||
color-selector-sliders-hsv = HSV
|
||||
|
||||
option-button-filter = Filter
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -428,3 +428,7 @@ command-description-cmd-info =
|
||||
On its own, this means it'll print the command's help message.
|
||||
command-description-comp-rm =
|
||||
Removes the given component from the entity.
|
||||
|
||||
command-description-overlay-toggle = Toggle an overlay on or off
|
||||
command-description-overlay-add = Add an overlay (if it does not already exist)
|
||||
command-description-overlay-remove = Remove an overlay
|
||||
|
||||
@@ -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;
|
||||
|
||||
19
Robust.Benchmarks/NumericsHelpers/Box2Benchmark.cs
Normal file
19
Robust.Benchmarks/NumericsHelpers/Box2Benchmark.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Benchmarks.NumericsHelpers;
|
||||
|
||||
[Virtual, DisassemblyDiagnoser]
|
||||
public class Box2Benchmark
|
||||
{
|
||||
public Box2 Box = new();
|
||||
public Matrix3x2 Matrix = new();
|
||||
|
||||
[Benchmark]
|
||||
public Box2 Transform()
|
||||
{
|
||||
return Matrix.TransformBox(Box);
|
||||
}
|
||||
}
|
||||
18
Robust.Benchmarks/NumericsHelpers/Box2RotatedBenchmark.cs
Normal file
18
Robust.Benchmarks/NumericsHelpers/Box2RotatedBenchmark.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Benchmarks.NumericsHelpers;
|
||||
|
||||
[Virtual, DisassemblyDiagnoser]
|
||||
public class Box2RotatedBenchmark
|
||||
{
|
||||
public Box2Rotated Box = new();
|
||||
|
||||
[Benchmark]
|
||||
public Matrix3x2 GetTransform()
|
||||
{
|
||||
return Box.Transform;
|
||||
}
|
||||
}
|
||||
25
Robust.Benchmarks/NumericsHelpers/GetAABBBenchmark.cs
Normal file
25
Robust.Benchmarks/NumericsHelpers/GetAABBBenchmark.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Runtime.Intrinsics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Benchmarks.NumericsHelpers;
|
||||
|
||||
[Virtual, DisassemblyDiagnoser]
|
||||
public class GetAABBBenchmark
|
||||
{
|
||||
public Vector128<float> X;
|
||||
public Vector128<float> Y;
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public Vector128<float> GetAABB_NoAvx()
|
||||
{
|
||||
return SimdHelpers.GetAABBSlow(X, Y);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public Vector128<float> GetAABB_Avx()
|
||||
{
|
||||
return SimdHelpers.GetAABBAvx(X, Y);
|
||||
}
|
||||
}
|
||||
21
Robust.Benchmarks/NumericsHelpers/SlimPolygonBenchmark.cs
Normal file
21
Robust.Benchmarks/NumericsHelpers/SlimPolygonBenchmark.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Shapes;
|
||||
|
||||
namespace Robust.Benchmarks.NumericsHelpers;
|
||||
|
||||
// SlimPolyon is internal, so this won't compile without changes.
|
||||
/*
|
||||
[Virtual, DisassemblyDiagnoser]
|
||||
public class SlimPolygonBenchmark
|
||||
{
|
||||
public Box2Rotated RotBox = new();
|
||||
|
||||
[Benchmark]
|
||||
public SlimPolygon RotatedBox()
|
||||
{
|
||||
return new SlimPolygon(in RotBox);
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -6,7 +6,7 @@ using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public static class Program
|
||||
internal static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -24,6 +25,7 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameControllerInternal _gameController = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -59,9 +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)
|
||||
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
|
||||
var remoteDebugPort = _cfg.GetCVar(WCVars.WebRemoteDebugPort);
|
||||
|
||||
var cachePath = FindAndLockCacheDirectory();
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
@@ -71,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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -54,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)
|
||||
{
|
||||
@@ -147,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,6 +1,6 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -16,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;
|
||||
}
|
||||
}
|
||||
@@ -44,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();
|
||||
}
|
||||
}
|
||||
@@ -62,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();
|
||||
}
|
||||
}
|
||||
@@ -80,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();
|
||||
}
|
||||
}
|
||||
@@ -98,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();
|
||||
}
|
||||
}
|
||||
@@ -116,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();
|
||||
}
|
||||
}
|
||||
@@ -134,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();
|
||||
}
|
||||
}
|
||||
@@ -152,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();
|
||||
}
|
||||
}
|
||||
@@ -170,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();
|
||||
}
|
||||
}
|
||||
@@ -188,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();
|
||||
}
|
||||
}
|
||||
@@ -206,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();
|
||||
}
|
||||
}
|
||||
@@ -224,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);
|
||||
}
|
||||
@@ -232,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();
|
||||
}
|
||||
}
|
||||
@@ -243,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();
|
||||
}
|
||||
}
|
||||
@@ -261,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();
|
||||
}
|
||||
}
|
||||
@@ -279,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);
|
||||
}
|
||||
@@ -287,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();
|
||||
}
|
||||
}
|
||||
@@ -298,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();
|
||||
}
|
||||
}
|
||||
@@ -316,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();
|
||||
}
|
||||
}
|
||||
@@ -334,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();
|
||||
}
|
||||
}
|
||||
@@ -352,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();
|
||||
}
|
||||
}
|
||||
@@ -370,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();
|
||||
}
|
||||
}
|
||||
@@ -388,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();
|
||||
}
|
||||
}
|
||||
@@ -406,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();
|
||||
}
|
||||
}
|
||||
@@ -424,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();
|
||||
}
|
||||
}
|
||||
@@ -442,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Clyde;
|
||||
using Robust.Client.Graphics.FontManagement;
|
||||
using Robust.Client.HWId;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Localization;
|
||||
@@ -67,6 +68,7 @@ namespace Robust.Client
|
||||
deps.Register<IMapManagerInternal, NetworkedMapManager>();
|
||||
deps.Register<INetworkedMapManager, NetworkedMapManager>();
|
||||
deps.Register<IEntityManager, ClientEntityManager>();
|
||||
deps.Register<FontTagHijackHolder>();
|
||||
deps.Register<IReflectionManager, ClientReflectionManager>();
|
||||
deps.Register<IConsoleHost, ClientConsoleHost>();
|
||||
deps.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
@@ -108,6 +110,8 @@ namespace Robust.Client
|
||||
deps.Register<IReloadManager, ReloadManager>();
|
||||
deps.Register<ILocalizationManager, ClientLocalizationManager>();
|
||||
deps.Register<ILocalizationManagerInternal, ClientLocalizationManager>();
|
||||
deps.Register<LoadingScreenManager>();
|
||||
deps.Register<ILoadingScreenManager, LoadingScreenManager>();
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
@@ -120,6 +124,8 @@ namespace Robust.Client
|
||||
deps.Register<IInputManager, InputManager>();
|
||||
deps.Register<IFileDialogManager, DummyFileDialogManager>();
|
||||
deps.Register<IUriOpener, UriOpenerDummy>();
|
||||
deps.Register<ISystemFontManager, SystemFontManagerFallback>();
|
||||
deps.Register<ISystemFontManagerInternal, SystemFontManagerFallback>();
|
||||
break;
|
||||
case GameController.DisplayMode.Clyde:
|
||||
deps.Register<IClyde, Clyde>();
|
||||
@@ -130,6 +136,8 @@ namespace Robust.Client
|
||||
deps.Register<IInputManager, ClydeInputManager>();
|
||||
deps.Register<IFileDialogManager, FileDialogManager>();
|
||||
deps.Register<IUriOpener, UriOpener>();
|
||||
deps.Register<ISystemFontManager, SystemFontManager>();
|
||||
deps.Register<ISystemFontManagerInternal, SystemFontManager>();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
@@ -31,8 +31,7 @@ public sealed class LightTreeSystem : ComponentTreeSystem<LightTreeComponent, Po
|
||||
|
||||
var pos = XformSystem.GetRelativePosition(
|
||||
entry.Transform,
|
||||
entry.Component.TreeUid.Value,
|
||||
GetEntityQuery<TransformComponent>());
|
||||
entry.Component.TreeUid.Value);
|
||||
|
||||
return ExtractAabb(in entry, pos, default);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
#if DEBUG
|
||||
#if TOOLS
|
||||
internal sealed class DumpMetadataMembersCommand : LocalizedCommands
|
||||
{
|
||||
public override string Command => "dmetamem";
|
||||
@@ -19,10 +20,28 @@ namespace Robust.Client.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var sig in AssemblyTypeChecker.DumpMetaMembers(type))
|
||||
var members = AssemblyTypeChecker.DumpMetaMembers(type)
|
||||
.GroupBy(x => x.IsField)
|
||||
.ToDictionary(x => x.Key, x => x.Select(t => t.Value).ToList());
|
||||
|
||||
if (members.TryGetValue(true, out var fields))
|
||||
{
|
||||
System.Console.WriteLine(@$"- ""{sig}""");
|
||||
shell.WriteLine(sig);
|
||||
fields.Sort(StringComparer.Ordinal);
|
||||
|
||||
foreach (var member in fields)
|
||||
{
|
||||
System.Console.WriteLine(@$"- ""{member}""");
|
||||
}
|
||||
}
|
||||
|
||||
if (members.TryGetValue(false, out var methods))
|
||||
{
|
||||
methods.Sort(StringComparer.Ordinal);
|
||||
|
||||
foreach (var member in methods)
|
||||
{
|
||||
System.Console.WriteLine(@$"- ""{member}""");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,8 @@ using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.RichText;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -44,7 +43,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");
|
||||
@@ -155,6 +157,7 @@ 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()
|
||||
@@ -205,7 +208,10 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
|
||||
private Control TabRichText()
|
||||
{
|
||||
var label = new RichTextLabel();
|
||||
label.SetMessage(FormattedMessage.FromMarkupOrThrow(Lipsum));
|
||||
var msg = FormattedMessage.FromMarkupOrThrow(Lipsum);
|
||||
msg.AddMarkupOrThrow("\n\nWAWWAAWAWWAWA [cmdlink=\"DOES IT WORK\" command=\"help\" /] DOES IT WORK");
|
||||
|
||||
label.SetMessage(msg, [typeof(CommandLinkTag)]);
|
||||
|
||||
TabContainer.SetTabTitle(label, "RichText");
|
||||
return label;
|
||||
@@ -275,32 +281,13 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
|
||||
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)
|
||||
{
|
||||
@@ -321,18 +308,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)
|
||||
{
|
||||
@@ -343,4 +322,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
|
||||
53
Robust.Client/Debugging/OverlayCommand.cs
Normal file
53
Robust.Client/Debugging/OverlayCommand.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.TypeParsers;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Debugging;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class OverlayCommand : ToolshedCommand
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _factory = default!;
|
||||
|
||||
[CommandImplementation("toggle")]
|
||||
internal void Toggle([CommandArgument(customParser:typeof(ReflectionTypeParser<Overlay>))] Type overlay)
|
||||
{
|
||||
if (!overlay.IsSubclassOf(typeof(Overlay)))
|
||||
throw new ArgumentException("Type must be a subclass of overlay");
|
||||
|
||||
if (_overlay.HasOverlay(overlay))
|
||||
Remove(overlay);
|
||||
else
|
||||
Add(overlay);
|
||||
}
|
||||
|
||||
[CommandImplementation("add")]
|
||||
internal void Add([CommandArgument(customParser: typeof(ReflectionTypeParser<Overlay>))] Type overlay)
|
||||
{
|
||||
if (!overlay.IsSubclassOf(typeof(Overlay)))
|
||||
throw new ArgumentException("Type must be a subclass of overlay");
|
||||
|
||||
if (!overlay.HasParameterlessConstructor())
|
||||
throw new ArgumentException("Type must have parameterless constructor");
|
||||
|
||||
if (_overlay.HasOverlay(overlay))
|
||||
return;
|
||||
|
||||
// TODO OVERLAYS Give overlays the ContentAccessAllowedAttribute?
|
||||
var instance = (Overlay) _factory.CreateInstanceUnchecked(overlay, oneOff: true);
|
||||
if (instance is IPostInjectInit init)
|
||||
init.PostInject();
|
||||
|
||||
_overlay.AddOverlay(instance);
|
||||
}
|
||||
|
||||
[CommandImplementation("remove")]
|
||||
public void Remove([CommandArgument(customParser: typeof(ReflectionTypeParser<Overlay>))] Type overlay)
|
||||
{
|
||||
_overlay.RemoveOverlay(overlay);
|
||||
}
|
||||
}
|
||||
229
Robust.Client/Debugging/Overlays/TileDebugOverlay.cs
Normal file
229
Robust.Client/Debugging/Overlays/TileDebugOverlay.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Debugging.Overlays;
|
||||
|
||||
/// <summary>
|
||||
/// This is an abstract helper class that can be used to create simple debug overlays that need to render tile based data.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public abstract class TileDebugOverlay : Overlay, IPostInjectInit
|
||||
{
|
||||
[Dependency] protected readonly IEntityManager Entity = default!;
|
||||
[Dependency] protected readonly IEyeManager Eye = default!;
|
||||
[Dependency] protected readonly IMapManager MapMan = default!;
|
||||
[Dependency] protected readonly IInputManager Input = default!;
|
||||
[Dependency] protected readonly IUserInterfaceManager Ui = default!;
|
||||
[Dependency] protected readonly IResourceCache Cache = default!;
|
||||
|
||||
protected SharedTransformSystem Transform = default!;
|
||||
protected MapSystem Map = default!;
|
||||
protected EntityLookupSystem Lookup = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
|
||||
|
||||
protected Font Font = default!;
|
||||
protected List<Entity<MapGridComponent>> Grids = new();
|
||||
|
||||
public void PostInject()
|
||||
{
|
||||
Transform = Entity.System<SharedTransformSystem>();
|
||||
Map = Entity.System<MapSystem>();
|
||||
Lookup = Entity.System<EntityLookupSystem>();
|
||||
var font = Cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf");
|
||||
Font = new VectorFont(font, 8);
|
||||
Init();
|
||||
}
|
||||
|
||||
protected virtual void Init()
|
||||
{
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
Grids.Clear();
|
||||
if (args.Viewport.Eye?.Position.MapId is not {} map || map == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
MapMan.FindGridsIntersecting(map, args.WorldBounds, ref Grids);
|
||||
|
||||
foreach (var grid in Grids)
|
||||
{
|
||||
switch (args.Space)
|
||||
{
|
||||
case OverlaySpace.ScreenSpace:
|
||||
DrawScreen(args, grid);
|
||||
break;
|
||||
case OverlaySpace.WorldSpace:
|
||||
DrawWorld(args, grid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Grids.Clear();
|
||||
}
|
||||
|
||||
protected virtual void DrawScreen(in OverlayDrawArgs args, Entity<MapGridComponent> grid)
|
||||
{
|
||||
var handle = args.ScreenHandle;
|
||||
var (_, _, matrix, invMatrix) = Transform.GetWorldPositionRotationMatrixWithInv(grid.Owner);
|
||||
var gridBounds = invMatrix.TransformBox(args.WorldBounds).Enlarged(grid.Comp.TileSize * 2);
|
||||
var tilesEnumerator = Map.GetLocalTilesEnumerator(grid, grid, gridBounds);
|
||||
while (tilesEnumerator.MoveNext(out var tile))
|
||||
{
|
||||
var tileBounds = Lookup.GetLocalBounds(tile, grid.Comp.TileSize);
|
||||
if (!gridBounds.Intersects(tileBounds))
|
||||
continue;
|
||||
var screenTileCentre = Eye.WorldToScreen(Vector2.Transform(tileBounds.Center, matrix));
|
||||
DrawTileText(handle, screenTileCentre, tile.GridIndices, grid);
|
||||
}
|
||||
|
||||
// Draw mouse tooltip
|
||||
DrawTooltip(handle);
|
||||
|
||||
}
|
||||
|
||||
protected virtual void DrawTooltip(DrawingHandleScreen handle)
|
||||
{
|
||||
var mousePos = Input.MouseScreenPosition;
|
||||
if (!mousePos.IsValid)
|
||||
return;
|
||||
|
||||
if (Ui.MouseGetControl(mousePos) is not IViewportControl viewport)
|
||||
return;
|
||||
|
||||
var coords = viewport.PixelToMap(mousePos.Position);
|
||||
|
||||
if (!MapMan.TryFindGridAt(coords, out var grid, out var comp))
|
||||
return;
|
||||
|
||||
var local = Map.WorldToLocal(grid, comp, coords.Position);
|
||||
var x = (int) Math.Floor(local.X / comp.TileSize);
|
||||
var y = (int) Math.Floor(local.Y / comp.TileSize);
|
||||
var indices = new Vector2i(x, y);
|
||||
|
||||
DrawTooltip(handle, mousePos.Position, local, indices, (grid, comp));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw a tooltip around the mouse
|
||||
/// </summary>
|
||||
/// <param name="mouseScreen">The mouse's screen coordinates</param>
|
||||
/// <param name="mouseLocal">The mouse's local grid coordinates</param>
|
||||
/// <param name="indices">The mouse's tile indices</param>
|
||||
/// <param name="grid">The grid that the mouse is hovering over</param>
|
||||
protected virtual void DrawTooltip(DrawingHandleScreen handle, Vector2 mouseScreen, Vector2 mouseLocal, Vector2i indices, Entity<MapGridComponent> grid)
|
||||
{
|
||||
if (GetTooltip(mouseLocal, indices, grid) is not { } text)
|
||||
return;
|
||||
|
||||
var lineHeight = Font.GetLineHeight(1f);
|
||||
var offset = new Vector2(0, lineHeight);
|
||||
handle.DrawString(Font, mouseScreen - offset, text);
|
||||
}
|
||||
|
||||
protected virtual void DrawTileText(DrawingHandleScreen handle, Vector2 tileCentre, Vector2i indices, Entity<MapGridComponent> grid)
|
||||
{
|
||||
if (GetText(indices, grid) is {} text)
|
||||
handle.DrawString(Font, tileCentre, text);
|
||||
}
|
||||
|
||||
protected virtual void DrawWorld(in OverlayDrawArgs args, Entity<MapGridComponent> grid)
|
||||
{
|
||||
var handle = args.WorldHandle;
|
||||
var (_, _, matrix, invMatrix) = Transform.GetWorldPositionRotationMatrixWithInv(grid.Owner);
|
||||
var gridBounds = invMatrix.TransformBox(args.WorldBounds).Enlarged(grid.Comp.TileSize * 2);
|
||||
var tilesEnumerator = Map.GetLocalTilesEnumerator(grid, grid, gridBounds);
|
||||
while (tilesEnumerator.MoveNext(out var tile))
|
||||
{
|
||||
handle.SetTransform(matrix);
|
||||
var tileBounds = Lookup.GetLocalBounds(tile, grid.Comp.TileSize);
|
||||
if (gridBounds.Intersects(tileBounds))
|
||||
DrawTile(handle, tileBounds, tile.GridIndices, grid);
|
||||
}
|
||||
|
||||
handle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
protected virtual void DrawTile(DrawingHandleWorld handle, Box2 tile, Vector2i indices, Entity<MapGridComponent> grid)
|
||||
{
|
||||
if (GetColor(indices, grid) is not { } color)
|
||||
return;
|
||||
|
||||
handle.DrawRect(tile, color.Border, filled: false);
|
||||
handle.DrawRect(tile, color.Fill, filled: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get text that will be rendered in a grid tile.
|
||||
/// </summary>
|
||||
protected abstract string? GetText(Vector2i indices, Entity<MapGridComponent> grid);
|
||||
|
||||
/// <summary>
|
||||
/// Get tooltip text that will be shown next to the mouse.
|
||||
/// </summary>
|
||||
/// <param name="mousePos">The mouse's position relative to the grid.</param>
|
||||
/// <param name="gridIndices">The grid indices corresponding to the mouse's position</param>
|
||||
/// <param name="grid">The grid that the mouse is over.</param>
|
||||
protected abstract string? GetTooltip(Vector2 mousePos, Vector2i indices, Entity<MapGridComponent> grid);
|
||||
|
||||
/// <summary>
|
||||
/// Get a border & fill color that will be used to draw a grid tile.
|
||||
/// </summary>
|
||||
protected abstract (Color Fill, Color Border)? GetColor(Vector2i indices, Entity<MapGridComponent> grid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Variant of <see cref="TileDebugOverlay"/> that exists to draw simple float information for each tile.
|
||||
/// </summary>
|
||||
public abstract class TileFloatDebugOverlay : TileDebugOverlay
|
||||
{
|
||||
protected virtual float MinValue => 0;
|
||||
protected virtual float MaxValue => 1;
|
||||
protected abstract float? GetData(Vector2i indices, Entity<MapGridComponent> grid);
|
||||
|
||||
protected override string? GetText(Vector2i indices, Entity<MapGridComponent> grid)
|
||||
{
|
||||
return GetData(indices, grid)?.ToString("F2");
|
||||
}
|
||||
|
||||
protected override string? GetTooltip(Vector2 mousePos, Vector2i indices, Entity<MapGridComponent> grid)
|
||||
{
|
||||
return GetData(indices, grid)?.ToString("F2");
|
||||
}
|
||||
|
||||
protected override (Color Fill, Color Border)? GetColor(Vector2i indices, Entity<MapGridComponent> grid)
|
||||
{
|
||||
if (GetData(indices, grid) is not { } value)
|
||||
return null;
|
||||
|
||||
var color = Gradient(value, MinValue, MaxValue);
|
||||
return (color.WithAlpha(0.2f), color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple yellow -> orange -> red gradient.
|
||||
/// </summary>
|
||||
public Color Gradient(float value, float min, float max)
|
||||
{
|
||||
// map min to 1, max to 0
|
||||
value = (value - min) / (max - min);
|
||||
return value < 0.5f
|
||||
? Color.InterpolateBetween(Color.Yellow, Color.Orange, value * 2)
|
||||
: Color.InterpolateBetween(Color.Orange, Color.Red, (value - 0.5f) * 2);
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,14 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using SDL3;
|
||||
|
||||
namespace Robust.Client
|
||||
{
|
||||
internal partial class GameController : IPostInjectInit
|
||||
{
|
||||
private IGameLoop? _mainLoop;
|
||||
private bool _dontStart;
|
||||
|
||||
[Dependency] private readonly IClientGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
@@ -93,6 +95,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!");
|
||||
@@ -159,8 +163,11 @@ namespace Robust.Client
|
||||
return;
|
||||
}
|
||||
|
||||
DebugTools.AssertNotNull(_mainLoop);
|
||||
_mainLoop!.Run();
|
||||
if (!_dontStart)
|
||||
{
|
||||
DebugTools.AssertNotNull(_mainLoop);
|
||||
_mainLoop!.Run();
|
||||
}
|
||||
|
||||
CleanupGameThread();
|
||||
}
|
||||
|
||||
@@ -96,6 +96,8 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IReloadManager _reload = default!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
[Dependency] private readonly ISystemFontManagerInternal _systemFontManager = default!;
|
||||
[Dependency] private readonly LoadingScreenManager _loadscr = default!;
|
||||
|
||||
private IWebViewManagerHook? _webViewHook;
|
||||
|
||||
@@ -110,6 +112,8 @@ namespace Robust.Client
|
||||
|
||||
private ResourceManifestData? _resourceManifest;
|
||||
|
||||
private DisplayMode _displayMode;
|
||||
|
||||
public void SetCommandLineArgs(CommandLineArgs args)
|
||||
{
|
||||
_commandLineArgs = args;
|
||||
@@ -130,27 +134,39 @@ namespace Robust.Client
|
||||
return Options.SplashLogo?.ToString() ?? _resourceManifest!.SplashLogo ?? "";
|
||||
}
|
||||
|
||||
public bool ShowLoadingBar()
|
||||
{
|
||||
return _resourceManifest!.ShowLoadingBar ?? _configurationManager.GetCVar(CVars.LoadingShowBar);
|
||||
}
|
||||
|
||||
internal bool StartupContinue(DisplayMode displayMode)
|
||||
{
|
||||
DebugTools.AssertNotNull(_resourceManifest);
|
||||
|
||||
_clyde.InitializePostWindowing();
|
||||
_audio.InitializePostWindowing();
|
||||
_clyde.SetWindowTitle(GameTitle());
|
||||
_loadscr.Initialize(42);
|
||||
|
||||
_taskManager.Initialize();
|
||||
_parallelMgr.Initialize();
|
||||
_loadscr.BeginLoadingSection("Init graphics", dontRender: true);
|
||||
_clyde.InitializePostWindowing();
|
||||
_clyde.SetWindowTitle(GameTitle());
|
||||
_loadscr.EndLoadingSection();
|
||||
|
||||
_loadscr.LoadingStep(_audio.InitializePostWindowing, "Init audio");
|
||||
|
||||
_loadscr.LoadingStep(_taskManager.Initialize, _taskManager);
|
||||
_loadscr.LoadingStep(_parallelMgr.Initialize, _parallelMgr);
|
||||
_fontManager.SetFontDpi((uint)_configurationManager.GetCVar(CVars.DisplayFontDpi));
|
||||
|
||||
// Load optional Robust modules.
|
||||
LoadOptionalRobustModules(displayMode, _resourceManifest!);
|
||||
_loadscr.LoadingStep(_systemFontManager.Initialize, "System fonts");
|
||||
|
||||
// Load optional Robust modules.
|
||||
_loadscr.LoadingStep(() => LoadOptionalRobustModules(displayMode, _resourceManifest!), "Robust Modules");
|
||||
|
||||
_loadscr.BeginLoadingSection(_modLoader);
|
||||
// Disable load context usage on content start.
|
||||
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
|
||||
_modLoader.SetUseLoadContext(!ContentStart);
|
||||
var disableSandbox = Environment.GetEnvironmentVariable("ROBUST_DISABLE_SANDBOX") == "1";
|
||||
_modLoader.SetEnableSandboxing(!disableSandbox && Options.Sandboxing);
|
||||
|
||||
if (!LoadModules())
|
||||
return false;
|
||||
|
||||
@@ -159,16 +175,23 @@ namespace Robust.Client
|
||||
_configurationManager.LoadCVarsFromAssembly(loadedModule);
|
||||
}
|
||||
|
||||
_serializationManager.Initialize();
|
||||
_loc.Initialize();
|
||||
_loadscr.EndLoadingSection();
|
||||
|
||||
_loadscr.LoadingStep(_serializationManager.Initialize, _serializationManager);
|
||||
_loadscr.LoadingStep(_loc.Initialize, _loc);
|
||||
|
||||
// Call Init in game assemblies.
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
|
||||
_loadscr.LoadingStep(() => _modLoader.BroadcastRunLevel(ModRunLevel.PreInit), "Content PreInit");
|
||||
|
||||
// Finish initialization of WebView if loaded.
|
||||
_webViewHook?.Initialize();
|
||||
_loadscr.LoadingStep(() =>
|
||||
{
|
||||
// Finish initialization of WebView if loaded.
|
||||
if (_webViewHook != null)
|
||||
_loadscr.LoadingStep(_webViewHook.Initialize, _webViewHook);
|
||||
},
|
||||
"WebView init");
|
||||
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
|
||||
_loadscr.LoadingStep(() => _modLoader.BroadcastRunLevel(ModRunLevel.Init), "Content Init");
|
||||
|
||||
// Start bad file extensions check after content init,
|
||||
// in case content screws with the VFS.
|
||||
@@ -177,42 +200,51 @@ namespace Robust.Client
|
||||
_configurationManager,
|
||||
_logManager.GetSawmill("res"));
|
||||
|
||||
_resourceCache.PreloadTextures();
|
||||
_networkManager.Initialize(false);
|
||||
_configurationManager.SetupNetworking();
|
||||
_serializer.Initialize();
|
||||
_inputManager.Initialize();
|
||||
_console.Initialize();
|
||||
_loadscr.LoadingStep(_resourceCache.PreloadTextures, "Texture preload");
|
||||
_loadscr.LoadingStep(() => { _networkManager.Initialize(false); }, _networkManager);
|
||||
_loadscr.LoadingStep(_configurationManager.SetupNetworking, _configurationManager);
|
||||
_loadscr.LoadingStep(_serializer.Initialize, _serializer);
|
||||
_loadscr.LoadingStep(_inputManager.Initialize, _inputManager);
|
||||
_loadscr.LoadingStep(_console.Initialize, _console);
|
||||
|
||||
// Make sure this is done before we try to load prototypes,
|
||||
// avoid any possibility of race conditions causing the check to not finish
|
||||
// before prototype load.
|
||||
ProgramShared.FinishCheckBadFileExtensions(checkBadExtensions);
|
||||
_loadscr.LoadingStep(
|
||||
() => ProgramShared.FinishCheckBadFileExtensions(checkBadExtensions),
|
||||
"Check bad file extensions");
|
||||
|
||||
_reload.Initialize();
|
||||
_reflectionManager.Initialize();
|
||||
_loadscr.LoadingStep(_reload.Initialize, _reload);
|
||||
_loadscr.LoadingStep(_reflectionManager.Initialize, _reflectionManager);
|
||||
_loadscr.LoadingStep(_xamlProxyManager.Initialize, _xamlProxyManager);
|
||||
_loadscr.LoadingStep(_xamlHotReloadManager.Initialize, _xamlHotReloadManager);
|
||||
_loadscr.BeginLoadingSection(_prototypeManager);
|
||||
_prototypeManager.Initialize();
|
||||
_prototypeManager.LoadDefaultPrototypes();
|
||||
_xamlProxyManager.Initialize();
|
||||
_xamlHotReloadManager.Initialize();
|
||||
_userInterfaceManager.Initialize();
|
||||
_eyeManager.Initialize();
|
||||
_entityManager.Initialize();
|
||||
_mapManager.Initialize();
|
||||
_gameStateManager.Initialize();
|
||||
_placementManager.Initialize();
|
||||
_viewVariablesManager.Initialize();
|
||||
_scriptClient.Initialize();
|
||||
_client.Initialize();
|
||||
_discord.Initialize();
|
||||
_tagManager.Initialize();
|
||||
_protoLoadMan.Initialize();
|
||||
_netResMan.Initialize();
|
||||
_replayLoader.Initialize();
|
||||
_replayPlayback.Initialize();
|
||||
_replayRecording.Initialize();
|
||||
_userInterfaceManager.PostInitialize();
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.PostInit);
|
||||
_loadscr.EndLoadingSection();
|
||||
_loadscr.LoadingStep(_userInterfaceManager.Initialize, "UI init");
|
||||
_loadscr.LoadingStep(_eyeManager.Initialize, _eyeManager);
|
||||
_loadscr.LoadingStep(_entityManager.Initialize, _entityManager);
|
||||
_loadscr.LoadingStep(_mapManager.Initialize, _mapManager);
|
||||
_loadscr.LoadingStep(_gameStateManager.Initialize, _gameStateManager);
|
||||
_loadscr.LoadingStep(_placementManager.Initialize, _placementManager);
|
||||
_loadscr.LoadingStep(_viewVariablesManager.Initialize, _viewVariablesManager);
|
||||
_loadscr.LoadingStep(_scriptClient.Initialize, _scriptClient);
|
||||
_loadscr.LoadingStep(_client.Initialize, _client);
|
||||
_loadscr.LoadingStep(_discord.Initialize, _discord);
|
||||
_loadscr.LoadingStep(_tagManager.Initialize, _tagManager);
|
||||
_loadscr.LoadingStep(_protoLoadMan.Initialize, _protoLoadMan);
|
||||
_loadscr.LoadingStep(_netResMan.Initialize, _netResMan);
|
||||
_loadscr.LoadingStep(_replayLoader.Initialize, _replayLoader);
|
||||
_loadscr.LoadingStep(_replayPlayback.Initialize, _replayPlayback);
|
||||
_loadscr.LoadingStep(_replayRecording.Initialize, _replayRecording);
|
||||
_loadscr.LoadingStep(_userInterfaceManager.PostInitialize, "UI postinit");
|
||||
|
||||
// Init stuff before this if at all possible.
|
||||
|
||||
_loadscr.LoadingStep(() => _modLoader.BroadcastRunLevel(ModRunLevel.PostInit), "Content PostInit");
|
||||
|
||||
_loadscr.Finish();
|
||||
|
||||
if (_commandLineArgs?.Username != null)
|
||||
{
|
||||
@@ -273,6 +305,9 @@ namespace Robust.Client
|
||||
}
|
||||
};
|
||||
|
||||
_configurationManager.OnValueChanged(CVars.DisplayMaxFPS, _ => UpdateVsyncConfig());
|
||||
_configurationManager.OnValueChanged(CVars.DisplayVSync, _ => UpdateVsyncConfig(), invokeImmediately: true);
|
||||
|
||||
_clyde.Ready();
|
||||
|
||||
if (_resourceManifest!.AutoConnect &&
|
||||
@@ -353,9 +388,6 @@ namespace Robust.Client
|
||||
var userDataDir = GetUserDataDir();
|
||||
|
||||
_configurationManager.Initialize(false);
|
||||
|
||||
// MUST load cvars before loading from config file so the cfg manager is aware of secure cvars.
|
||||
// So SECURE CVars are blacklisted from config.
|
||||
_configurationManager.LoadCVarsFromAssembly(typeof(GameController).Assembly); // Client
|
||||
_configurationManager.LoadCVarsFromAssembly(typeof(IConfigurationManager).Assembly); // Shared
|
||||
|
||||
@@ -387,7 +419,7 @@ namespace Robust.Client
|
||||
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
@@ -419,7 +451,8 @@ namespace Robust.Client
|
||||
_configurationManager.OverrideConVars(new[]
|
||||
{
|
||||
(CVars.DisplayWindowIconSet.Name, WindowIconSet()),
|
||||
(CVars.DisplaySplashLogo.Name, SplashLogo())
|
||||
(CVars.DisplaySplashLogo.Name, SplashLogo()),
|
||||
(CVars.LoadingShowBar.Name, ShowLoadingBar().ToString()),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -484,10 +517,18 @@ namespace Robust.Client
|
||||
|
||||
public void Shutdown(string? reason = null)
|
||||
{
|
||||
DebugTools.AssertNotNull(_mainLoop);
|
||||
if (_mainLoop == null)
|
||||
{
|
||||
if (!_dontStart)
|
||||
{
|
||||
_logger.Info($"Shutdown called before client init completed: {reason ?? "No reason provided"}");
|
||||
_dontStart = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Already got shut down I assume,
|
||||
if (!_mainLoop!.Running)
|
||||
if (!_mainLoop.Running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -709,6 +750,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();
|
||||
@@ -216,6 +219,35 @@ namespace Robust.Client.GameObjects
|
||||
base.TickUpdate(frameTime, noPredictions, histogram);
|
||||
}
|
||||
|
||||
internal override void ProcessQueueudDeletions()
|
||||
{
|
||||
base.ProcessQueueudDeletions();
|
||||
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 Is actually needed after the current predicted deletion fix?
|
||||
base.Dirty(uid, xform, meta);
|
||||
}
|
||||
}
|
||||
|
||||
_queuedPredictedDeletionsSet.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SendSystemNetworkMessage(EntityEventArgs message, bool recordReplay = true)
|
||||
{
|
||||
@@ -317,18 +349,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 +374,10 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
else
|
||||
{
|
||||
_xforms.DetachEntity(ent.Owner, ent.Comp2);
|
||||
if (!_queuedPredictedDeletionsSet.Add(ent.Owner))
|
||||
return;
|
||||
|
||||
_queuedPredictedDeletions.Enqueue(ent.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,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.
|
||||
@@ -601,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;
|
||||
@@ -1157,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;
|
||||
@@ -1336,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);
|
||||
}
|
||||
@@ -1663,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;
|
||||
@@ -1691,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;
|
||||
@@ -1711,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;
|
||||
|
||||
@@ -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!);
|
||||
|
||||
@@ -361,6 +361,9 @@ namespace Robust.Client.GameStates
|
||||
// avoid exception spam from repeatedly trying to reset the same entity.
|
||||
_entitySystemManager.GetEntitySystem<ClientDirtySystem>().Reset();
|
||||
_runtimeLog.LogException(e, "ResetPredictedEntities");
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
|
||||
// If we were waiting for a new state, we are now applying it.
|
||||
@@ -541,6 +544,11 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
((IBroadcastEventBusInternal)_entities.EventBus).ProcessEventQueue();
|
||||
}
|
||||
|
||||
using (_prof.Group("QueueDel"))
|
||||
{
|
||||
_entities.ProcessQueueudDeletions();
|
||||
}
|
||||
}
|
||||
|
||||
_prof.WriteGroupEnd(groupStart, "Prediction tick", ProfData.Int64(_timing.CurTick.Value));
|
||||
@@ -949,6 +957,9 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
_sawmill.Error($"Caught exception while deleting entities");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(ApplyEntityStates)}");
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
|
||||
@@ -76,9 +76,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
// Short path to render only the splash.
|
||||
if (_drawingSplash)
|
||||
if (_drawingLoadingScreen)
|
||||
{
|
||||
DrawSplash(_renderHandle);
|
||||
DrawLoadingScreen(_renderHandle);
|
||||
FlushRenderQueue();
|
||||
SwapAllBuffers();
|
||||
return;
|
||||
@@ -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.
|
||||
@@ -417,18 +430,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
FlushRenderQueue();
|
||||
}
|
||||
|
||||
private void DrawSplash(IRenderHandle handle)
|
||||
private void DrawLoadingScreen(IRenderHandle handle)
|
||||
{
|
||||
// Clear screen to black for splash.
|
||||
ClearFramebuffer(Color.Black);
|
||||
|
||||
var splashTex = _cfg.GetCVar(CVars.DisplaySplashLogo);
|
||||
if (string.IsNullOrEmpty(splashTex))
|
||||
return;
|
||||
|
||||
var texture = _resourceCache.GetResource<TextureResource>(splashTex).Texture;
|
||||
|
||||
handle.DrawingHandleScreen.DrawTexture(texture, (ScreenSize - texture.Size) / 2);
|
||||
_loadingScreenManager.DrawLoadingScreen(handle, ScreenSize);
|
||||
}
|
||||
|
||||
private void RenderInRenderTarget(RenderTargetBase rt, Action a, Color? clearColor=default)
|
||||
|
||||
@@ -451,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"))
|
||||
@@ -504,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)
|
||||
|
||||
@@ -161,47 +161,33 @@ internal partial class Clyde
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is effectively a specialized combination of a <see cref="Matrix3.TransformBox(in Box2Rotated)"/> and <see cref="Box2Rotated.CalcBoundingBox()"/>.
|
||||
/// This is effectively a specialized combination of a <see cref="Matrix3Helpers.TransformBox(Matrix3x2, in Box2)"/>.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe Box2 TransformCenteredBox(in Box2 box, float angle, in Vector2 offset, in Vector2 scale)
|
||||
internal static unsafe Box2 TransformCenteredBox(in Box2 box, float angle, in Vector2 offset, in Vector2 scale)
|
||||
{
|
||||
// This function is for sprites, which flip the y axis, so here we flip the definition of t and b relative to the normal function.
|
||||
DebugTools.Assert(scale.Y < 0);
|
||||
|
||||
var boxVec = Unsafe.As<Box2, Vector128<float>>(ref Unsafe.AsRef(in box));
|
||||
|
||||
var sin = Vector128.Create(MathF.Sin(angle));
|
||||
var cos = Vector128.Create(MathF.Cos(angle));
|
||||
var allX = Vector128.Shuffle(boxVec, Vector128.Create(0, 0, 2, 2));
|
||||
var allY = Vector128.Shuffle(boxVec, Vector128.Create(1, 3, 3, 1));
|
||||
var modX = allX * cos - allY * sin;
|
||||
var modY = allX * sin + allY * cos;
|
||||
var boxX = Vector128.Shuffle(boxVec, Vector128.Create(0, 0, 2, 2));
|
||||
var boxY = Vector128.Shuffle(boxVec, Vector128.Create(1, 3, 3, 1));
|
||||
|
||||
var x = boxX * cos - boxY * sin;
|
||||
var y = boxX * sin + boxY * cos;
|
||||
var lbrt = SimdHelpers.GetAABB(x, y);
|
||||
|
||||
// This function is for sprites, which flip the y-axis via the scale, so we need to flip t & b.
|
||||
DebugTools.Assert(scale.Y < 0);
|
||||
lbrt = Vector128.Shuffle(lbrt, Vector128.Create(0,3,2,1));
|
||||
|
||||
var offsetVec = Unsafe.As<Vector2, Vector128<float>>(ref Unsafe.AsRef(in offset)); // upper undefined
|
||||
var scaleVec = Unsafe.As<Vector2, Vector128<float>>(ref Unsafe.AsRef(in scale)); // upper undefined
|
||||
offsetVec = Vector128.Shuffle(offsetVec, Vector128.Create(0, 1, 0, 1));
|
||||
scaleVec = Vector128.Shuffle(scaleVec, Vector128.Create(0, 1, 0, 1));
|
||||
|
||||
Vector128<float> lbrt;
|
||||
if (Sse.IsSupported)
|
||||
{
|
||||
var lrlr = SimdHelpers.MinMaxHorizontalSse(modX);
|
||||
var btbt = SimdHelpers.MaxMinHorizontalSse(modY);
|
||||
lbrt = Sse.UnpackLow(lrlr, btbt);
|
||||
}
|
||||
else
|
||||
{
|
||||
var l = SimdHelpers.MinHorizontal128(allX);
|
||||
var b = SimdHelpers.MaxHorizontal128(allY);
|
||||
var r = SimdHelpers.MaxHorizontal128(allX);
|
||||
var t = SimdHelpers.MinHorizontal128(allY);
|
||||
lbrt = SimdHelpers.MergeRows128(l, b, r, t);
|
||||
}
|
||||
|
||||
// offset and scale box.
|
||||
// note that the scaling here is scaling the whole space, not jut the box. I.e., the centre of the box is changing
|
||||
lbrt = (lbrt + offsetVec) * scaleVec;
|
||||
|
||||
return Unsafe.As<Vector128<float>, Box2>(ref lbrt);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<(string? name, ViewportDisposeData data)> _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,44 @@ 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.data, data.name, wasLeaked: true);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeViewport(ViewportDisposeData disposeData, string? name = null, bool wasLeaked = false)
|
||||
{
|
||||
if (wasLeaked)
|
||||
_clydeSawmill.Warning($"Viewport {disposeData.Id} ({name ?? "null"}) 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 +127,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 +210,56 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_clyde.RenderOverlaysDirect(this, control, handle, OverlaySpace.ScreenSpace, viewportBounds);
|
||||
}
|
||||
|
||||
~Viewport()
|
||||
{
|
||||
_clyde._viewportDisposeQueue.Enqueue((Name, 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), Name);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -114,8 +119,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
break;
|
||||
default:
|
||||
_logManager.GetSawmill("clyde.win").Log(
|
||||
LogLevel.Error, "Unknown windowing API: {name}. Falling back to GLFW.", windowingApi);
|
||||
goto case "glfw";
|
||||
LogLevel.Error, "Unknown windowing API: {name}. Falling back to SDL3.", windowingApi);
|
||||
goto case "sdl3";
|
||||
}
|
||||
|
||||
_windowing = 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)
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
[Dependency] private readonly ClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IReloadManager _reloads = default!;
|
||||
[Dependency] private readonly LoadingScreenManager _loadingScreenManager = default!;
|
||||
|
||||
private GLUniformBuffer<ProjViewMatrices> ProjViewUBO = default!;
|
||||
private GLUniformBuffer<UniformConstants> UniformConstantsUBO = default!;
|
||||
@@ -68,7 +69,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// VAO is per-window and not stored (not necessary!)
|
||||
private GLBuffer WindowVBO = default!;
|
||||
|
||||
private bool _drawingSplash = true;
|
||||
private bool _drawingLoadingScreen = true;
|
||||
|
||||
private GLShaderProgram? _currentProgram;
|
||||
|
||||
@@ -114,7 +115,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 +128,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);
|
||||
|
||||
@@ -210,7 +214,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public void Ready()
|
||||
{
|
||||
_drawingSplash = false;
|
||||
_drawingLoadingScreen = false;
|
||||
|
||||
InitLighting();
|
||||
}
|
||||
|
||||
@@ -34,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)
|
||||
{
|
||||
@@ -75,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;
|
||||
@@ -240,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()
|
||||
@@ -307,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()
|
||||
@@ -482,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));
|
||||
|
||||
@@ -499,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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -146,7 +146,7 @@ internal partial class Clyde
|
||||
MapKey(SC.SDL_SCANCODE_RALT, Key.Alt);
|
||||
MapKey(SC.SDL_SCANCODE_LGUI, Key.LSystem);
|
||||
MapKey(SC.SDL_SCANCODE_RGUI, Key.RSystem);
|
||||
MapKey(SC.SDL_SCANCODE_MENU, Key.Menu);
|
||||
MapKey(SC.SDL_SCANCODE_APPLICATION, Key.Menu);
|
||||
MapKey(SC.SDL_SCANCODE_LEFTBRACKET, Key.LBracket);
|
||||
MapKey(SC.SDL_SCANCODE_RIGHTBRACKET, Key.RBracket);
|
||||
MapKey(SC.SDL_SCANCODE_SEMICOLON, Key.SemiColon);
|
||||
@@ -173,7 +173,7 @@ internal partial class Clyde
|
||||
MapKey(SC.SDL_SCANCODE_KP_MINUS, Key.NumpadSubtract);
|
||||
MapKey(SC.SDL_SCANCODE_KP_DIVIDE, Key.NumpadDivide);
|
||||
MapKey(SC.SDL_SCANCODE_KP_MULTIPLY, Key.NumpadMultiply);
|
||||
MapKey(SC.SDL_SCANCODE_KP_DECIMAL, Key.NumpadDecimal);
|
||||
MapKey(SC.SDL_SCANCODE_KP_PERIOD, Key.NumpadDecimal);
|
||||
MapKey(SC.SDL_SCANCODE_LEFT, Key.Left);
|
||||
MapKey(SC.SDL_SCANCODE_RIGHT, Key.Right);
|
||||
MapKey(SC.SDL_SCANCODE_UP, Key.Up);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -104,6 +104,12 @@ namespace Robust.Client.Graphics
|
||||
Handle = IoCManager.Resolve<IFontManagerInternal>().MakeInstance(res.FontFaceHandle, size);
|
||||
}
|
||||
|
||||
internal VectorFont(IFontInstanceHandle handle, int size)
|
||||
{
|
||||
Size = size;
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
public override int GetAscent(float scale) => Handle.GetAscent(scale);
|
||||
public override int GetHeight(float scale) => Handle.GetHeight(scale);
|
||||
public override int GetDescent(float scale) => Handle.GetDescent(scale);
|
||||
@@ -222,4 +228,74 @@ namespace Robust.Client.Graphics
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible values for font weights. Larger values have thicker font strokes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// These values are based on the <c>usWeightClass</c> property of the OpenType specification:
|
||||
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2#usweightclass
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="ISystemFontFace.Weight"/>
|
||||
public enum FontWeight : ushort
|
||||
{
|
||||
Thin = 100,
|
||||
ExtraLight = 200,
|
||||
UltraLight = ExtraLight,
|
||||
Light = 300,
|
||||
SemiLight = 350,
|
||||
Normal = 400,
|
||||
Regular = Normal,
|
||||
Medium = 500,
|
||||
SemiBold = 600,
|
||||
DemiBold = SemiBold,
|
||||
Bold = 700,
|
||||
ExtraBold = 800,
|
||||
UltraBold = ExtraBold,
|
||||
Black = 900,
|
||||
Heavy = Black,
|
||||
ExtraBlack = 950,
|
||||
UltraBlack = ExtraBlack,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible slant values for fonts.
|
||||
/// </summary>
|
||||
/// <seealso cref="ISystemFontFace.Slant"/>
|
||||
public enum FontSlant : byte
|
||||
{
|
||||
// NOTE: Enum values correspond to DWRITE_FONT_STYLE.
|
||||
Normal = 0,
|
||||
Oblique = 1,
|
||||
|
||||
// FUN FACT: they're called "italics" because they look like the Leaning Tower of Pisa.
|
||||
// Don't fact-check that.
|
||||
Italic = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible values for font widths. Larger values are proportionally wider.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// These values are based on the <c>usWidthClass</c> property of the OpenType specification:
|
||||
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="ISystemFontFace.Width"/>
|
||||
public enum FontWidth : ushort
|
||||
{
|
||||
UltraCondensed = 1,
|
||||
ExtraCondensed = 2,
|
||||
Condensed = 3,
|
||||
SemiCondensed = 4,
|
||||
Normal = 5,
|
||||
Medium = Normal,
|
||||
SemiExpanded = 6,
|
||||
Expanded = 7,
|
||||
ExtraExpanded = 8,
|
||||
UltraExpanded = 9,
|
||||
}
|
||||
}
|
||||
|
||||
15
Robust.Client/Graphics/FontManagement/SystemFontDebug.cs
Normal file
15
Robust.Client/Graphics/FontManagement/SystemFontDebug.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Robust.Client.Graphics.FontManagement;
|
||||
|
||||
internal sealed class SystemFontDebugCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "system_font_debug";
|
||||
public string Description => "";
|
||||
public string Help => "";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
new SystemFontDebugWindow().OpenCentered();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="System font debug">
|
||||
<SplitContainer Orientation="Horizontal" MinSize="800 600">
|
||||
<ScrollContainer HScrollEnabled="False">
|
||||
<BoxContainer Name="SelectorContainer" Orientation="Vertical" />
|
||||
</ScrollContainer>
|
||||
<ScrollContainer HScrollEnabled="False">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Name="FamilyLabel" />
|
||||
<BoxContainer Orientation="Vertical" Name="FaceContainer" />
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</SplitContainer>
|
||||
</DefaultWindow>
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Linq;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Graphics.FontManagement;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
internal sealed partial class SystemFontDebugWindow : DefaultWindow
|
||||
{
|
||||
private static readonly int[] ExampleFontSizes = [8, 12, 16, 24, 36];
|
||||
private const string ExampleString = "The quick brown fox jumps over the lazy dog";
|
||||
|
||||
[Dependency] private readonly ISystemFontManager _systemFontManager = default!;
|
||||
|
||||
public SystemFontDebugWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
var buttonGroup = new ButtonGroup();
|
||||
|
||||
foreach (var group in _systemFontManager.SystemFontFaces.GroupBy(k => k.FamilyName).OrderBy(k => k.Key))
|
||||
{
|
||||
var fonts = group.ToArray();
|
||||
SelectorContainer.AddChild(new Selector(this, buttonGroup, group.Key, fonts));
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectFontFamily(ISystemFontFace[] fonts)
|
||||
{
|
||||
FamilyLabel.Text = fonts[0].FamilyName;
|
||||
|
||||
FaceContainer.RemoveAllChildren();
|
||||
|
||||
foreach (var font in fonts)
|
||||
{
|
||||
var exampleContainer = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
Margin = new Thickness(8)
|
||||
};
|
||||
|
||||
foreach (var size in ExampleFontSizes)
|
||||
{
|
||||
var fontInstance = font.Load(size);
|
||||
|
||||
var richTextLabel = new RichTextLabel
|
||||
{
|
||||
Stylesheet = new Stylesheet([
|
||||
StylesheetHelpers.Element<RichTextLabel>().Prop("font", fontInstance)
|
||||
]),
|
||||
};
|
||||
richTextLabel.SetMessage(FormattedMessage.FromUnformatted(ExampleString));
|
||||
exampleContainer.AddChild(richTextLabel);
|
||||
}
|
||||
|
||||
FaceContainer.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
Children =
|
||||
{
|
||||
new RichTextLabel
|
||||
{
|
||||
Text = $"""
|
||||
{font.FullName}
|
||||
Family: "{font.FamilyName}", face: "{font.FaceName}", PostScript = "{font.PostscriptName}"
|
||||
Weight: {font.Weight} ({(int) font.Weight}), slant: {font.Slant} ({(int) font.Slant}), width: {font.Width} ({(int) font.Width})
|
||||
""",
|
||||
},
|
||||
exampleContainer
|
||||
},
|
||||
Margin = new Thickness(0, 0, 0, 8)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Selector : Control
|
||||
{
|
||||
public Selector(SystemFontDebugWindow window, ButtonGroup group, string family, ISystemFontFace[] fonts)
|
||||
{
|
||||
var button = new Button
|
||||
{
|
||||
Text = family,
|
||||
Group = group,
|
||||
ToggleMode = true
|
||||
};
|
||||
AddChild(button);
|
||||
|
||||
button.OnPressed += _ => window.SelectFontFamily(fonts);
|
||||
}
|
||||
}
|
||||
}
|
||||
170
Robust.Client/Graphics/FontManagement/SystemFontManagerBase.cs
Normal file
170
Robust.Client/Graphics/FontManagement/SystemFontManagerBase.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.MemoryMappedFiles;
|
||||
using System.Threading;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Client.Graphics.FontManagement;
|
||||
|
||||
internal abstract class SystemFontManagerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The "standard" locale used when looking up the PostScript name of a font face.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Font files allow the PostScript name to be localized, however in practice
|
||||
/// we would really like to have a language-unambiguous identifier to refer to a font file.
|
||||
/// We use this locale (en-US) to look up teh PostScript font name, if there are multiple provided.
|
||||
/// This matches the behavior of the Local Font Access web API:
|
||||
/// https://wicg.github.io/local-font-access/#concept-font-representation
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
protected static readonly CultureInfo StandardLocale = new("en-US", false);
|
||||
|
||||
protected readonly IFontManagerInternal FontManager;
|
||||
protected readonly ISawmill Sawmill;
|
||||
|
||||
protected readonly Lock Lock = new();
|
||||
protected readonly List<BaseHandle> Fonts = [];
|
||||
|
||||
public IEnumerable<ISystemFontFace> SystemFontFaces { get; }
|
||||
|
||||
public SystemFontManagerBase(ILogManager logManager, IFontManagerInternal fontManager)
|
||||
{
|
||||
FontManager = fontManager;
|
||||
Sawmill = logManager.GetSawmill("font.system");
|
||||
|
||||
SystemFontFaces = Fonts.AsReadOnly();
|
||||
}
|
||||
|
||||
protected abstract IFontFaceHandle LoadFontFace(BaseHandle handle);
|
||||
|
||||
protected static string GetLocalizedForLocaleOrFirst(LocalizedStringSet set, CultureInfo culture)
|
||||
{
|
||||
var matchCulture = culture;
|
||||
while (!Equals(matchCulture, CultureInfo.InvariantCulture))
|
||||
{
|
||||
if (set.Values.TryGetValue(culture.Name, out var value))
|
||||
return value;
|
||||
|
||||
matchCulture = matchCulture.Parent;
|
||||
}
|
||||
|
||||
return set.Values[set.Primary];
|
||||
}
|
||||
|
||||
protected abstract class BaseHandle(SystemFontManagerBase parent) : ISystemFontFace
|
||||
{
|
||||
private IFontFaceHandle? _cachedFont;
|
||||
|
||||
public required string PostscriptName { get; init; }
|
||||
|
||||
public required LocalizedStringSet FullNames;
|
||||
public required LocalizedStringSet FamilyNames;
|
||||
public required LocalizedStringSet FaceNames;
|
||||
|
||||
public required FontWeight Weight { get; init; }
|
||||
public required FontSlant Slant { get; init; }
|
||||
public required FontWidth Width { get; init; }
|
||||
|
||||
public string FullName => GetLocalizedFullName(CultureInfo.CurrentCulture);
|
||||
public string FamilyName => GetLocalizedFamilyName(CultureInfo.CurrentCulture);
|
||||
public string FaceName => GetLocalizedFaceName(CultureInfo.CurrentCulture);
|
||||
|
||||
public string GetLocalizedFullName(CultureInfo culture)
|
||||
{
|
||||
return GetLocalizedForLocaleOrFirst(FullNames, culture);
|
||||
}
|
||||
|
||||
public string GetLocalizedFamilyName(CultureInfo culture)
|
||||
{
|
||||
return GetLocalizedForLocaleOrFirst(FamilyNames, culture);
|
||||
}
|
||||
|
||||
public string GetLocalizedFaceName(CultureInfo culture)
|
||||
{
|
||||
return GetLocalizedForLocaleOrFirst(FaceNames, culture);
|
||||
}
|
||||
|
||||
public Font Load(int size)
|
||||
{
|
||||
var handle = GetFaceHandle();
|
||||
|
||||
var instance = parent.FontManager.MakeInstance(handle, size);
|
||||
|
||||
return new VectorFont(instance, size);
|
||||
}
|
||||
|
||||
private IFontFaceHandle GetFaceHandle()
|
||||
{
|
||||
lock (parent.Lock)
|
||||
{
|
||||
if (_cachedFont != null)
|
||||
return _cachedFont;
|
||||
|
||||
parent.Sawmill.Verbose($"Loading system font face: {PostscriptName}");
|
||||
|
||||
return _cachedFont = parent.LoadFontFace(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected struct LocalizedStringSet
|
||||
{
|
||||
public static readonly LocalizedStringSet Empty = FromSingle("");
|
||||
|
||||
/// <summary>
|
||||
/// The first locale to appear in the list of localized strings.
|
||||
/// Used as fallback if the desired locale is not provided.
|
||||
/// </summary>
|
||||
public required string Primary;
|
||||
public required Dictionary<string, string> Values;
|
||||
|
||||
public static LocalizedStringSet FromSingle(string value, string language = "en")
|
||||
{
|
||||
return new LocalizedStringSet
|
||||
{
|
||||
Primary = language,
|
||||
Values = new Dictionary<string, string> { { language, value } }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected sealed class MemoryMappedFontMemoryHandle : IFontMemoryHandle
|
||||
{
|
||||
private readonly MemoryMappedFile _mappedFile;
|
||||
private readonly MemoryMappedViewAccessor _accessor;
|
||||
|
||||
public MemoryMappedFontMemoryHandle(string filePath)
|
||||
{
|
||||
_mappedFile = MemoryMappedFile.CreateFromFile(
|
||||
filePath,
|
||||
FileMode.Open,
|
||||
null,
|
||||
0,
|
||||
MemoryMappedFileAccess.Read);
|
||||
|
||||
_accessor = _mappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read);
|
||||
}
|
||||
|
||||
public unsafe byte* GetData()
|
||||
{
|
||||
byte* pointer = null;
|
||||
_accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
public nint GetDataSize()
|
||||
{
|
||||
return (nint)_accessor.Capacity;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_accessor.Dispose();
|
||||
_mappedFile.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
#if MACOS
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Client.Interop.MacOS;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using CF = Robust.Client.Interop.MacOS.CoreFoundation;
|
||||
using CT = Robust.Client.Interop.MacOS.CoreText;
|
||||
|
||||
namespace Robust.Client.Graphics.FontManagement;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="ISystemFontManager"/> that uses CoreText on macOS.
|
||||
/// </summary>
|
||||
internal sealed class SystemFontManagerCoreText : SystemFontManagerBase, ISystemFontManagerInternal
|
||||
{
|
||||
private static readonly FontWidth[] FontWidths = Enum.GetValues<FontWidth>();
|
||||
|
||||
public bool IsSupported => true;
|
||||
|
||||
public SystemFontManagerCoreText(ILogManager logManager, IFontManagerInternal fontManager) : base(logManager,
|
||||
fontManager)
|
||||
{
|
||||
}
|
||||
|
||||
public unsafe void Initialize()
|
||||
{
|
||||
Sawmill.Verbose("Getting CTFontCollection...");
|
||||
|
||||
var collection = CT.CTFontCollectionCreateFromAvailableFonts(null);
|
||||
var array = CT.CTFontCollectionCreateMatchingFontDescriptors(collection);
|
||||
|
||||
var count = CF.CFArrayGetCount(array);
|
||||
Sawmill.Verbose($"Have {count} descriptors...");
|
||||
|
||||
for (nint i = 0; i < count.Value; i++)
|
||||
{
|
||||
var item = (__CTFontDescriptor*)CF.CFRetain(CF.CFArrayGetValueAtIndex(array, new CLong(i)));
|
||||
|
||||
try
|
||||
{
|
||||
LoadFontDescriptor(item);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Sawmill.Error($"Failed to load font descriptor: {ex}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
CF.CFRelease(item);
|
||||
}
|
||||
}
|
||||
|
||||
CF.CFRelease(array);
|
||||
CF.CFRelease(collection);
|
||||
}
|
||||
|
||||
private unsafe void LoadFontDescriptor(__CTFontDescriptor* descriptor)
|
||||
{
|
||||
var displayName = GetFontAttributeManaged(descriptor, CT.kCTFontDisplayNameAttribute);
|
||||
var postscriptName = GetFontAttributeManaged(descriptor, CT.kCTFontNameAttribute);
|
||||
var familyName = GetFontAttributeManaged(descriptor, CT.kCTFontFamilyNameAttribute);
|
||||
var styleName = GetFontAttributeManaged(descriptor, CT.kCTFontStyleNameAttribute);
|
||||
|
||||
var url = (__CFURL*)CT.CTFontDescriptorCopyAttribute(descriptor, CT.kCTFontURLAttribute);
|
||||
|
||||
const int maxPath = 1024;
|
||||
var buf = stackalloc byte[maxPath];
|
||||
var result = CF.CFURLGetFileSystemRepresentation(url, 1, buf, new CLong(maxPath));
|
||||
if (result == 0)
|
||||
throw new Exception("CFURLGetFileSystemRepresentation failed!");
|
||||
|
||||
// Sawmill.Verbose(CF.CFStringToManaged(CF.CFURLGetString(url)));
|
||||
|
||||
CF.CFRelease(url);
|
||||
|
||||
var traits = (__CFDictionary*)CT.CTFontDescriptorCopyAttribute(descriptor, CT.kCTFontTraitsAttribute);
|
||||
var (weight, slant, width) = ParseTraits(traits);
|
||||
|
||||
CF.CFRelease(traits);
|
||||
|
||||
var path = Marshal.PtrToStringUTF8((nint)buf)!;
|
||||
|
||||
Fonts.Add(new Handle(this)
|
||||
{
|
||||
PostscriptName = postscriptName,
|
||||
FullNames = LocalizedStringSet.FromSingle(displayName),
|
||||
FamilyNames = LocalizedStringSet.FromSingle(familyName),
|
||||
FaceNames = LocalizedStringSet.FromSingle(styleName),
|
||||
Weight = weight,
|
||||
Slant = slant,
|
||||
Width = width,
|
||||
Path = path
|
||||
});
|
||||
}
|
||||
|
||||
private static unsafe (FontWeight, FontSlant, FontWidth) ParseTraits(__CFDictionary* dictionary)
|
||||
{
|
||||
var weight = FontWeight.Normal;
|
||||
var slant = FontSlant.Normal;
|
||||
var width = FontWidth.Normal;
|
||||
|
||||
var weightVal = (__CFNumber*)CF.CFDictionaryGetValue(dictionary, CT.kCTFontWeightTrait);
|
||||
if (weightVal != null)
|
||||
weight = ConvertWeight(weightVal);
|
||||
|
||||
var slantVal = (__CFNumber*)CF.CFDictionaryGetValue(dictionary, CT.kCTFontSlantTrait);
|
||||
if (slantVal != null)
|
||||
slant = ConvertSlant(slantVal);
|
||||
|
||||
var widthVal = (__CFNumber*)CF.CFDictionaryGetValue(dictionary, CT.kCTFontWidthTrait);
|
||||
if (widthVal != null)
|
||||
width = ConvertWidth(widthVal);
|
||||
|
||||
return (weight, slant, width);
|
||||
}
|
||||
|
||||
private static readonly (float, FontWeight)[] FontWeightTable =
|
||||
[
|
||||
((float) AppKit.NSFontWeightUltraLight, FontWeight.UltraLight),
|
||||
((float) AppKit.NSFontWeightThin, FontWeight.Thin),
|
||||
((float) AppKit.NSFontWeightLight, FontWeight.Light),
|
||||
((float) AppKit.NSFontWeightRegular, FontWeight.Regular),
|
||||
((float) AppKit.NSFontWeightMedium, FontWeight.Medium),
|
||||
((float) AppKit.NSFontWeightSemiBold, FontWeight.SemiBold),
|
||||
((float) AppKit.NSFontWeightBold, FontWeight.Bold),
|
||||
((float) AppKit.NSFontWeightHeavy, FontWeight.Heavy),
|
||||
((float) AppKit.NSFontWeightBlack, FontWeight.Black)
|
||||
];
|
||||
|
||||
private static unsafe FontWeight ConvertWeight(__CFNumber* number)
|
||||
{
|
||||
float val;
|
||||
CF.CFNumberGetValue(number, new CLong(CF.kCFNumberFloat32Type), &val);
|
||||
|
||||
var valCopy = val;
|
||||
return FontWeightTable.MinBy(tup => Math.Abs(tup.Item1 - valCopy)).Item2;
|
||||
}
|
||||
|
||||
private static unsafe FontWidth ConvertWidth(__CFNumber* number)
|
||||
{
|
||||
float val;
|
||||
CF.CFNumberGetValue(number, new CLong(CF.kCFNumberFloat32Type), &val);
|
||||
|
||||
// Normalize to 0-1 range
|
||||
val = (val + 1) / 2;
|
||||
var lerped = MathHelper.Lerp((float)FontWidths[0], (float)FontWidths[^1], val);
|
||||
return FontWidths.MinBy(x => Math.Abs((float)x - lerped));
|
||||
}
|
||||
|
||||
private static unsafe FontSlant ConvertSlant(__CFNumber* number)
|
||||
{
|
||||
float val;
|
||||
CF.CFNumberGetValue(number, new CLong(CF.kCFNumberFloat32Type), &val);
|
||||
|
||||
// Normalize to 0-1 range
|
||||
return val == 0 ? FontSlant.Normal : FontSlant.Italic;
|
||||
}
|
||||
|
||||
private static unsafe string GetFontAttributeManaged(__CTFontDescriptor* descriptor, __CFString* key)
|
||||
{
|
||||
var str = (__CFString*)CT.CTFontDescriptorCopyAttribute(descriptor, key);
|
||||
|
||||
try
|
||||
{
|
||||
return CF.CFStringToManaged(str);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CF.CFRelease(str);
|
||||
}
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
protected override IFontFaceHandle LoadFontFace(BaseHandle handle)
|
||||
{
|
||||
var path = ((Handle)handle).Path;
|
||||
Sawmill.Verbose(path);
|
||||
|
||||
// CTFontDescriptor does not seem to have any way to identify *which* index in the font file should be accessed.
|
||||
// So we have to just load every one until the postscript name matches.
|
||||
return FontManager.LoadWithPostscriptName(new MemoryMappedFontMemoryHandle(path), handle.PostscriptName);
|
||||
}
|
||||
|
||||
private sealed class Handle(SystemFontManagerCoreText parent) : BaseHandle(parent)
|
||||
{
|
||||
public required string Path;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,503 @@
|
||||
#if WINDOWS
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
using static TerraFX.Interop.DirectX.DWRITE_FACTORY_TYPE;
|
||||
using static TerraFX.Interop.DirectX.DWRITE_FONT_PROPERTY_ID;
|
||||
using static TerraFX.Interop.Windows.Windows;
|
||||
|
||||
namespace Robust.Client.Graphics.FontManagement;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="ISystemFontManager"/> that uses DirectWrite on Windows.
|
||||
/// </summary>
|
||||
internal sealed unsafe class SystemFontManagerDirectWrite : SystemFontManagerBase, ISystemFontManagerInternal
|
||||
{
|
||||
// For future implementors of other platforms:
|
||||
// a significant amount of code in this file will be shareable with that of other platforms,
|
||||
// so some refactoring is warranted.
|
||||
|
||||
private readonly IConfigurationManager _cfg;
|
||||
|
||||
private IDWriteFactory3* _dWriteFactory;
|
||||
private IDWriteFontSet* _systemFontSet;
|
||||
|
||||
public bool IsSupported => true;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="ISystemFontManager"/> that uses DirectWrite on Windows.
|
||||
/// </summary>
|
||||
public SystemFontManagerDirectWrite(
|
||||
ILogManager logManager,
|
||||
IConfigurationManager cfg,
|
||||
IFontManagerInternal fontManager)
|
||||
: base(logManager, fontManager)
|
||||
{
|
||||
_cfg = cfg;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
CreateDWriteFactory();
|
||||
|
||||
_systemFontSet = GetSystemFontSet(_dWriteFactory);
|
||||
|
||||
lock (Lock)
|
||||
{
|
||||
var fontCount = _systemFontSet->GetFontCount();
|
||||
for (var i = 0u; i < fontCount; i++)
|
||||
{
|
||||
LoadSingleFontFromSet(_systemFontSet, i);
|
||||
}
|
||||
}
|
||||
|
||||
Sawmill.Verbose($"Loaded {Fonts.Count} fonts");
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
_systemFontSet->Release();
|
||||
_systemFontSet = null;
|
||||
|
||||
_dWriteFactory->Release();
|
||||
_dWriteFactory = null;
|
||||
|
||||
lock (Lock)
|
||||
{
|
||||
foreach (var systemFont in Fonts)
|
||||
{
|
||||
((Handle)systemFont).FontFace->Release();
|
||||
}
|
||||
|
||||
Fonts.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadSingleFontFromSet(IDWriteFontSet* set, uint fontIndex)
|
||||
{
|
||||
// Get basic parameters that every font should probably have?
|
||||
if (!TryGetStringsSet(set, fontIndex, DWRITE_FONT_PROPERTY_ID_POSTSCRIPT_NAME, out var postscriptNames))
|
||||
return;
|
||||
|
||||
if (!TryGetStringsSet(set, fontIndex, DWRITE_FONT_PROPERTY_ID_FULL_NAME, out var fullNames))
|
||||
return;
|
||||
|
||||
if (!TryGetStringsSet(set, fontIndex, DWRITE_FONT_PROPERTY_ID_FAMILY_NAME, out var familyNames))
|
||||
return;
|
||||
|
||||
if (!TryGetStringsSet(set, fontIndex, DWRITE_FONT_PROPERTY_ID_FACE_NAME, out var faceNames))
|
||||
return;
|
||||
|
||||
// I assume these parameters can't be missing in practice, but better safe than sorry.
|
||||
TryGetStrings(set, fontIndex, DWRITE_FONT_PROPERTY_ID_WEIGHT, out var weight);
|
||||
TryGetStrings(set, fontIndex, DWRITE_FONT_PROPERTY_ID_STYLE, out var style);
|
||||
TryGetStrings(set, fontIndex, DWRITE_FONT_PROPERTY_ID_STRETCH, out var stretch);
|
||||
|
||||
var parsedWeight = ParseFontWeight(weight);
|
||||
var parsedSlant = ParseFontSlant(style);
|
||||
var parsedWidth = ParseFontWidth(stretch);
|
||||
|
||||
IDWriteFontFaceReference* reference = null;
|
||||
var result = set->GetFontFaceReference(fontIndex, &reference);
|
||||
ThrowIfFailed(result);
|
||||
|
||||
var handle = new Handle(this, reference)
|
||||
{
|
||||
PostscriptName = GetLocalizedForLocaleOrFirst(postscriptNames, StandardLocale),
|
||||
FullNames = fullNames,
|
||||
FamilyNames = familyNames,
|
||||
FaceNames = faceNames,
|
||||
Weight = parsedWeight,
|
||||
Slant = parsedSlant,
|
||||
Width = parsedWidth
|
||||
};
|
||||
|
||||
Fonts.Add(handle);
|
||||
}
|
||||
|
||||
private static FontWeight ParseFontWeight(DWriteLocalizedString[]? strings)
|
||||
{
|
||||
if (strings == null)
|
||||
return FontWeight.Regular;
|
||||
|
||||
return (FontWeight)Parse.Int32(strings[0].Value);
|
||||
}
|
||||
|
||||
private static FontSlant ParseFontSlant(DWriteLocalizedString[]? strings)
|
||||
{
|
||||
if (strings == null)
|
||||
return FontSlant.Normal;
|
||||
|
||||
return (FontSlant)Parse.Int32(strings[0].Value);
|
||||
}
|
||||
|
||||
private static FontWidth ParseFontWidth(DWriteLocalizedString[]? strings)
|
||||
{
|
||||
if (strings == null)
|
||||
return FontWidth.Normal;
|
||||
|
||||
return (FontWidth)Parse.Int32(strings[0].Value);
|
||||
}
|
||||
|
||||
private void CreateDWriteFactory()
|
||||
{
|
||||
fixed (IDWriteFactory3** pFactory = &_dWriteFactory)
|
||||
{
|
||||
var result = DirectX.DWriteCreateFactory(
|
||||
DWRITE_FACTORY_TYPE_SHARED,
|
||||
__uuidof<IDWriteFactory3>(),
|
||||
(IUnknown**)pFactory);
|
||||
|
||||
ThrowIfFailed(result);
|
||||
}
|
||||
}
|
||||
|
||||
private IDWriteFontSet* GetSystemFontSet(IDWriteFactory3* factory)
|
||||
{
|
||||
IDWriteFactory6* factory6;
|
||||
IDWriteFontSet* fontSet;
|
||||
var result = factory->QueryInterface(__uuidof<IDWriteFactory6>(), (void**)&factory6);
|
||||
if (result.SUCCEEDED)
|
||||
{
|
||||
Sawmill.Verbose("IDWriteFactory6 available, using newer GetSystemFontSet");
|
||||
|
||||
result = factory6->GetSystemFontSet(
|
||||
_cfg.GetCVar(CVars.FontWindowsDownloadable),
|
||||
(IDWriteFontSet1**)(&fontSet));
|
||||
|
||||
factory6->Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
Sawmill.Verbose("IDWriteFactory6 not available");
|
||||
|
||||
result = factory->GetSystemFontSet(&fontSet);
|
||||
}
|
||||
|
||||
ThrowIfFailed(result, "GetSystemFontSet");
|
||||
return fontSet;
|
||||
}
|
||||
|
||||
protected override IFontFaceHandle LoadFontFace(BaseHandle handle)
|
||||
{
|
||||
var fontFace = ((Handle)handle).FontFace;
|
||||
IDWriteFontFile* file = null;
|
||||
IDWriteFontFileLoader* loader = null;
|
||||
|
||||
try
|
||||
{
|
||||
var result = fontFace->GetFontFile(&file);
|
||||
ThrowIfFailed(result, "IDWriteFontFaceReference::GetFontFile");
|
||||
result = file->GetLoader(&loader);
|
||||
ThrowIfFailed(result, "IDWriteFontFile::GetLoader");
|
||||
|
||||
void* referenceKey;
|
||||
uint referenceKeyLength;
|
||||
result = file->GetReferenceKey(&referenceKey, &referenceKeyLength);
|
||||
ThrowIfFailed(result, "IDWriteFontFile::GetReferenceKey");
|
||||
|
||||
IDWriteLocalFontFileLoader* localLoader;
|
||||
result = loader->QueryInterface(__uuidof<IDWriteLocalFontFileLoader>(), (void**)&localLoader);
|
||||
if (result.SUCCEEDED)
|
||||
{
|
||||
Sawmill.Verbose("Loading font face via memory mapped file...");
|
||||
|
||||
// We can get the local file path on disk. This means we can directly load it via mmap.
|
||||
uint filePathLength;
|
||||
ThrowIfFailed(
|
||||
localLoader->GetFilePathLengthFromKey(referenceKey, referenceKeyLength, &filePathLength),
|
||||
"IDWriteLocalFontFileLoader::GetFilePathLengthFromKey");
|
||||
var filePath = new char[filePathLength + 1];
|
||||
fixed (char* pFilePath = filePath)
|
||||
{
|
||||
ThrowIfFailed(
|
||||
localLoader->GetFilePathFromKey(
|
||||
referenceKey,
|
||||
referenceKeyLength,
|
||||
pFilePath,
|
||||
(uint)filePath.Length),
|
||||
"IDWriteLocalFontFileLoader::GetFilePathFromKey");
|
||||
}
|
||||
|
||||
var path = new string(filePath, 0, (int)filePathLength);
|
||||
|
||||
localLoader->Release();
|
||||
|
||||
return FontManager.Load(new MemoryMappedFontMemoryHandle(path));
|
||||
}
|
||||
else
|
||||
{
|
||||
Sawmill.Verbose("Loading font face via stream...");
|
||||
|
||||
// DirectWrite doesn't give us anything to go with for this file, read it into regular memory.
|
||||
// If the font file has multiple faces, which is possible, then this approach will duplicate memory.
|
||||
// That sucks, but I'm really not sure whether there's any way around this short of
|
||||
// comparing the memory contents by hashing to check equality.
|
||||
// As I'm pretty sure we can't like reference equality check the font objects somehow.
|
||||
IDWriteFontFileStream* stream;
|
||||
result = loader->CreateStreamFromKey(referenceKey, referenceKeyLength, &stream);
|
||||
ThrowIfFailed(result, "IDWriteFontFileLoader::CreateStreamFromKey");
|
||||
|
||||
using var streamObject = new DirectWriteStream(stream);
|
||||
return FontManager.Load(streamObject, (int)fontFace->GetFontFaceIndex());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (file != null)
|
||||
file->Release();
|
||||
if (loader != null)
|
||||
loader->Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetStrings(
|
||||
IDWriteFontSet* set,
|
||||
uint listIndex,
|
||||
DWRITE_FONT_PROPERTY_ID property,
|
||||
[NotNullWhen(true)] out DWriteLocalizedString[]? strings)
|
||||
{
|
||||
BOOL exists;
|
||||
IDWriteLocalizedStrings* dWriteStrings = null;
|
||||
var result = set->GetPropertyValues(
|
||||
listIndex,
|
||||
property,
|
||||
&exists,
|
||||
&dWriteStrings);
|
||||
ThrowIfFailed(result, "IDWriteFontSet::GetPropertyValues");
|
||||
|
||||
if (!exists)
|
||||
{
|
||||
strings = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
strings = GetStrings(dWriteStrings);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
dWriteStrings->Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetStringsSet(
|
||||
IDWriteFontSet* set,
|
||||
uint listIndex,
|
||||
DWRITE_FONT_PROPERTY_ID property,
|
||||
out LocalizedStringSet strings)
|
||||
{
|
||||
if (!TryGetStrings(set, listIndex, property, out var stringsArray))
|
||||
{
|
||||
strings = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
strings = StringsToSet(stringsArray);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static DWriteLocalizedString[] GetStrings(IDWriteLocalizedStrings* localizedStrings)
|
||||
{
|
||||
IDWriteStringList* list;
|
||||
ThrowIfFailed(localizedStrings->QueryInterface(__uuidof<IDWriteStringList>(), (void**)&list));
|
||||
|
||||
try
|
||||
{
|
||||
return GetStrings(list);
|
||||
}
|
||||
finally
|
||||
{
|
||||
list->Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static DWriteLocalizedString[] GetStrings(IDWriteStringList* stringList)
|
||||
{
|
||||
var array = new DWriteLocalizedString[stringList->GetCount()];
|
||||
|
||||
var stringPool = ArrayPool<char>.Shared.Rent(256);
|
||||
|
||||
for (var i = 0; i < array.Length; i++)
|
||||
{
|
||||
uint length;
|
||||
|
||||
ThrowIfFailed(stringList->GetStringLength((uint)i, &length), "IDWriteStringList::GetStringLength");
|
||||
ExpandIfNecessary(ref stringPool, length + 1);
|
||||
fixed (char* pArr = stringPool)
|
||||
{
|
||||
ThrowIfFailed(
|
||||
stringList->GetString((uint)i, pArr, (uint)stringPool.Length),
|
||||
"IDWriteStringList::GetString");
|
||||
}
|
||||
|
||||
var value = new string(stringPool, 0, (int)length);
|
||||
|
||||
ThrowIfFailed(stringList->GetLocaleNameLength((uint)i, &length), "IDWriteStringList::GetLocaleNameLength");
|
||||
ExpandIfNecessary(ref stringPool, length + 1);
|
||||
fixed (char* pArr = stringPool)
|
||||
{
|
||||
ThrowIfFailed(
|
||||
stringList->GetLocaleName((uint)i, pArr, (uint)stringPool.Length),
|
||||
"IDWriteStringList::GetLocaleName");
|
||||
}
|
||||
|
||||
var localeName = new string(stringPool, 0, (int)length);
|
||||
|
||||
array[i] = new DWriteLocalizedString(value, localeName);
|
||||
}
|
||||
|
||||
ArrayPool<char>.Shared.Return(stringPool);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
private static void ExpandIfNecessary(ref char[] array, uint requiredLength)
|
||||
{
|
||||
if (requiredLength < array.Length)
|
||||
return;
|
||||
|
||||
ArrayPool<char>.Shared.Return(array);
|
||||
array = ArrayPool<char>.Shared.Rent(checked((int)requiredLength));
|
||||
}
|
||||
|
||||
private static LocalizedStringSet StringsToSet(DWriteLocalizedString[] strings)
|
||||
{
|
||||
var dict = new Dictionary<string, string>();
|
||||
|
||||
foreach (var (value, localeName) in strings)
|
||||
{
|
||||
dict[localeName] = value;
|
||||
}
|
||||
|
||||
return new LocalizedStringSet { Primary = strings[0].LocaleName, Values = dict };
|
||||
}
|
||||
|
||||
private sealed class Handle(SystemFontManagerDirectWrite parent, IDWriteFontFaceReference* fontFace) : BaseHandle(parent)
|
||||
{
|
||||
public readonly IDWriteFontFaceReference* FontFace = fontFace;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A simple implementation of a .NET Stream over a IDWriteFontFileStream.
|
||||
/// </summary>
|
||||
private sealed class DirectWriteStream : Stream
|
||||
{
|
||||
private readonly IDWriteFontFileStream* _stream;
|
||||
private readonly ulong _size;
|
||||
|
||||
private ulong _position;
|
||||
private bool _disposed;
|
||||
|
||||
public DirectWriteStream(IDWriteFontFileStream* stream)
|
||||
{
|
||||
_stream = stream;
|
||||
|
||||
fixed (ulong* pSize = &_size)
|
||||
{
|
||||
var result = _stream->GetFileSize(pSize);
|
||||
ThrowIfFailed(result, "IDWriteFontFileStream::GetFileSize");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return Read(buffer.AsSpan(offset, count));
|
||||
}
|
||||
|
||||
public override int Read(Span<byte> buffer)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(DirectWriteStream));
|
||||
|
||||
var readLength = (uint)buffer.Length;
|
||||
if (readLength + _position > _size)
|
||||
readLength = (uint)(_size - _position);
|
||||
|
||||
void* fragmentStart;
|
||||
void* fragmentContext;
|
||||
|
||||
var result = _stream->ReadFileFragment(&fragmentStart, _position, readLength, &fragmentContext);
|
||||
ThrowIfFailed(result);
|
||||
|
||||
var data = new ReadOnlySpan<byte>(fragmentStart, (int)readLength);
|
||||
data.CopyTo(buffer);
|
||||
|
||||
_stream->ReleaseFileFragment(fragmentContext);
|
||||
|
||||
_position += readLength;
|
||||
return (int)readLength;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
Position = offset;
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
Position += offset;
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
Position = Length + offset;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(origin), origin, null);
|
||||
}
|
||||
|
||||
return Position;
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanSeek => true;
|
||||
public override bool CanWrite => false;
|
||||
public override long Length => (long)_size;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => (long)_position;
|
||||
set
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(value);
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan((ulong)value, _size);
|
||||
|
||||
_position = (ulong)value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_stream->Release();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private record struct DWriteLocalizedString(string Value, string LocaleName);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Robust.Client.Graphics.FontManagement;
|
||||
|
||||
/// <summary>
|
||||
/// A fallback implementation of <see cref="ISystemFontManager"/> that just loads no fonts.
|
||||
/// </summary>
|
||||
internal sealed class SystemFontManagerFallback : ISystemFontManagerInternal
|
||||
{
|
||||
public void Initialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public bool IsSupported => false;
|
||||
public IEnumerable<ISystemFontFace> SystemFontFaces => [];
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
#if FREEDESKTOP
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Log;
|
||||
using SpaceWizards.Fontconfig.Interop;
|
||||
|
||||
namespace Robust.Client.Graphics.FontManagement;
|
||||
|
||||
internal sealed unsafe class SystemFontManagerFontconfig : SystemFontManagerBase, ISystemFontManagerInternal
|
||||
{
|
||||
private static readonly (int Fc, FontWidth Width)[] WidthTable = [
|
||||
(Fontconfig.FC_WIDTH_ULTRACONDENSED, FontWidth.UltraCondensed),
|
||||
(Fontconfig.FC_WIDTH_EXTRACONDENSED, FontWidth.ExtraCondensed),
|
||||
(Fontconfig.FC_WIDTH_CONDENSED, FontWidth.Condensed),
|
||||
(Fontconfig.FC_WIDTH_SEMICONDENSED, FontWidth.SemiCondensed),
|
||||
(Fontconfig.FC_WIDTH_NORMAL, FontWidth.Normal),
|
||||
(Fontconfig.FC_WIDTH_SEMIEXPANDED, FontWidth.SemiExpanded),
|
||||
(Fontconfig.FC_WIDTH_EXPANDED, FontWidth.Expanded),
|
||||
(Fontconfig.FC_WIDTH_EXTRAEXPANDED, FontWidth.ExtraExpanded),
|
||||
(Fontconfig.FC_WIDTH_ULTRAEXPANDED, FontWidth.UltraExpanded),
|
||||
];
|
||||
|
||||
public bool IsSupported => true;
|
||||
|
||||
public SystemFontManagerFontconfig(ILogManager logManager, IFontManagerInternal fontManager)
|
||||
: base(logManager, fontManager)
|
||||
{
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
Sawmill.Verbose("Initializing Fontconfig...");
|
||||
|
||||
var result = Fontconfig.FcInit();
|
||||
if (result == Fontconfig.FcFalse)
|
||||
throw new InvalidOperationException("Failed to initialize fontconfig!");
|
||||
|
||||
Sawmill.Verbose("Listing fonts...");
|
||||
|
||||
var os = Fontconfig.FcObjectSetCreate();
|
||||
AddToObjectSet(os, Fontconfig.FC_FAMILY);
|
||||
AddToObjectSet(os, Fontconfig.FC_FAMILYLANG);
|
||||
AddToObjectSet(os, Fontconfig.FC_STYLE);
|
||||
AddToObjectSet(os, Fontconfig.FC_STYLELANG);
|
||||
AddToObjectSet(os, Fontconfig.FC_FULLNAME);
|
||||
AddToObjectSet(os, Fontconfig.FC_FULLNAMELANG);
|
||||
AddToObjectSet(os, Fontconfig.FC_POSTSCRIPT_NAME);
|
||||
|
||||
AddToObjectSet(os, Fontconfig.FC_SLANT);
|
||||
AddToObjectSet(os, Fontconfig.FC_WEIGHT);
|
||||
AddToObjectSet(os, Fontconfig.FC_WIDTH);
|
||||
|
||||
AddToObjectSet(os, Fontconfig.FC_FILE);
|
||||
AddToObjectSet(os, Fontconfig.FC_INDEX);
|
||||
|
||||
var allPattern = Fontconfig.FcPatternCreate();
|
||||
var set = Fontconfig.FcFontList(null, allPattern, os);
|
||||
|
||||
for (var i = 0; i < set->nfont; i++)
|
||||
{
|
||||
var pattern = set->fonts[i];
|
||||
|
||||
try
|
||||
{
|
||||
LoadPattern(pattern);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Sawmill.Error($"Error while loading pattern: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
Fontconfig.FcPatternDestroy(allPattern);
|
||||
Fontconfig.FcObjectSetDestroy(os);
|
||||
Fontconfig.FcFontSetDestroy(set);
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
private void LoadPattern(FcPattern* pattern)
|
||||
{
|
||||
var path = PatternGetStrings(pattern, Fontconfig.FC_FILE)![0];
|
||||
var idx = PatternGetInts(pattern, Fontconfig.FC_INDEX)![0];
|
||||
|
||||
var family = PatternToLocalized(pattern, Fontconfig.FC_FAMILY, Fontconfig.FC_FAMILYLANG);
|
||||
var style = PatternToLocalized(pattern, Fontconfig.FC_STYLE, Fontconfig.FC_STYLELANG);
|
||||
var fullName = PatternToLocalized(pattern, Fontconfig.FC_FULLNAME, Fontconfig.FC_FULLNAMELANG);
|
||||
var psName = PatternGetStrings(pattern, Fontconfig.FC_POSTSCRIPT_NAME);
|
||||
if (psName == null)
|
||||
return;
|
||||
|
||||
var slant = PatternGetInts(pattern, Fontconfig.FC_SLANT) ?? [Fontconfig.FC_SLANT_ROMAN];
|
||||
var weight = PatternGetInts(pattern, Fontconfig.FC_WEIGHT) ?? [Fontconfig.FC_WEIGHT_REGULAR];
|
||||
var width = PatternGetInts(pattern, Fontconfig.FC_WIDTH) ?? [Fontconfig.FC_WIDTH_NORMAL];
|
||||
|
||||
Fonts.Add(new Handle(this)
|
||||
{
|
||||
FilePath = path,
|
||||
FileIndex = idx,
|
||||
FaceNames = style ?? LocalizedStringSet.Empty,
|
||||
FullNames = fullName ?? LocalizedStringSet.Empty,
|
||||
FamilyNames = family ?? LocalizedStringSet.Empty,
|
||||
PostscriptName = psName[0],
|
||||
Slant = SlantFromFontconfig(slant[0]),
|
||||
Weight = WeightFromFontconfig(weight[0]),
|
||||
Width = WidthFromFontconfig(width[0])
|
||||
});
|
||||
}
|
||||
|
||||
private static FontWeight WeightFromFontconfig(int value)
|
||||
{
|
||||
return (FontWeight)Fontconfig.FcWeightToOpenType(value);
|
||||
}
|
||||
|
||||
private static FontSlant SlantFromFontconfig(int value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
Fontconfig.FC_SLANT_ITALIC => FontSlant.Italic,
|
||||
Fontconfig.FC_SLANT_OBLIQUE => FontSlant.Italic,
|
||||
_ => FontSlant.Normal,
|
||||
};
|
||||
}
|
||||
|
||||
private static FontWidth WidthFromFontconfig(int value)
|
||||
{
|
||||
return WidthTable.MinBy(t => Math.Abs(t.Fc - value)).Width;
|
||||
}
|
||||
|
||||
private static unsafe void AddToObjectSet(FcObjectSet* os, ReadOnlySpan<byte> value)
|
||||
{
|
||||
var result = Fontconfig.FcObjectSetAdd(os, (sbyte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(value)));
|
||||
if (result == Fontconfig.FcFalse)
|
||||
throw new InvalidOperationException("Failed to add to object set!");
|
||||
}
|
||||
|
||||
private static unsafe string[]? PatternGetStrings(FcPattern* pattern, ReadOnlySpan<byte> @object)
|
||||
{
|
||||
return PatternGetValues(pattern, @object, static (FcPattern* p, sbyte* o, int i, out string value) =>
|
||||
{
|
||||
byte* str = null;
|
||||
var res = Fontconfig.FcPatternGetString(p, o, i, &str);
|
||||
value = Marshal.PtrToStringUTF8((nint)str)!;
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
private static unsafe int[]? PatternGetInts(FcPattern* pattern, ReadOnlySpan<byte> @object)
|
||||
{
|
||||
return PatternGetValues(pattern, @object, static (FcPattern* p, sbyte* o, int i, out int value) =>
|
||||
{
|
||||
FcResult res;
|
||||
fixed (int* pValue = &value)
|
||||
{
|
||||
res = Fontconfig.FcPatternGetInteger(p, o, i, pValue);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
private delegate FcResult GetValue<T>(FcPattern* p, sbyte* o, int i, out T value);
|
||||
private static unsafe T[]? PatternGetValues<T>(FcPattern* pattern, ReadOnlySpan<byte> @object, GetValue<T> getValue)
|
||||
{
|
||||
var list = new List<T>();
|
||||
|
||||
var i = 0;
|
||||
while (true)
|
||||
{
|
||||
var result = getValue(pattern, (sbyte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(@object)), i++, out var value);
|
||||
if (result == FcResult.FcResultMatch)
|
||||
{
|
||||
list.Add(value);
|
||||
}
|
||||
else if (result == FcResult.FcResultNoMatch)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if (result == FcResult.FcResultNoId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"FcPatternGetString gave error: {result}");
|
||||
}
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
private static LocalizedStringSet? PatternToLocalized(FcPattern* pattern, ReadOnlySpan<byte> @object, ReadOnlySpan<byte> objectLang)
|
||||
{
|
||||
var values = PatternGetStrings(pattern, @object);
|
||||
var languages = PatternGetStrings(pattern, objectLang);
|
||||
|
||||
if (values == null || languages == null || values.Length == 0 || languages.Length != values.Length)
|
||||
return null;
|
||||
|
||||
var dict = new Dictionary<string, string>();
|
||||
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
var val = values[i];
|
||||
var lang = languages[i];
|
||||
|
||||
dict.TryAdd(lang, val);
|
||||
}
|
||||
|
||||
return new LocalizedStringSet
|
||||
{
|
||||
Primary = languages[0],
|
||||
Values = dict
|
||||
};
|
||||
}
|
||||
|
||||
protected override IFontFaceHandle LoadFontFace(BaseHandle handle)
|
||||
{
|
||||
var cast = (Handle)handle;
|
||||
|
||||
return FontManager.Load(new MemoryMappedFontMemoryHandle(cast.FilePath), cast.FileIndex);
|
||||
}
|
||||
|
||||
private sealed class Handle(SystemFontManagerFontconfig parent) : BaseHandle(parent)
|
||||
{
|
||||
public required string FilePath;
|
||||
public required int FileIndex;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,16 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SharpFont;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -20,6 +20,7 @@ namespace Robust.Client.Graphics
|
||||
private const int SheetHeight = 256;
|
||||
|
||||
private readonly IClyde _clyde;
|
||||
private readonly ISawmill _sawmill;
|
||||
|
||||
private uint _baseFontDpi = 96;
|
||||
|
||||
@@ -28,22 +29,56 @@ namespace Robust.Client.Graphics
|
||||
private readonly Dictionary<(FontFaceHandle, int fontSize), FontInstanceHandle> _loadedInstances =
|
||||
new();
|
||||
|
||||
public FontManager(IClyde clyde)
|
||||
public FontManager(IClyde clyde, ILogManager logManager)
|
||||
{
|
||||
_clyde = clyde;
|
||||
_library = new Library();
|
||||
_sawmill = logManager.GetSawmill("font");
|
||||
}
|
||||
|
||||
public IFontFaceHandle Load(Stream stream)
|
||||
public IFontFaceHandle Load(Stream stream, int index = 0)
|
||||
{
|
||||
// Freetype directly operates on the font memory managed by us.
|
||||
// As such, the font data should be pinned in POH.
|
||||
var fontData = stream.CopyToPinnedArray();
|
||||
var face = new Face(_library, fontData, 0);
|
||||
var handle = new FontFaceHandle(face);
|
||||
return Load(new ArrayMemoryHandle(fontData), index);
|
||||
}
|
||||
|
||||
public IFontFaceHandle Load(IFontMemoryHandle memory, int index = 0)
|
||||
{
|
||||
var face = FaceLoad(memory, index);
|
||||
var handle = new FontFaceHandle(face, memory);
|
||||
return handle;
|
||||
}
|
||||
|
||||
public IFontFaceHandle LoadWithPostscriptName(IFontMemoryHandle memory, string postscriptName)
|
||||
{
|
||||
var numFaces = 1;
|
||||
|
||||
for (var i = 0; i < numFaces; i++)
|
||||
{
|
||||
var face = FaceLoad(memory, i);
|
||||
numFaces = face.FaceCount;
|
||||
|
||||
if (face.GetPostscriptName() == postscriptName)
|
||||
return new FontFaceHandle(face, memory);
|
||||
|
||||
face.Dispose();
|
||||
}
|
||||
|
||||
// Fallback, load SOMETHING.
|
||||
_sawmill.Warning($"Failed to load correct font via postscript name! {postscriptName}");
|
||||
return new FontFaceHandle(FaceLoad(memory, 0), memory);
|
||||
}
|
||||
|
||||
private unsafe Face FaceLoad(IFontMemoryHandle memory, int index)
|
||||
{
|
||||
return new Face(_library,
|
||||
(nint)memory.GetData(),
|
||||
checked((int)memory.GetDataSize()),
|
||||
index);
|
||||
}
|
||||
|
||||
void IFontManagerInternal.SetFontDpi(uint fontDpi)
|
||||
{
|
||||
_baseFontDpi = fontDpi;
|
||||
@@ -235,10 +270,13 @@ namespace Robust.Client.Graphics
|
||||
|
||||
private sealed class FontFaceHandle : IFontFaceHandle
|
||||
{
|
||||
// Keep this alive to avoid it being GC'd.
|
||||
private readonly IFontMemoryHandle _memoryHandle;
|
||||
public Face Face { get; }
|
||||
|
||||
public FontFaceHandle(Face face)
|
||||
public FontFaceHandle(Face face, IFontMemoryHandle memoryHandle)
|
||||
{
|
||||
_memoryHandle = memoryHandle;
|
||||
Face = face;
|
||||
}
|
||||
}
|
||||
@@ -377,5 +415,32 @@ namespace Robust.Client.Graphics
|
||||
public CharMetrics Metrics;
|
||||
public AtlasTexture? Texture;
|
||||
}
|
||||
|
||||
private sealed class ArrayMemoryHandle(byte[] array) : IFontMemoryHandle
|
||||
{
|
||||
private GCHandle _gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
|
||||
|
||||
public unsafe byte* GetData()
|
||||
{
|
||||
return (byte*) _gcHandle.AddrOfPinnedObject();
|
||||
}
|
||||
|
||||
public IntPtr GetDataSize()
|
||||
{
|
||||
return array.Length;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gcHandle.Free();
|
||||
_gcHandle = default;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~ArrayMemoryHandle()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Robust.Shared.Graphics;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -10,7 +10,15 @@ namespace Robust.Client.Graphics
|
||||
}
|
||||
internal interface IFontManagerInternal : IFontManager
|
||||
{
|
||||
IFontFaceHandle Load(Stream stream);
|
||||
IFontFaceHandle Load(Stream stream, int index = 0);
|
||||
IFontFaceHandle Load(IFontMemoryHandle memory, int index = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Load a specified font in a font collection.
|
||||
/// </summary>
|
||||
/// <param name="memory">Memory for the entire font collection.</param>
|
||||
/// <param name="postscriptName">The postscript name of the font to load.</param>
|
||||
IFontFaceHandle LoadWithPostscriptName(IFontMemoryHandle memory, string postscriptName);
|
||||
IFontInstanceHandle MakeInstance(IFontFaceHandle handle, int size);
|
||||
void SetFontDpi(uint fontDpi);
|
||||
}
|
||||
@@ -22,8 +30,6 @@ namespace Robust.Client.Graphics
|
||||
|
||||
internal interface IFontInstanceHandle
|
||||
{
|
||||
|
||||
|
||||
Texture? GetCharTexture(Rune codePoint, float scale);
|
||||
Texture? GetCharTexture(char chr, float scale) => GetCharTexture((Rune) chr, scale);
|
||||
CharMetrics? GetCharMetrics(Rune codePoint, float scale);
|
||||
@@ -35,6 +41,12 @@ namespace Robust.Client.Graphics
|
||||
int GetLineHeight(float scale);
|
||||
}
|
||||
|
||||
internal unsafe interface IFontMemoryHandle : IDisposable
|
||||
{
|
||||
byte* GetData();
|
||||
nint GetDataSize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metrics for a single glyph in a font.
|
||||
/// Refer to https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.html for more information.
|
||||
|
||||
127
Robust.Client/Graphics/ISystemFontManager.cs
Normal file
127
Robust.Client/Graphics/ISystemFontManager.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Robust.Client.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to fonts installed on the user's operating system.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Different operating systems ship different fonts, so you should generally not rely on any one
|
||||
/// specific font being available. This system is primarily provided for allowing user preference.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="ISystemFontFace"/>
|
||||
public interface ISystemFontManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether access to system fonts is currently supported on this platform.
|
||||
/// </summary>
|
||||
bool IsSupported { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of font face available from the operating system.
|
||||
/// </summary>
|
||||
IEnumerable<ISystemFontFace> SystemFontFaces { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single font face, provided by the user's operating system.
|
||||
/// </summary>
|
||||
/// <seealso cref="ISystemFontManager"/>
|
||||
public interface ISystemFontFace
|
||||
{
|
||||
/// <summary>
|
||||
/// The PostScript name of the font face.
|
||||
/// This is generally the closest to an unambiguous unique identifier as you're going to get.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// For example, "Arial-ItalicMT"
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
string PostscriptName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The full name of the font face, localized to the current locale.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// For example, "Arial Cursiva"
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="GetLocalizedFullName"/>
|
||||
string FullName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The family name of the font face, localized to the current locale.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// For example, "Arial"
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="GetLocalizedFamilyName"/>
|
||||
string FamilyName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The face name (or "style name") of the font face, localized to the current locale.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// For example, "Cursiva"
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="GetLocalizedFaceName"/>
|
||||
string FaceName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="FullName"/>, localized to a specific locale.
|
||||
/// </summary>
|
||||
/// <param name="culture">The locale to fetch the localized string for.</param>
|
||||
string GetLocalizedFullName(CultureInfo culture);
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="FamilyName"/>, localized to a specific locale.
|
||||
/// </summary>
|
||||
/// <param name="culture">The locale to fetch the localized string for.</param>
|
||||
string GetLocalizedFamilyName(CultureInfo culture);
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="FaceName"/>, localized to a specific locale.
|
||||
/// </summary>
|
||||
/// <param name="culture">The locale to fetch the localized string for.</param>
|
||||
string GetLocalizedFaceName(CultureInfo culture);
|
||||
|
||||
/// <summary>
|
||||
/// The weight of the font face.
|
||||
/// </summary>
|
||||
FontWeight Weight { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The slant of the font face.
|
||||
/// </summary>
|
||||
FontSlant Slant { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The width of the font face.
|
||||
/// </summary>
|
||||
FontWidth Width { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Load the font face so that it can be used in-engine.
|
||||
/// </summary>
|
||||
/// <param name="size">The size to load the font at.</param>
|
||||
/// <returns>A font object that can be used to render text.</returns>
|
||||
Font Load(int size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Engine-internal API for <see cref="ISystemFontManager"/>.
|
||||
/// </summary>
|
||||
internal interface ISystemFontManagerInternal : ISystemFontManager
|
||||
{
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
}
|
||||
306
Robust.Client/Graphics/LoadingScreenManager.cs
Normal file
306
Robust.Client/Graphics/LoadingScreenManager.cs
Normal file
@@ -0,0 +1,306 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Stopwatch = Robust.Shared.Timing.Stopwatch;
|
||||
|
||||
namespace Robust.Client.Graphics;
|
||||
|
||||
internal interface ILoadingScreenManager
|
||||
{
|
||||
void BeginLoadingSection(string sectionName);
|
||||
|
||||
/// <summary>
|
||||
/// Start a loading bar "section" for the given method.
|
||||
/// Must be ended with EndSection.
|
||||
/// </summary>
|
||||
void BeginLoadingSection(object method);
|
||||
|
||||
void EndLoadingSection();
|
||||
|
||||
/// <summary>
|
||||
/// Will run the giving function and add a custom "section" for it on the loading screen.
|
||||
/// </summary>
|
||||
void LoadingStep(Action action, object method);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manager that creates and displays a basic splash screen and loading bar.
|
||||
/// </summary>
|
||||
internal sealed class LoadingScreenManager : ILoadingScreenManager
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private readonly Stopwatch _sw = new();
|
||||
|
||||
#region UI constants
|
||||
|
||||
private const int LoadingBarWidth = 250;
|
||||
private const int LoadingBarHeight = 20;
|
||||
private const int LoadingBarOutlineOffset = 5;
|
||||
private static readonly Vector2i LogoLoadingBarOffset = (0, 20);
|
||||
private static readonly Vector2i LoadTimesIndent = (20, 0);
|
||||
|
||||
private const int NumLongestLoadTimes = 5;
|
||||
|
||||
private static readonly Color LoadingBarColor = Color.White;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cvars
|
||||
|
||||
private string _splashLogo = "";
|
||||
private bool _showLoadingBar;
|
||||
private bool _showDebug;
|
||||
|
||||
#endregion
|
||||
|
||||
private const string FontLocation = "/EngineFonts/NotoSans/NotoSans-Regular.ttf";
|
||||
private const int FontSize = 11;
|
||||
private VectorFont? _font;
|
||||
|
||||
// Number of loading sections for the loading bar. This has to be manually set!
|
||||
private int _numberOfLoadingSections;
|
||||
|
||||
// The name of the section and how much time it took to load
|
||||
internal readonly List<(string Name, TimeSpan LoadTime)> Times = [];
|
||||
|
||||
private int _currentSection;
|
||||
private string? _currentSectionName;
|
||||
|
||||
private bool _currentlyInSection;
|
||||
private bool _finished;
|
||||
|
||||
public void Initialize(int sections)
|
||||
{
|
||||
if (_finished)
|
||||
return;
|
||||
|
||||
_clyde.VsyncEnabled = false;
|
||||
|
||||
_numberOfLoadingSections = sections;
|
||||
|
||||
_sawmill = _logManager.GetSawmill("loading");
|
||||
|
||||
_splashLogo = _cfg.GetCVar(CVars.DisplaySplashLogo);
|
||||
_showLoadingBar = _cfg.GetCVar(CVars.LoadingShowBar);
|
||||
_showDebug = _cfg.GetCVar(CVars.LoadingShowDebug);
|
||||
|
||||
if (_resourceCache.TryGetResource<FontResource>(FontLocation, out var fontResource))
|
||||
_font = new VectorFont(fontResource, FontSize);
|
||||
else
|
||||
_sawmill.Error($"Could not load font: {FontLocation}");
|
||||
}
|
||||
|
||||
public void BeginLoadingSection(string sectionName) => BeginLoadingSection(sectionName, false);
|
||||
public void BeginLoadingSection(string sectionName, bool dontRender)
|
||||
{
|
||||
if (_finished)
|
||||
return;
|
||||
|
||||
if (_currentlyInSection)
|
||||
throw new InvalidOperationException("You cannot begin more than one section at a time!");
|
||||
|
||||
_currentlyInSection = true;
|
||||
|
||||
_currentSectionName = sectionName;
|
||||
|
||||
if (!dontRender)
|
||||
{
|
||||
// This ensures that if the screen was resized or something the new size is properly updated to clyde.
|
||||
_clyde.ProcessInput(new FrameEventArgs((float)_sw.Elapsed.TotalSeconds));
|
||||
_sw.Restart();
|
||||
_clyde.Render();
|
||||
}
|
||||
else
|
||||
{
|
||||
_sw.Restart();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start a loading bar "section" for the given method.
|
||||
/// Must be ended with EndSection.
|
||||
/// </summary>
|
||||
public void BeginLoadingSection(object method)
|
||||
{
|
||||
if (_finished)
|
||||
return;
|
||||
|
||||
BeginLoadingSection(method.GetType().Name);
|
||||
}
|
||||
|
||||
public void EndLoadingSection()
|
||||
{
|
||||
if (_finished)
|
||||
return;
|
||||
|
||||
var time = _sw.Elapsed;
|
||||
if (_currentSectionName != null)
|
||||
Times.Add((_currentSectionName, time));
|
||||
_currentSection++;
|
||||
_currentlyInSection = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will run the giving function and add a custom "section" for it on the loading screen.
|
||||
/// </summary>
|
||||
public void LoadingStep(Action action, object method)
|
||||
{
|
||||
if (_finished)
|
||||
return;
|
||||
|
||||
BeginLoadingSection(method as string ?? method.GetType().Name);
|
||||
action();
|
||||
EndLoadingSection();
|
||||
}
|
||||
|
||||
public void Finish()
|
||||
{
|
||||
if (_finished)
|
||||
return;
|
||||
|
||||
if (_currentSection != _numberOfLoadingSections)
|
||||
_sawmill.Error($"The number of seen loading sections isn't equal to the total number of loading sections! Seen: {_currentSection}, Total: {_numberOfLoadingSections}");
|
||||
|
||||
_finished = true;
|
||||
}
|
||||
|
||||
#region Drawing functions
|
||||
|
||||
/// <summary>
|
||||
/// Draw out the splash and loading screen.
|
||||
/// </summary>
|
||||
public void DrawLoadingScreen(IRenderHandle handle, Vector2i screenSize)
|
||||
{
|
||||
if (_finished)
|
||||
return;
|
||||
|
||||
var scale = UserInterfaceManager.CalculateUIScale(_clyde.MainWindow.ContentScale.X, _cfg);
|
||||
|
||||
// Start at the center!
|
||||
var location = screenSize / 2;
|
||||
|
||||
DrawSplash(handle, ref location, scale);
|
||||
|
||||
DrawLoadingBar(handle, ref location, scale);
|
||||
|
||||
if (_showDebug)
|
||||
{
|
||||
DrawCurrentLoading(handle, ref location, scale);
|
||||
|
||||
DrawTopTimes(handle, ref location, scale);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSplash(IRenderHandle handle, ref Vector2i startLocation, float scale)
|
||||
{
|
||||
if (!_resourceCache.TryGetResource<TextureResource>(_splashLogo, out var textureResource))
|
||||
return;
|
||||
|
||||
var drawSize = textureResource.Texture.Size * scale;
|
||||
|
||||
handle.DrawingHandleScreen.DrawTextureRect(textureResource.Texture, UIBox2.FromDimensions(startLocation - drawSize / 2, drawSize));
|
||||
startLocation += Vector2i.Up * (int) drawSize.Y / 2;
|
||||
}
|
||||
|
||||
private void DrawLoadingBar(IRenderHandle handle, ref Vector2i location, float scale)
|
||||
{
|
||||
var barWidth = (int)(LoadingBarWidth * scale);
|
||||
var barHeight = (int)(LoadingBarHeight * scale);
|
||||
var outlineOffset = (int)(LoadingBarOutlineOffset * scale);
|
||||
|
||||
// Always do the offsets, it looks a lot better!
|
||||
location.X -= barWidth / 2;
|
||||
location += (Vector2i) (LogoLoadingBarOffset * scale);
|
||||
|
||||
if (!_showLoadingBar)
|
||||
return;
|
||||
|
||||
var sectionWidth = barWidth / _numberOfLoadingSections;
|
||||
|
||||
var barTopLeft = location;
|
||||
var barBottomRight = new Vector2i(_currentSection * sectionWidth % barWidth, barHeight);
|
||||
var barBottomRightMax = new Vector2i(barWidth, barHeight);
|
||||
|
||||
var outlinePosition = barTopLeft + Vector2i.DownLeft * outlineOffset;
|
||||
var outlineSize = barBottomRightMax + Vector2i.UpRight * 2 * outlineOffset;
|
||||
|
||||
// Outline
|
||||
handle.DrawingHandleScreen.DrawRect(UIBox2.FromDimensions(outlinePosition, outlineSize), LoadingBarColor, false);
|
||||
|
||||
// Progress bar
|
||||
handle.DrawingHandleScreen.DrawRect(UIBox2.FromDimensions(barTopLeft, barBottomRight), LoadingBarColor);
|
||||
|
||||
location += Vector2i.Up * outlineSize;
|
||||
}
|
||||
|
||||
// Draw the currently loading section to the screen.
|
||||
private void DrawCurrentLoading(IRenderHandle handle, ref Vector2i location, float scale)
|
||||
{
|
||||
if (_font == null || _currentSectionName == null)
|
||||
return;
|
||||
|
||||
handle.DrawingHandleScreen.DrawString(_font, location, _currentSectionName, scale, Color.White);
|
||||
location += Vector2i.Up * _font.GetLineHeight(scale);
|
||||
}
|
||||
|
||||
// Draw the slowest loading times to the screen.
|
||||
private void DrawTopTimes(IRenderHandle handle, ref Vector2i location, float scale)
|
||||
{
|
||||
if (_font == null)
|
||||
return;
|
||||
|
||||
location += (Vector2i)(LoadTimesIndent * scale);
|
||||
|
||||
var offset = 0;
|
||||
var x = 0;
|
||||
Times.Sort((a, b) => b.LoadTime.CompareTo(a.LoadTime));
|
||||
|
||||
foreach (var (name, time) in Times)
|
||||
{
|
||||
if (x >= NumLongestLoadTimes)
|
||||
break;
|
||||
|
||||
var entry = $"{time.TotalSeconds:F2} - {name}";
|
||||
handle.DrawingHandleScreen.DrawString(_font, location + new Vector2i(0, offset), entry, scale, Color.White);
|
||||
offset += _font.GetLineHeight(scale);
|
||||
x++;
|
||||
}
|
||||
|
||||
location += Vector2i.Up * offset;
|
||||
}
|
||||
|
||||
#endregion // Drawing functions
|
||||
}
|
||||
|
||||
internal sealed class ShowTopLoadingTimesCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly LoadingScreenManager _mgr = default!;
|
||||
|
||||
public string Command => "loading_top";
|
||||
public string Description => "";
|
||||
public string Help => "";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var sorted = _mgr.Times.Where(x => x.LoadTime > TimeSpan.FromSeconds(0.01)).OrderByDescending(x => x.LoadTime);
|
||||
foreach (var (name, time) in sorted)
|
||||
{
|
||||
shell.WriteLine($"{time.TotalSeconds:F2} - {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
89
Robust.Client/Graphics/SystemFontManager.cs
Normal file
89
Robust.Client/Graphics/SystemFontManager.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics.FontManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="ISystemFontManager"/> that proxies to platform-specific implementations,
|
||||
/// and adds additional logging.
|
||||
/// </summary>
|
||||
internal sealed class SystemFontManager : ISystemFontManagerInternal, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IFontManagerInternal _fontManager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private ISystemFontManagerInternal _implementation = default!;
|
||||
|
||||
public bool IsSupported => _implementation.IsSupported;
|
||||
public IEnumerable<ISystemFontFace> SystemFontFaces => _implementation.SystemFontFaces;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_implementation = GetImplementation();
|
||||
_sawmill.Verbose($"Using {_implementation.GetType()}");
|
||||
|
||||
_sawmill.Debug("Initializing system font manager implementation");
|
||||
try
|
||||
{
|
||||
var sw = RStopwatch.StartNew();
|
||||
_implementation.Initialize();
|
||||
_sawmill.Debug($"Done initializing system font manager in {sw.Elapsed}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// This is a non-critical engine system that has to parse significant amounts of external data.
|
||||
// Best to fail gracefully to avoid full startup failures.
|
||||
|
||||
_sawmill.Error($"Error while initializing system font manager, resorting to fallback: {e}");
|
||||
_implementation = new SystemFontManagerFallback();
|
||||
}
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
_sawmill.Verbose("Shutting down system font manager");
|
||||
|
||||
try
|
||||
{
|
||||
_implementation.Shutdown();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Exception shutting down system font manager: {e}");
|
||||
return;
|
||||
}
|
||||
|
||||
_sawmill.Verbose("Successfully shut down system font manager");
|
||||
}
|
||||
|
||||
private ISystemFontManagerInternal GetImplementation()
|
||||
{
|
||||
if (!_cfg.GetCVar(CVars.FontSystem))
|
||||
return new SystemFontManagerFallback();
|
||||
|
||||
#if WINDOWS
|
||||
return new SystemFontManagerDirectWrite(_logManager, _cfg, _fontManager);
|
||||
#elif FREEDESKTOP
|
||||
return new SystemFontManagerFontconfig(_logManager, _fontManager);
|
||||
#elif MACOS
|
||||
return new SystemFontManagerCoreText(_logManager, _fontManager);
|
||||
#else
|
||||
return new SystemFontManagerFallback();
|
||||
#endif
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_sawmill = _logManager.GetSawmill("font.system");
|
||||
// _sawmill.Level = LogLevel.Verbose;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
24
Robust.Client/Interop/MacOS/AppKit.cs
Normal file
24
Robust.Client/Interop/MacOS/AppKit.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// ReSharper disable InconsistentNaming
|
||||
#if MACOS
|
||||
|
||||
namespace Robust.Client.Interop.MacOS;
|
||||
|
||||
/// <summary>
|
||||
/// Binding to macOS AppKit.
|
||||
/// </summary>
|
||||
internal static class AppKit
|
||||
{
|
||||
// Values pulled from here:
|
||||
// https://chromium.googlesource.com/chromium/src/+/b5019b491932dfa597acb3a13a9e7780fb6525a9/ui/gfx/platform_font_mac.mm#53
|
||||
public const double NSFontWeightUltraLight = -0.8;
|
||||
public const double NSFontWeightThin = -0.6;
|
||||
public const double NSFontWeightLight = -0.4;
|
||||
public const double NSFontWeightRegular = 0;
|
||||
public const double NSFontWeightMedium = 0.23;
|
||||
public const double NSFontWeightSemiBold = 0.30;
|
||||
public const double NSFontWeightBold = 0.40;
|
||||
public const double NSFontWeightHeavy = 0.56;
|
||||
public const double NSFontWeightBlack = 0.62;
|
||||
}
|
||||
|
||||
#endif
|
||||
97
Robust.Client/Interop/MacOS/CoreFoundation.cs
Normal file
97
Robust.Client/Interop/MacOS/CoreFoundation.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
#if MACOS
|
||||
using System.Runtime.InteropServices;
|
||||
using CFIndex = System.Runtime.InteropServices.CLong;
|
||||
using Boolean = byte;
|
||||
|
||||
namespace Robust.Client.Interop.MacOS;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
/// <summary>
|
||||
/// Binding to macOS Core Foundation.
|
||||
/// </summary>
|
||||
internal static unsafe class CoreFoundation
|
||||
{
|
||||
private const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
|
||||
|
||||
public const int kCFNumberFloat32Type = 5;
|
||||
|
||||
public static string CFStringToManaged(__CFString* str)
|
||||
{
|
||||
var length = CFStringGetLength(str);
|
||||
|
||||
return string.Create(
|
||||
checked((int)length.Value),
|
||||
(nint)str,
|
||||
static (span, arg) =>
|
||||
{
|
||||
fixed (char* pBuffer = span)
|
||||
{
|
||||
CFStringGetCharacters((__CFString*)arg,
|
||||
new CFRange
|
||||
{
|
||||
location = new CFIndex(0),
|
||||
length = new CFIndex(span.Length),
|
||||
},
|
||||
pBuffer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[DllImport(CoreFoundationLibrary)]
|
||||
internal static extern void* CFRetain(void* cf);
|
||||
|
||||
[DllImport(CoreFoundationLibrary)]
|
||||
internal static extern void CFRelease(void* cf);
|
||||
|
||||
[DllImport(CoreFoundationLibrary)]
|
||||
internal static extern CFIndex CFArrayGetCount(__CFArray* array);
|
||||
|
||||
[DllImport(CoreFoundationLibrary)]
|
||||
internal static extern void* CFArrayGetValueAtIndex(__CFArray* array, CFIndex index);
|
||||
|
||||
[DllImport(CoreFoundationLibrary)]
|
||||
internal static extern CFIndex CFStringGetLength(__CFString* str);
|
||||
|
||||
[DllImport(CoreFoundationLibrary)]
|
||||
internal static extern void CFStringGetCharacters(__CFString* str, CFRange range, char* buffer);
|
||||
|
||||
[DllImport(CoreFoundationLibrary)]
|
||||
internal static extern Boolean CFURLGetFileSystemRepresentation(
|
||||
__CFURL* url,
|
||||
Boolean resolveAgainstBase,
|
||||
byte* buffer,
|
||||
CFIndex maxBufLen);
|
||||
|
||||
[DllImport(CoreFoundationLibrary)]
|
||||
internal static extern __CFString* CFURLGetString(__CFURL* url);
|
||||
|
||||
[DllImport(CoreFoundationLibrary)]
|
||||
internal static extern CFIndex CFDictionaryGetCount(__CFDictionary* theDict);
|
||||
|
||||
[DllImport(CoreFoundationLibrary)]
|
||||
internal static extern void* CFDictionaryGetValue(__CFDictionary* theDict, void* key);
|
||||
|
||||
[DllImport(CoreFoundationLibrary)]
|
||||
internal static extern void CFDictionaryGetKeysAndValues(__CFDictionary* theDict, void** keys, void** values);
|
||||
|
||||
[DllImport(CoreFoundationLibrary)]
|
||||
internal static extern void CFNumberGetValue(__CFNumber* number, CLong theType, void* valuePtr);
|
||||
}
|
||||
|
||||
internal struct __CFNumber;
|
||||
|
||||
internal struct __CFString;
|
||||
|
||||
internal struct __CFURL;
|
||||
|
||||
internal struct __CFArray;
|
||||
|
||||
internal struct __CFDictionary;
|
||||
|
||||
internal struct CFRange
|
||||
{
|
||||
public CFIndex location;
|
||||
public CFIndex length;
|
||||
}
|
||||
#endif
|
||||
54
Robust.Client/Interop/MacOS/CoreText.cs
Normal file
54
Robust.Client/Interop/MacOS/CoreText.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
#if MACOS
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Robust.Client.Interop.MacOS;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
/// <summary>
|
||||
/// Binding to macOS Core Text.
|
||||
/// </summary>
|
||||
internal static unsafe class CoreText
|
||||
{
|
||||
private const string CoreTextLibrary = "/System/Library/Frameworks/CoreText.framework/CoreText";
|
||||
|
||||
public static readonly __CFString* kCTFontURLAttribute;
|
||||
public static readonly __CFString* kCTFontNameAttribute;
|
||||
public static readonly __CFString* kCTFontDisplayNameAttribute;
|
||||
public static readonly __CFString* kCTFontFamilyNameAttribute;
|
||||
public static readonly __CFString* kCTFontStyleNameAttribute;
|
||||
public static readonly __CFString* kCTFontTraitsAttribute;
|
||||
public static readonly __CFString* kCTFontWeightTrait;
|
||||
public static readonly __CFString* kCTFontWidthTrait;
|
||||
public static readonly __CFString* kCTFontSlantTrait;
|
||||
|
||||
static CoreText()
|
||||
{
|
||||
var lib = NativeLibrary.Load(CoreTextLibrary);
|
||||
kCTFontURLAttribute = *(__CFString**)NativeLibrary.GetExport(lib, nameof(kCTFontURLAttribute));
|
||||
kCTFontNameAttribute = *(__CFString**)NativeLibrary.GetExport(lib, nameof(kCTFontNameAttribute));
|
||||
kCTFontDisplayNameAttribute = *(__CFString**)NativeLibrary.GetExport(lib, nameof(kCTFontDisplayNameAttribute));
|
||||
kCTFontFamilyNameAttribute = *(__CFString**)NativeLibrary.GetExport(lib, nameof(kCTFontFamilyNameAttribute));
|
||||
kCTFontStyleNameAttribute = *(__CFString**)NativeLibrary.GetExport(lib, nameof(kCTFontStyleNameAttribute));
|
||||
kCTFontTraitsAttribute = *(__CFString**)NativeLibrary.GetExport(lib, nameof(kCTFontTraitsAttribute));
|
||||
kCTFontWeightTrait = *(__CFString**)NativeLibrary.GetExport(lib, nameof(kCTFontWeightTrait));
|
||||
kCTFontWidthTrait = *(__CFString**)NativeLibrary.GetExport(lib, nameof(kCTFontWidthTrait));
|
||||
kCTFontSlantTrait = *(__CFString**)NativeLibrary.GetExport(lib, nameof(kCTFontSlantTrait));
|
||||
}
|
||||
|
||||
[DllImport(CoreTextLibrary)]
|
||||
public static extern __CTFontCollection* CTFontCollectionCreateFromAvailableFonts(__CFDictionary* options);
|
||||
|
||||
[DllImport(CoreTextLibrary)]
|
||||
public static extern __CFArray* CTFontCollectionCreateMatchingFontDescriptors(__CTFontCollection* collection);
|
||||
|
||||
[DllImport(CoreTextLibrary)]
|
||||
public static extern void* CTFontDescriptorCopyAttribute(__CTFontDescriptor* descriptor, __CFString* attribute);
|
||||
|
||||
[DllImport(CoreTextLibrary)]
|
||||
public static extern __CFDictionary* CTFontDescriptorCopyAttributes(__CTFontDescriptor* descriptor);
|
||||
}
|
||||
|
||||
internal struct __CTFontCollection;
|
||||
internal struct __CTFontDescriptor;
|
||||
#endif
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -105,6 +106,7 @@ public sealed partial class PhysicsSystem
|
||||
}
|
||||
|
||||
UpdateIsTouching(contacts);
|
||||
DispatchEvents();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -141,7 +143,8 @@ public sealed partial class PhysicsSystem
|
||||
if ((contact.Flags & ContactFlags.Filter) != 0x0)
|
||||
{
|
||||
if (!ShouldCollide(fixtureA, fixtureB) ||
|
||||
!ShouldCollide(uidA, uidB, bodyA, bodyB, fixtureA, fixtureB, xformA, xformB))
|
||||
!ShouldCollideSlow(uidA, uidB, bodyA, bodyB, fixtureA, fixtureB, xformA, xformB) ||
|
||||
!ShouldCollideJoints(uidA, uidB))
|
||||
{
|
||||
contact.IsTouching = false;
|
||||
continue;
|
||||
@@ -206,7 +209,20 @@ public sealed partial class PhysicsSystem
|
||||
var uidB = contact.EntityB;
|
||||
var bodyATransform = GetPhysicsTransform(uidA, xformQuery.GetComponent(uidA));
|
||||
var bodyBTransform = GetPhysicsTransform(uidB, xformQuery.GetComponent(uidB));
|
||||
var wasTouching = contact.IsTouching;
|
||||
|
||||
contact.UpdateIsTouching(bodyATransform, bodyBTransform);
|
||||
var points = new FixedArray4<Vector2>();
|
||||
|
||||
// Need to re-run the event otherwise we skip the StartCollideEvent running again later in the physics step.
|
||||
if (!wasTouching && contact.IsTouching)
|
||||
{
|
||||
contact.GetWorldManifold(bodyATransform, bodyBTransform, out var worldNormal, points.AsSpan);
|
||||
// Use the 3rd Vector2 as the world normal, 4th is blank.
|
||||
points._02 = worldNormal;
|
||||
}
|
||||
|
||||
RunContactEvents(Contact.GetContactStatus(contact, wasTouching), contact, points);
|
||||
}
|
||||
|
||||
ArrayPool<Contact>.Shared.Return(contacts);
|
||||
|
||||
@@ -562,7 +562,7 @@ namespace Robust.Client.Placement
|
||||
}
|
||||
|
||||
coordinates = InputManager.MouseScreenPosition;
|
||||
return true;
|
||||
return coordinates.IsValid;
|
||||
}
|
||||
|
||||
private bool CurrentEraserMouseCoordinates(out EntityCoordinates coordinates)
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace Robust.Client.Replays.Commands;
|
||||
public abstract class BaseReplayCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] protected readonly IReplayPlaybackManager PlaybackManager = default!;
|
||||
protected ILocalizationManager Loc => LocalizationManager;
|
||||
|
||||
public override string Description => Loc.GetString($"cmd-{Command.Replace('_','-')}-desc");
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ using System.Runtime.CompilerServices;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.ResourceManagement;
|
||||
@@ -17,6 +16,7 @@ namespace Robust.Client.ResourceManagement;
|
||||
/// </summary>
|
||||
internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInternal, IDisposable
|
||||
{
|
||||
[Shared.IoC.Dependency] private readonly IDependencyCollection _deps = default!;
|
||||
private readonly Dictionary<Type, TypeData> _cachedResources = new();
|
||||
private readonly Dictionary<Type, BaseResource> _fallbacks = new();
|
||||
|
||||
@@ -36,8 +36,7 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
var resource = new T();
|
||||
try
|
||||
{
|
||||
var dependencies = IoCManager.Instance!;
|
||||
resource.Load(dependencies, path);
|
||||
resource.Load(_deps, path);
|
||||
cache.Resources[path] = resource;
|
||||
return resource;
|
||||
}
|
||||
@@ -81,8 +80,7 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
var _resource = new T();
|
||||
try
|
||||
{
|
||||
var dependencies = IoCManager.Instance!;
|
||||
_resource.Load(dependencies, path);
|
||||
_resource.Load(_deps, path);
|
||||
resource = _resource;
|
||||
cache.Resources[path] = resource;
|
||||
return true;
|
||||
@@ -123,8 +121,7 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
|
||||
try
|
||||
{
|
||||
var dependencies = IoCManager.Instance!;
|
||||
res.Reload(dependencies, path);
|
||||
res.Reload(_deps, path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
<PackageReference Include="SpaceWizards.NFluidsynth" PrivateAssets="compile" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" />
|
||||
<PackageReference Include="OpenToolkit.Graphics" PrivateAssets="compile" />
|
||||
<PackageReference Include="OpenTK.OpenAL" PrivateAssets="compile" />
|
||||
<PackageReference Include="OpenTK.Audio.OpenAL" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.SharpFont" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.Sdl" PrivateAssets="compile" />
|
||||
<PackageReference Include="Robust.Natives" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" PrivateAssets="compile" />
|
||||
<PackageReference Condition="'$(RobustToolsBuild)' == 'True'" Include="JetBrains.Profiler.Api" PrivateAssets="compile" />
|
||||
@@ -63,10 +64,17 @@
|
||||
<RobustLinkAssemblies Include="TerraFX.Interop.Windows" />
|
||||
<RobustLinkAssemblies Include="TerraFX.Interop.Xlib" />
|
||||
<RobustLinkAssemblies Include="OpenToolkit.Graphics" />
|
||||
<RobustLinkAssemblies Include="SpaceWizards.Sdl" />
|
||||
<RobustLinkAssemblies Include="SpaceWizards.SharpFont" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets" />
|
||||
|
||||
<ItemGroup Condition="'$(IsFreedesktop)' == 'True'">
|
||||
<PackageReference Include="SpaceWizards.Fontconfig.Interop" />
|
||||
<RobustLinkAssemblies Include="SpaceWizards.Fontconfig.Interop" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\MSBuild\XamlIL.targets" />
|
||||
|
||||
<Import Project="..\MSBuild\Robust.Trimming.targets" />
|
||||
|
||||
192
Robust.Client/UserInterface/Axis.cs
Normal file
192
Robust.Client/UserInterface/Axis.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.UserInterface;
|
||||
|
||||
/// <summary>
|
||||
/// Defines an axis that certain controls can be laid out along.
|
||||
/// </summary>
|
||||
/// <seealso cref="IAxisImplementation"/>
|
||||
public enum Axis : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Items are laid out left to right.
|
||||
/// </summary>
|
||||
Horizontal,
|
||||
|
||||
/// <summary>
|
||||
/// Items are laid out right to left.
|
||||
/// </summary>
|
||||
HorizontalReverse,
|
||||
|
||||
/// <summary>
|
||||
/// Items are laid out top to bottom.
|
||||
/// </summary>
|
||||
Vertical,
|
||||
|
||||
/// <summary>
|
||||
/// Items are laid out bottom to top.
|
||||
/// </summary>
|
||||
VerticalReverse,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface that implements the rules of an <see cref="Axis"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// To make it easier to write code that supports all 4 layout axis, layout code is advised to use generics over this
|
||||
/// type and its implementors.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// An axis has a "main" and a "cross" axis. For example,
|
||||
/// <see cref="HorizontalAxis"/> has the main axis go left to right, and the cross axis go top to bottom.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The functions in this interface primarily allow converting between "UI space" (the normal UI coordinate system) and
|
||||
/// "axis space" (same as UI space for <see cref="HorizontalAxis"/>). This allows you to write all code as if you're
|
||||
/// doing only horizontal layout, but automatically have it work on all axis.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="HorizontalAxis"/>
|
||||
/// <seealso cref="HorizontalReverseAxis"/>
|
||||
/// <seealso cref="VerticalAxis"/>
|
||||
/// <seealso cref="VerticalReverseAxis"/>
|
||||
public interface IAxisImplementation
|
||||
{
|
||||
//
|
||||
// To/from axis space conversions
|
||||
//
|
||||
|
||||
/// <summary>
|
||||
/// Convert a size value (e.g. from <see cref="Control.DesiredSize"/>) from UI space to axis space.
|
||||
/// </summary>
|
||||
static abstract Vector2 SizeToAxis(Vector2 size);
|
||||
|
||||
/// <summary>
|
||||
/// Convert a size value (e.g. for <see cref="Control.Measure"/>) from axis space to UI space.
|
||||
/// </summary>
|
||||
static abstract Vector2 SizeFromAxis(Vector2 size);
|
||||
|
||||
/// <summary>
|
||||
/// Convert a box (e.g. for <see cref="Control.Arrange"/>) from axis space to UI space.
|
||||
/// </summary>
|
||||
/// <param name="box">The box to convert, in axis space.</param>
|
||||
/// <param name="spaceSize">The amount of space, in UI space, that the layout is happening relative to.</param>
|
||||
static abstract UIBox2 BoxFromAxis(UIBox2 box, Vector2 spaceSize);
|
||||
|
||||
//
|
||||
// Control
|
||||
//
|
||||
|
||||
/// <summary>
|
||||
/// Gets the "expand flag" (<see cref="Control.HorizontalExpand"/> or <see cref="Control.VerticalExpand"/>) for a
|
||||
/// control that is appropriate for the main axis.
|
||||
/// </summary>
|
||||
static abstract bool GetMainExpandFlag(Control control);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Axis implementation for <see cref="Axis.Horizontal"/>.
|
||||
/// </summary>
|
||||
public struct HorizontalAxis : IAxisImplementation
|
||||
{
|
||||
public static Vector2 SizeToAxis(Vector2 size)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
public static Vector2 SizeFromAxis(Vector2 size)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
public static UIBox2 BoxFromAxis(UIBox2 box, Vector2 spaceSize)
|
||||
{
|
||||
return box;
|
||||
}
|
||||
|
||||
public static bool GetMainExpandFlag(Control control)
|
||||
{
|
||||
return control.HorizontalExpand;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Axis implementation for <see cref="Axis.HorizontalReverse"/>.
|
||||
/// </summary>
|
||||
public struct HorizontalReverseAxis : IAxisImplementation
|
||||
{
|
||||
public static Vector2 SizeToAxis(Vector2 size)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
public static Vector2 SizeFromAxis(Vector2 size)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
public static UIBox2 BoxFromAxis(UIBox2 box, Vector2 spaceSize)
|
||||
{
|
||||
return new UIBox2(spaceSize.X - box.Right, box.Top, spaceSize.X - box.Left, box.Bottom);
|
||||
}
|
||||
|
||||
public static bool GetMainExpandFlag(Control control)
|
||||
{
|
||||
return control.HorizontalExpand;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Axis implementation for <see cref="Axis.Vertical"/>.
|
||||
/// </summary>
|
||||
public struct VerticalAxis : IAxisImplementation
|
||||
{
|
||||
public static Vector2 SizeToAxis(Vector2 size)
|
||||
{
|
||||
return new Vector2(size.Y, size.X);
|
||||
}
|
||||
|
||||
public static Vector2 SizeFromAxis(Vector2 size)
|
||||
{
|
||||
return new Vector2(size.Y, size.X);
|
||||
}
|
||||
|
||||
public static UIBox2 BoxFromAxis(UIBox2 box, Vector2 spaceSize)
|
||||
{
|
||||
return new UIBox2(box.Top, box.Left, box.Bottom, box.Right);
|
||||
}
|
||||
|
||||
public static bool GetMainExpandFlag(Control control)
|
||||
{
|
||||
return control.VerticalExpand;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Axis implementation for <see cref="Axis.VerticalReverse"/>.
|
||||
/// </summary>
|
||||
public struct VerticalReverseAxis : IAxisImplementation
|
||||
{
|
||||
public static Vector2 SizeToAxis(Vector2 size)
|
||||
{
|
||||
return new Vector2(size.Y, size.X);
|
||||
}
|
||||
|
||||
public static Vector2 SizeFromAxis(Vector2 size)
|
||||
{
|
||||
return new Vector2(size.Y, size.X);
|
||||
}
|
||||
|
||||
public static UIBox2 BoxFromAxis(UIBox2 box, Vector2 spaceSize)
|
||||
{
|
||||
return new UIBox2(box.Top, spaceSize.Y - box.Right, box.Bottom, spaceSize.Y - box.Left);
|
||||
}
|
||||
|
||||
public static bool GetMainExpandFlag(Control control)
|
||||
{
|
||||
return control.VerticalExpand;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ namespace Robust.Client.UserInterface
|
||||
get => _stylesheet;
|
||||
set
|
||||
{
|
||||
if (ReferenceEquals(_stylesheet, value))
|
||||
return;
|
||||
|
||||
_stylesheet = value;
|
||||
StylesheetUpdateRecursive();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Client.UserInterface.Themes;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -74,6 +75,8 @@ namespace Robust.Client.UserInterface
|
||||
// _nameScope = nameScope;
|
||||
//}
|
||||
|
||||
public virtual ISawmill Log => UserInterfaceManager.ControlSawmill;
|
||||
|
||||
public UITheme Theme { get; internal set; }
|
||||
|
||||
private UITheme? _themeOverride;
|
||||
@@ -381,8 +384,6 @@ namespace Robust.Client.UserInterface
|
||||
/// </summary>
|
||||
public event EventHandler? OnShowTooltip;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// If this control is currently showing a tooltip provided via TooltipSupplier,
|
||||
/// returns that tooltip. Do not move this control within the tree, it should remain in PopupRoot.
|
||||
@@ -1049,7 +1050,7 @@ namespace Robust.Client.UserInterface
|
||||
Ignore = 2,
|
||||
}
|
||||
|
||||
public sealed class OrderedChildCollection : ICollection<Control>, IReadOnlyCollection<Control>
|
||||
public sealed class OrderedChildCollection : ICollection<Control>, IReadOnlyList<Control>
|
||||
{
|
||||
private readonly Control Owner;
|
||||
|
||||
@@ -1101,6 +1102,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
int ICollection<Control>.Count => Owner.ChildCount;
|
||||
int IReadOnlyCollection<Control>.Count => Owner.ChildCount;
|
||||
public Control this[int index] => Owner._orderedChildren[index];
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
|
||||
@@ -27,8 +27,6 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </remarks>
|
||||
public AlignMode Align { get; set; }
|
||||
|
||||
private bool Vertical => Orientation == LayoutOrientation.Vertical;
|
||||
|
||||
public LayoutOrientation Orientation
|
||||
{
|
||||
get => _orientation;
|
||||
@@ -56,19 +54,24 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
// Account for separation.
|
||||
var separation = ActualSeparation * (Children.Where(c => c.Visible).Count() - 1);
|
||||
var desiredSize = Vector2.Zero;
|
||||
if (Vertical)
|
||||
if (Orientation == LayoutOrientation.Vertical)
|
||||
{
|
||||
desiredSize.Y += separation;
|
||||
availableSize.Y = Math.Max(0, availableSize.Y - separation);
|
||||
return MeasureItems<VerticalAxis>(availableSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
desiredSize.X += separation;
|
||||
availableSize.X = Math.Max(0, availableSize.X - separation);
|
||||
return MeasureItems<HorizontalAxis>(availableSize);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 MeasureItems<TAxis>(Vector2 availableSize) where TAxis : IAxisImplementation
|
||||
{
|
||||
availableSize = TAxis.SizeToAxis(availableSize);
|
||||
|
||||
// Account for separation.
|
||||
var separation = ActualSeparation * (Children.Where(c => c.Visible).Count() - 1);
|
||||
var desiredSize = new Vector2(separation, 0);
|
||||
availableSize.X = Math.Max(0, availableSize.X) - separation;
|
||||
|
||||
// First, we measure non-stretching children.
|
||||
foreach (var child in Children)
|
||||
@@ -76,46 +79,74 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (!child.Visible)
|
||||
continue;
|
||||
|
||||
child.Measure(availableSize);
|
||||
child.Measure(TAxis.SizeFromAxis(availableSize));
|
||||
var childDesired = TAxis.SizeToAxis(child.DesiredSize);
|
||||
|
||||
if (Vertical)
|
||||
{
|
||||
desiredSize.Y += child.DesiredSize.Y;
|
||||
desiredSize.X = Math.Max(desiredSize.X, child.DesiredSize.X);
|
||||
availableSize.Y = Math.Max(0, availableSize.Y - child.DesiredSize.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
desiredSize.X += child.DesiredSize.X;
|
||||
desiredSize.Y = Math.Max(desiredSize.Y, child.DesiredSize.Y);
|
||||
availableSize.X = Math.Max(0, availableSize.X - child.DesiredSize.X);
|
||||
}
|
||||
desiredSize.X += childDesired.X;
|
||||
desiredSize.Y = Math.Max(desiredSize.Y, childDesired.Y);
|
||||
|
||||
availableSize.X = Math.Max(0, availableSize.X - childDesired.X);
|
||||
}
|
||||
|
||||
return desiredSize;
|
||||
return TAxis.SizeFromAxis(desiredSize);
|
||||
}
|
||||
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
var separation = ActualSeparation;
|
||||
var visibleChildCount = Children.Where(c => c.Visible).Count();
|
||||
|
||||
var stretchAvail = Vertical ? finalSize.Y : finalSize.X;
|
||||
if (Orientation == LayoutOrientation.Vertical)
|
||||
{
|
||||
LayOutItems<VerticalAxis>(default, finalSize, Align, Children, 0, ChildCount, separation);
|
||||
}
|
||||
else
|
||||
{
|
||||
LayOutItems<HorizontalAxis>(default, finalSize, Align, Children, 0, ChildCount, separation);
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
internal static void LayOutItems<TAxis>(
|
||||
Vector2 baseOffset,
|
||||
Vector2 finalSize,
|
||||
AlignMode align,
|
||||
OrderedChildCollection children,
|
||||
int start,
|
||||
int end,
|
||||
float separation,
|
||||
Vector2? fixedSize = null)
|
||||
where TAxis : IAxisImplementation
|
||||
{
|
||||
var realFinalSize = finalSize;
|
||||
finalSize = TAxis.SizeToAxis(finalSize);
|
||||
fixedSize = fixedSize == null ? null : TAxis.SizeToAxis(fixedSize.Value);
|
||||
|
||||
var visibleChildCount = 0;
|
||||
for (var i = start; i < end; i++)
|
||||
{
|
||||
if (children[i].Visible)
|
||||
visibleChildCount += 1;
|
||||
}
|
||||
|
||||
var stretchAvail = finalSize.X;
|
||||
stretchAvail -= separation * (visibleChildCount - 1);
|
||||
stretchAvail = Math.Max(0, stretchAvail);
|
||||
|
||||
// Step one: figure out the sizes of all our children and whether they want to stretch.
|
||||
var sizeList = new List<(Control control, float size, bool stretch)>(visibleChildCount);
|
||||
var totalStretchRatio = 0f;
|
||||
foreach (var child in Children)
|
||||
for (var i = start; i < end; i++)
|
||||
{
|
||||
var child = children[i];
|
||||
if (!child.Visible)
|
||||
continue;
|
||||
|
||||
bool stretch = Vertical ? child.VerticalExpand : child.HorizontalExpand;
|
||||
bool stretch = TAxis.GetMainExpandFlag(child);
|
||||
if (!stretch)
|
||||
{
|
||||
var size = Vertical ? child.DesiredSize.Y : child.DesiredSize.X;
|
||||
var measuredSize = fixedSize ?? TAxis.SizeToAxis(child.DesiredSize);
|
||||
var size = measuredSize.X;
|
||||
size = Math.Clamp(size, 0, stretchAvail);
|
||||
stretchAvail -= size;
|
||||
sizeList.Add((child, size, false));
|
||||
@@ -146,7 +177,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
continue;
|
||||
|
||||
var share = stretchAvail * control.SizeFlagsStretchRatio / totalStretchRatio;
|
||||
var desired = Vertical ? control.DesiredSize.Y : control.DesiredSize.X;
|
||||
var measuredSize = fixedSize ?? TAxis.SizeToAxis(control.DesiredSize);
|
||||
var desired = measuredSize.X;
|
||||
if (share >= desired)
|
||||
{
|
||||
sizeList[i] = (control, share, true);
|
||||
@@ -164,7 +196,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
else
|
||||
{
|
||||
// No stretching children -> offset the children based on the alignment.
|
||||
switch (Align)
|
||||
switch (align)
|
||||
{
|
||||
case AlignMode.Begin:
|
||||
break;
|
||||
@@ -190,22 +222,14 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
first = false;
|
||||
|
||||
UIBox2 targetBox;
|
||||
if (Vertical)
|
||||
{
|
||||
targetBox = new UIBox2(0, offset, finalSize.X, offset + size);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetBox = new UIBox2(offset, 0, offset + size, finalSize.Y);
|
||||
}
|
||||
var targetBox = TAxis.BoxFromAxis(new UIBox2(offset, 0, offset + size, finalSize.Y), realFinalSize);
|
||||
|
||||
targetBox = targetBox.Translated(baseOffset);
|
||||
|
||||
control.Arrange(targetBox);
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
public enum AlignMode : byte
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
private ReadOnlyMemory<char> _textMemory;
|
||||
private bool _clipText;
|
||||
private AlignMode _align;
|
||||
private Font? _fontOverride;
|
||||
|
||||
public Label()
|
||||
{
|
||||
@@ -106,7 +107,16 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
[ViewVariables] public VAlignMode VAlign { get; set; }
|
||||
|
||||
public Font? FontOverride { get; set; }
|
||||
public Font? FontOverride
|
||||
{
|
||||
get => _fontOverride;
|
||||
set
|
||||
{
|
||||
_fontOverride = value;
|
||||
_textDimensionCacheValid = false;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
private Font ActualFont
|
||||
{
|
||||
|
||||
@@ -121,11 +121,14 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
if (show)
|
||||
{
|
||||
if (Root == null)
|
||||
throw new InvalidOperationException("No UI root! We can't pop up!");
|
||||
|
||||
var globalPos = GlobalPosition;
|
||||
_popupVBox.Measure(Vector2Helpers.Infinity);
|
||||
var (minX, minY) = _popupVBox.DesiredSize;
|
||||
var box = UIBox2.FromDimensions(globalPos, new Vector2(Math.Max(minX, Width), minY));
|
||||
UserInterfaceManager.ModalRoot.AddChild(_popup);
|
||||
Root.ModalRoot.AddChild(_popup);
|
||||
_popup.Open(box);
|
||||
}
|
||||
else
|
||||
@@ -136,7 +139,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
private void OnPopupHide()
|
||||
{
|
||||
UserInterfaceManager.ModalRoot.RemoveChild(_popup);
|
||||
_popup.Orphan();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user