Compare commits

...

276 Commits

Author SHA1 Message Date
DrSmugleaf
190c35dbdf Version: 240.1.3-source-gen-debug 2025-01-21 19:11:31 -08:00
DrSmugleaf
b52c26d0eb Merge branch 'master' of https://github.com/space-wizards/RobustToolbox into serialization-source-gen-debug 2025-01-21 19:11:05 -08:00
DrSmugleaf
e03aec47ef Fix component network generator not working for dicts with entityuid values but not keys 2025-01-21 19:09:22 -08:00
DrSmugleaf
35c983d2fd Version: 240.1.2-source-gen-debug 2025-01-21 18:47:40 -08:00
DrSmugleaf
88cfd04f01 Source gen changes 2025-01-21 18:47:25 -08:00
DrSmugleaf
ee906af16e Version: 240.1.2 2025-01-21 18:43:28 -08:00
wixoa
e205ae3627 Use the color arg when drawing a font character in world space (#5626) 2025-01-21 22:39:07 +01:00
Jerry
d818c5aa0c fix StatusHost RespondJsonAsync method (#5622) 2025-01-21 16:58:37 +01:00
ElectroJr
aa8fe8ac92 Version: 240.1.1 2025-01-20 14:16:39 +13:00
Leon Friedrich
4ba6687b9d Fix OverlayManager.RemoveOverlay (#5623) 2025-01-20 12:14:58 +11:00
ElectroJr
8f2817aa4e Version: 240.1.0 2025-01-20 13:04:19 +13:00
Leon Friedrich
89c7839fe2 Fix exception in DestroyContacts (#5619) 2025-01-19 16:25:58 +11:00
DrSmugleaf
9799132001 Make GamePrototypeLoadManager send a single message with all uploaded prototypes in a single message (#5616) 2025-01-19 11:49:27 +11:00
Milon
34ffa56c57 add AsNullable() extension method to Entity<T> (#5617)
* just works first try

* use a constructor instead
2025-01-18 20:52:18 +01:00
ElectroJr
f8410a4674 Version: 240.0.1 2025-01-18 16:54:03 +13:00
Leon Friedrich
43b991c690 Fix SharedBroadphaseSystem.GetBroadphases (#5615) 2025-01-18 14:37:54 +11:00
PJB3005
c463fc5e78 Make source generators emit EditorBrowsable(Never)
This hides the generated types from intellisense if you have the relevant option in Rider enabled.

Good because honestly these just bloat IntelliSense, and if you want to show them there's an IDE setting for it.
2025-01-18 01:23:35 +01:00
PJB3005
e21b3e069a Version: 240.0.0 2025-01-17 17:57:31 +01:00
PJB3005
c8f94ab40d Update release notes 2025-01-17 17:57:14 +01:00
Tornado Tech
2a882b5555 Moved Overlay sorting to OverlayManager (#5614)
* Moved Overlay sorting to OverlayManager

* Changed Add to AddRange
2025-01-17 17:47:13 +01:00
Leon Friedrich
32d8a1cba9 Misc grid state changes (#5597) 2025-01-17 17:07:09 +01:00
Leon Friedrich
5d84be9c78 Make ComponentRegistry not implement ISerializationContext (#5613) 2025-01-17 15:01:25 +01:00
PJB3005
eaaa70437a Reduce DynamicMethod use in Serv3
Data definitions created individual read/write methods for every single field that can be serialized. This was extremely inefficient and likely caused lots of overhead.

These methods are necessary because, in some cases, we can't directly use expression trees to write fields. But... we can still do it most of the time! So now for most data fields we can avoid a DynamicMethod entirely.
2025-01-16 15:10:35 +01:00
Leon Friedrich
448e8b0c2c Validate static EntProtoId<T> fields (#5593) 2025-01-16 10:36:47 +01:00
Leon Friedrich
42948d8f8e Fix autocompletion hint for toolshed strings (#5584)
* Fix autocompletion hint for toolshed strings

* Split functionality

* Remove combined flag
2025-01-16 10:30:42 +01:00
c4llv07e
039468f4b6 Add function to check if localization culture was already loaded. (#5603) 2025-01-16 10:28:15 +01:00
Leon Friedrich
6a3f88b1c6 Fix replay playback bugs (#5604)
* Sort replay entities

* Fix nameof(TState)

* I forgot to generate implicit states

* release notes
2025-01-16 10:26:53 +01:00
PJB3005
a314c5f797 Tickrate is now a ushort
Fixes #5592

This allows net.tickrate to be set to a max of 65535 instead of 255.

I didn't raise it fully to a uint because there are many places it's cast to an int, so uint would cause various compiler errors and compat issues I don't wanna deal with.
2025-01-16 01:13:50 +01:00
PJB3005
bcc4cd77cf Version: 239.0.1 2025-01-15 20:16:05 +01:00
PJB3005
941cb4c1d6 Release notes 2025-01-15 20:15:21 +01:00
Myra
7b58760331 Downgrade VorbisPizza back to 1.3.0 (#5607)
Somewhere between 1.3.0 and 1.4.0 (I tested all versions) exists a bug that for some reason messes with the audio on some devices.

A proper fix would require us trying to make contact with the developer of VorbisPizza, find the buggy commit and waiting them to update their repo. This repo has been inactive for 8 months. I doubt we can get support unless we fork it and find the bug ourselves.

Closes & fixes #5605
2025-01-15 17:18:23 +01:00
PJB3005
f0306b593a Optimize Robust.Serialization.Generator
Slightly less bad use of incremental generators.

Remove format of generated code, it was taking most of the CPU time.

This seems to significantly cut compile time cost of this generator. Nice. I'm seeing 20 -> 5 seconds on my system.
2025-01-15 02:41:57 +01:00
PJB3005
5e1935c310 Optimize ByRefEventAnalyzer
According to a log, like 20 seconds of the build is spent in this analyzer. It now takes ~200ms. Hooray!
2025-01-13 05:52:31 +01:00
PJB3005
67c44a5fc5 Add unit test for ByRefEventAnalyzer 2025-01-13 05:44:35 +01:00
PJB3005
3ea0a0244b Fix net.packet RECV logging 2025-01-10 18:09:27 +01:00
PJB3005
325a39ee4b Wow way to expose that my lazy ass didn't actually try running package_webview.py on this build. 2025-01-09 04:22:25 +01:00
PJB3005
e4190f4f29 Version: 239.0.0 2025-01-09 01:45:49 +01:00
PJB3005
4d163ed818 Update release notes 2025-01-09 01:44:16 +01:00
ike709
09c6a816e0 Bump cefglue (#5585)
* Bump cefglue

* another bump

* Update build-test.yml

* Update to latest CEF natives and fix code to be compatible.

* Switch CEF CreateBrowserWindow to Alloy style

you will NOT open Chrome

* Update cefglue, again

---------

Co-authored-by: ike709 <ike709@github.com>
Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-01-08 02:32:24 +01:00
Pieter-Jan Briers
dfc4894c8b Dependencies update & cleanup (#5590)
* Dependencies update & cleanup

Fixes security vuln warnings etc

* Remove ILReader dependency

RIP in piss FastAccessors.
2025-01-08 02:19:27 +01:00
PJB3005
f2b096f145 Fix SDL3 backend
.NET 9 update changed library import generator behavior and it broke my funny shit.
2025-01-08 01:39:56 +01:00
PJB3005
1984e97d2f Show "null" in loglevel command completions
This was already supported, but the completions didn't list it.
2025-01-07 21:33:37 +01:00
PJB3005
57291b88c0 Log received/sent net messages at verbose level
This has always been in the code but commented out for debugging. Now it's actually logging to Verbose level. Also the log level is "net.packet" now instead of just "net". Also it logs received size too.

This makes use of the new IsLogLevelEnabled to avoid the perf overhead when not being used.
2025-01-07 21:11:56 +01:00
PJB3005
f40dd51648 Add ISawmill.IsLogLevelEnabled
So you can avoid logging things unless somebody's actually trying to debug something.
2025-01-07 20:54:14 +01:00
Leon Friedrich
d08fdd3a18 Fix component delta state auto-generation (#5589)
* Fix component delta state auto-generation

* re-use ApplyToFullState

* release notes
2025-01-07 16:54:59 +01:00
Leon Friedrich
80dbf02af4 Log errors when encountering entity data fields (#5578)
* Log errors when encountering entity data fields

* Cleanup & comments

* Remove unnecessary GetGenericTypeDefinition
2025-01-07 16:54:25 +01:00
IProduceWidgets
a2983a5ee0 safe dictionary assignment (#5587) 2025-01-06 21:02:02 +01:00
PJB3005
7810cd0c2e Disable CETCompat in Robust.Client.WebView
Guess what doesn't work with CEF (not that it works in the first place for us)
2025-01-06 03:15:36 +01:00
PJB3005
6c8b863731 Add string split with ROS<char> to sandbox 2025-01-06 03:08:28 +01:00
ike709
c2ca7c7811 .NET 9 (#5552)
Co-authored-by: ike709 <ike709@github.com>
2025-01-06 01:16:01 +01:00
PJB3005
347d240fae Add API for creating mock configuration managers in unit tests. 2025-01-05 23:27:59 +01:00
Pieter-Jan Briers
87a5745519 SDL3 (#5583)
* Start converting SDL2 backend to SDL3.

Game starts, but a lot of stuff is broken. Oh well.

* Fix text input

SDL3 changed the API somewhat, for the better. Changes all over UI/Clyde/SDL3 layer.

* Fix mouse buttons being broken

* Remove records from SDL3 WSI

The fact that this shaved 2-3% off Robust.Client.dll is mindboggling. Records are so bad.

* Set Windows/X11 native window properties

* Fix window resize events getting wrong size

oops

* Remove "using static" from SDL3 WSI

Seriously seems to hurt IDE performance, oh well.

* Apparently I never called CheckThreadApartment().

* Add STAThreadAttribute to sandbox

Necessary for content start

* Set window title on creation properly.

* Load window icons

* Fix GLFW NoTitleBar style handling

Yeah this PR is supposed to be about SDL3, so what?

* Implement more window creation settings in SDL3

Mostly the ones that need a lot of platform-specific stuff to work.

* Make fullscreen work properly in SDL3.

* File dialogs with SDL3

Removes need for swnfd.

* Fix some TODOs

* Fix WebView build
2025-01-03 18:42:57 +01:00
ElectroJr
e47ba0faea Version: 238.0.1 2024-12-28 18:51:38 +13:00
Southbridge
fb705702fb PlacementManager CurrentEraserMouseCoordinates fix (#5576) 2024-12-28 16:48:26 +11:00
sleepyyapril
a2aec44ebb Fix Build Error with Auto-networked EntityUid Dictionaries (#5569) 2024-12-22 21:12:44 +11:00
ElectroJr
5e97db435c Version: 238.0.0 2024-12-21 19:51:56 +13:00
Leon Friedrich
9af119f57a Toolshed Rejig (#5455)
* Toolshed Rejig

* shorten hint string

* Try fix conflicts. Ill make with work later

* bodge

* Fix ProtoIdTypeParser assert

* comment

* AllEntities

* Remove more linq from WhereCommand

* better help strings

* Add ContainsCommand

* loc strings

* Add contains command description

* Add $self variable

* Errors for writing to readonly variables

* A
2024-12-21 17:49:11 +11:00
MLGTASTICa
6247be2c84 Changes SharedGridTraversalSystem accesibility from internal to public (#5551)
* Make this public.

* Add warnings.
2024-12-21 17:11:18 +11:00
metalgearsloth
acb1d37b99 Version: 237.4.0 2024-12-21 15:53:21 +11:00
metalgearsloth
82c94fc8b0 transform traversals (#5564) 2024-12-21 15:49:43 +11:00
metalgearsloth
9837c33de7 Add sourcegenned field deltas (#5155)
* Remove full "delta" states

* Update MapGridComponentState

* abstract ComponentState

* Release notes

* Fix tests

* Fix nullable errors

* A

* Sourcegen component deltas

* Audio deltas + methids

* Also eye

* Optimise out the dictionary

* Minor fixes

* Physics deltas

* Also this

* Fix field deltas

* remove old release notes

* Make IComponentDelta implement IComponent

* add sourcegen launch settings

* make silent error loud

* Review

* UI deltas

* Slimmer

* Sourcegen bandaid

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2024-12-21 15:48:33 +11:00
MilenVolf
ac30ad1820 Replace all usages of obsolete MapGridComponent methods (#5547)
* Replace obsolete MapGridComponent methods

Some other obsolete methods were also replaced in edited files

* Whitespace and better readability

* Value instead of cast

* Fix ISawmill not initialized and incorrect log
2024-12-18 18:46:53 +01:00
metalgearsloth
9c30fdf5fd Version: 237.3.0 2024-12-16 16:33:24 +11:00
metalgearsloth
0b7e8c2560 Add shapecasts + raycasts (#5440)
* Add shapecasts + raycasts

Actual raycasts. Need this for AI LIDAR experiment.

* cassette

* more cudin

* Mostly ported

* more work

* More ports

* the big house

* rays

* builds

* Janky not working raycasts

* Fix GJK

* Test fixes

* Shapecast + fixes

* free

* tests

* More fixes

* Minor changes

* Not these

* Release notes
2024-12-16 16:07:24 +11:00
IProduceWidgets
7982aa236c TryFindComponentOnEntityContainerOrParent no fail please (#5322)
* TryFindComponentsOnEntityContainerOrParent no fail please

* remove my comments because Im dumb

* out makes sense to me!

* Revert "out makes sense to me!" because PJB no want breaky

This reverts commit 54f4a6d50c.
2024-12-14 17:16:43 +01:00
PJB3005
0559339143 Add AssetPassFilterDrop
Planning to use this to drop .svg files from SS14's resources folder. Tiny opt.
2024-12-14 17:07:57 +01:00
Zachary Higgs
89fcd1dd2b Add InterfaceData constructor (#5559)
* Add InterfaceData constructor

Add InterfaceData constructor to allow for dynamic UI assignment via
SharedUiSystem::setUi

* empty commit because of heisentest
2024-12-14 16:19:05 +01:00
metalgearsloth
649378e59a BUI helpers (#5558)
* BUI helpers

- Some virtual methods for BUI to make it slightly easier. Haven't though of a good way to do it via sourcegen yet.
- TryGetUiState which is occasionally useful.

* Also this one
2024-12-13 01:33:17 +01:00
SpaceManiac
0c7ace16d1 Fix most non-obsolete warnings (#5555) 2024-12-13 01:25:00 +01:00
metalgearsloth
27f7f5ee36 Add Pure attribute to some entmanager methods (#5557) 2024-12-12 18:24:20 +01:00
Pieter-Jan Briers
fe0fcbd851 Add RSI key to disable meta-atlas (#5544)
This allows huge textures (e.g. Ratvar) to be removed from the meta-atlas. This saves a significant chunk of VRAM from the meta atlas in SS14 (~126 MB) which might help a bit with low-VRAM systems.
2024-12-08 23:49:12 +01:00
Pieter-Jan Briers
aca7847933 Add CVars for privacy policy information (#5545)
* Add CVars for privacy policy information

Engine side of https://github.com/space-wizards/SS14.Launcher/issues/194

* Improve/fix cvar desc
2024-12-08 23:48:50 +01:00
Leon Friedrich
1621d25a92 Fix UserInterfaceSystem debug assert (#5546) 2024-11-30 13:09:51 +01:00
Amy
b7e0a9bc03 Make font drawing more generic (#5533)
* make richtextentry more generic

* font

* oops
2024-11-29 11:28:27 +01:00
metalgearsloth
9909416006 Fix grid container layout (#5543)
This decrements index and cooks the layout if controls are invisible.
2024-11-29 10:54:34 +01:00
Leon Friedrich
c3e487b61c Fix IPrototypeManager.TryGetKindFrom() (#5542) 2024-11-29 01:48:02 +01:00
Nikolai Korolev
89ad8b6c9f Upgrade GitHub actions in workflows (#5536)
* Upgrade github workflows to node20

* Fix incorrect version for actions/checkout in test-content.yml
2024-11-28 19:52:14 +01:00
Nikolai Korolev
efbc9ef2bf Fix codeql-analysis breaking in every fork repo that enables GitHub Actions (#5537) 2024-11-28 19:50:39 +01:00
Pieter-Jan Briers
ce240773e8 ValueList<T> extensions (#5534)
Stack-like functions. Just some code I had lying around and never committed.

Add ROS overload for AddRange
2024-11-28 19:49:51 +01:00
SpaceManiac
8563466011 Fix wrong filename used when log.enabled is set (#5541) 2024-11-28 19:46:53 +01:00
Nikolai Korolev
af4d53fb54 No need for disabling RA0003 warning in FastNoise (#5535) 2024-11-25 00:37:05 +01:00
Pieter-Jan Briers
3086fc446c Sandbox error reference locator now works with generic method calls
This means resolving the MethodSpec table entry for it.
2024-11-22 18:06:33 +01:00
Nikolai Korolev
5f3a54376d Fix warnings for using async without any await (#5532)
* Fix warnings for using async without any await

* Fix async without await warning in EntityTypeParser

* Fix async without await in EnumTypeParser

* Update SessionTypeParser.cs

* Update EntityTypeParser.cs

* Update BoolTypeParser.cs

* Update EntityTypeParser.cs

* Update EnumTypeParser.cs

* Update SessionTypeParser.cs

* Fix compilation and formatting
2024-11-22 02:18:35 +01:00
Nikolai Korolev
9bb7af364e Fix warning for using non-generic variant of TryComp for MetaDataComponent and TransformComponent RA0030 (#5531)
* Fix warning for using non-generic variant of TryComp for MetaDataComponent RA0030 (Use non-generic variant)

* Use non-generic variant of TryComp for TransformComponent
2024-11-22 01:30:55 +01:00
Pieter-Jan Briers
92b0e7f1a8 Version: 237.2.0 2024-11-21 00:03:19 +01:00
Pieter-Jan Briers
47d1c372b2 Mute null error from Rider 2024-11-20 23:45:47 +01:00
SpaceManiac
453f763128 Fix minor layout bugs in SplitContainer and BoxContainer (#5529)
* Fix SplitContainer using invalidated measures when clamping SplitCenter

* Fix BoxContainer adding separation for invisible children
2024-11-20 23:41:10 +01:00
Nikolai Korolev
65a7942d63 Remove unused variable, local function and private field (#5528)
* Remove unused local function

* Remove unused variable

* Remove private field
2024-11-20 02:51:46 +01:00
Saphire Lattice
f1f3c60d1f Improve Toolshed type intersection mechanism, add WithCommand for ProtoId (#5515) 2024-11-20 01:00:13 +01:00
SpaceManiac
d4e8a27c23 Add FormattedMessage.TrimEnd (#5524)
* Add FormattedMessage.TrimTrailingNewlines

* Trim all trailing whitespace
2024-11-19 19:55:19 +01:00
Pieter-Jan Briers
18a17da8fa .NET 9 forward compatibility changes
This doesn't switch the projects over to .NET 9, but it does make them work on .NET 9 when we decide to switch in the future.
2024-11-19 19:54:01 +01:00
Nikolai Korolev
90a8c66e96 Fix System.ArgumentException: '0' cannot be greater than -0.01 for very fast audios (#5521)
* Fix `System.ArgumentException: '0' cannot be greater than -0.01`

In case of playing empty audio files

* Add semicolon
2024-11-18 18:46:01 +01:00
MilenVolf
45c14b2bc3 Replace remaining obsolete TileAccess methods (#5519)
* Replace remaining TileAccess methods

* Small fix
2024-11-18 17:19:57 +01:00
SpaceManiac
d227613997 Fix cursor getting stuck when click-dragging off of a control (#5523) 2024-11-16 23:51:32 +01:00
Partmedia
7557cc703c Add FreeBSD packaging target (#5522) 2024-11-16 02:01:17 +01:00
Leon Friedrich
7b81d0d881 Make PVS ignore duplicate view subscriber (#5502) 2024-11-13 00:07:53 +01:00
Leon Friedrich
b59f7801ac More UniqueIndex fixes (#5501) 2024-11-12 23:40:01 +01:00
FluffMe
d724c5b3eb Add conditional formatting to SpinBox buttons text (#5511) 2024-11-12 23:32:14 +01:00
Saphire Lattice
f812dc4dac Hopefully fix the dreaded VV refresh blink (#5517) 2024-11-12 23:31:39 +01:00
MilenVolf
2a1bcb6f1e Replace some obsolete TileAccess methods (#5516)
* Replace some obsolete TileAccess methods

* Guh
2024-11-12 23:17:40 +01:00
slarticodefast
fa9030e59c correct sandbox whitelist for Regex.Matches Method (#5513) 2024-11-12 21:48:39 +01:00
Pieter-Jan Briers
8dcae8631b Update NetSerializer
This enables sending of ImmutableArray<T>
2024-11-11 21:36:41 +01:00
Pieter-Jan Briers
21c3535486 Avoid unhandled exception handlers logging into disposed sawmills on shutdown
This caused a crash & exception swallow on Salamander.
2024-11-11 16:26:04 +01:00
lzk
4e100d96bc Add dative case function to loc manager (#5510)
* dative

* slipped it

* slipped it twice
2024-11-05 19:55:30 +01:00
qrtDaniil
14d3699ae2 Fix for server consoles without width and length (#5507)
* Update SystemConsoleManager.cs

* Update SystemConsoleManager.cs
2024-11-01 23:39:30 +01:00
Amy
350fa8736d add ref readonly to sandbox (#5506) 2024-10-30 02:49:25 +01:00
eoineoineoin
5a82df216d Fixes for rendering in multiple windows (#5497)
* Fix race condition when swapping buffers of secondary windows

* Avoid creating opengl 3.3 windows, to avoid Steam overlay bug

* Revert "Avoid creating opengl 3.3 windows, to avoid Steam overlay bug"

This reverts commit 97b5e7f461.

* Add CVar to perform unlocking test
2024-10-19 16:13:29 +02:00
Pieter-Jan Briers
32bca7cfd4 Version: 237.1.0 2024-10-19 12:03:52 +02:00
wixoa
008babebc6 Fix some window UIScale bugs (#5499)
* Fix some window UIScale bugs

* Use CalculateAutoScale()
2024-10-19 00:08:16 +02:00
Pieter-Jan Briers
c65c4ba57e Made csi reflection helpers get members up the inheritance chain too 2024-10-18 18:40:39 +02:00
Pieter-Jan Briers
eb5b838e61 Made csi type auto-completion aware of generic types 2024-10-18 18:40:39 +02:00
Pieter-Jan Briers
6b43036c9d Fix UniqueIndexHkm memory leaking
Yeah that's just great this goddamn data structure had no damn API to ever remove anything from it. Incredible.
2024-10-18 18:40:39 +02:00
ElectroJr
f23a55793d Version: 237.0.0 2024-10-18 16:11:33 +13:00
wixoa
46143d2589 Separate window creation in OSWindow.Show() to allow creation in the background (#5489)
* OSWindow rework
OSWindow now created ClydeWindow and WindowRoot immediately, but non-visible in the background
Also added the ability to programatically resize an open window

* Implement window resizing on SDL2

* Revert OSWindow changes

* Split `Show()` into `Create()` and `Show()`

* Formatting
2024-10-17 17:21:38 +02:00
MilenVolf
ba7d1452c1 Add Erase button for TileSpawnWindow (#5488)
* Add Erase button for TileSpawnWindow

Small QoL for mappers. Basically, it just selects space tile on "Erase" button toggled.

* Remove copy paste. Conevrt this into method
2024-10-13 15:55:57 +02:00
Pieter-Jan Briers
1c1343466e Improve docs for IConsoleShell.Player
Just realized this relation of "no player = server console" is not clearly documented.
2024-10-11 15:14:38 +02:00
Pieter-Jan Briers
0d534e8bcd Allow watchdog to specify more information about why the server should restart.
Had a plan to use this, but realized for what I'm doing immediately I don't quite need it yet.

/update server endpoint can now receive a Reason code and Message field. These are available with IWatchdogApi.RestartRequested.

Cleaned up IWatchdogApi: Added comments, moved symbols that should only be called by the engine to an internal interface. Also cleaned up some code in WatchdogApi to remove some IDE warnings.
2024-10-11 00:26:37 +02:00
Pieter-Jan Briers
c83c6f9592 Fix RobustSerializer breaking for non-seekable streams.
Shows up in replay loading from zip files, as the stream may be compressed. The statistics code in RobustSerializer assumes the stream is always seekable (by accessing .Position).

Now we don't run the statistics logic when reading/writing non-seekable-streams.
2024-10-10 04:31:37 +02:00
Pieter-Jan Briers
c794bd84bf Replay load: remove unnecessary bufferSize parameter.
This usage really doesn't make sense, and it makes the usage invalid if the size is zero. Now realistically I don't think this happens except in edge-case replay files, but it's still silly.

Removed if for no other reason than spite for making me look at this code and reason about it.
2024-10-10 04:06:39 +02:00
Pieter-Jan Briers
d1d43f834b Version: 236.1.0 2024-10-08 22:59:51 +02:00
Pieter-Jan Briers
9505cb68df Add SwitchExpressionException to sandbox
Fixes #5450
2024-10-07 19:05:17 +02:00
Ed
9763f5fdf4 filter entities (#5473) 2024-10-07 18:48:39 +02:00
Mervill
80a963ec05 Replace obsolete functions in MapSystem (#5483) 2024-10-07 18:48:09 +02:00
Mervill
3a670ec25e Replace obsolete functions in EntityLookup Test (#5482) 2024-10-07 18:34:22 +02:00
mhamster
e45950a557 Update BaseServer.cs (#5487)
+ Server now gives a proper reason of shutdown when shutting down before main loop has been started
2024-10-07 14:06:49 +02:00
eoineoineoin
6f1427ef3c Interface to remove a controls child by index (#5485)
Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
2024-10-06 22:51:53 +02:00
Mervill
9a7d1a39c1 Replace obsolete functions in GridFixtureSystem (#5484) 2024-10-06 02:03:01 +02:00
Mervill
9be903ee56 Replace obsolete functions in GridRotation Tests (#5481) 2024-10-05 13:21:26 +10:00
Mervill
72f9f9c343 Trim unused method variables (#5480) 2024-10-05 13:19:21 +10:00
metalgearsloth
3ad760a99e Add another lookup overload (#5477) 2024-10-03 18:56:44 +02:00
metalgearsloth
e2f3722ce9 Set sprite flicks immediately (#5467)
So on content we have an issue where the animation is played in doorsystem but sprite visibility is controlled by airlocksystem. The issue then is that we get a single frame where the incorrect sprite is shown before it corrects itself. The easiest way to reproduce this is to walk into a door that denies you and observe it shows the incorrect sprite then flickers to the denied one.

There might be more systems with these issues which is why I did this here instead.
2024-10-03 18:56:06 +02:00
Vasilis
b4beca6562 Expose GameTitle, WindowIconSet and SplashLogo to content (#5475)
* Expose GameTitle to public

Requirement for upstream ss14 pr

* Missing method implemented

* Add windowiconset and splashlogo (I think this is what pjb meant?)

I don't think its worth it to add the other stuff (modules, assemblyprefix, autocnnect, clientassemblies)

* Docs
2024-10-01 12:05:44 +02:00
Pieter-Jan Briers
ea02260230 Add LineEdit.SelectAllOnFocus 2024-10-01 01:07:08 +02:00
Leon Friedrich
3b243e487d Add required keyword attributes to sandbox whitelist (#5474)
* Add `required` keyword attributes to sandbox

* Release notes
2024-09-30 16:00:24 +02:00
Pieter-Jan Briers
f40ccb7558 New HWID system prep (#5446)
* New HWID system prep

* Allow HWID to be disabled.

Both client and server can now request HWID to be disabled.

On the server via CVar, if disabled the client won't send it.

On the client via env var, if disabled it won't be sent to the client.

This involved moving legacy HWID to be sent in MsgEncryptionResponse instead of MsgLoginStart. This means the legacy HWID won't be available anymore if the connection isn't authenticated.

* Fix tests

* Fix another test

* Review

* Thanks Rider
2024-09-29 00:29:02 +02:00
Stalen
f467a7027b Added MuteSounds property for BaseButton control (#5465) 2024-09-29 00:25:56 +02:00
eoineoineoin
c9d7d442d9 Make IPlayerManager accessible to derived classes (#5471)
Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
2024-09-28 23:48:07 +02:00
eoineoineoin
342626ad9b Account for scale when calculating sprite offset (#5470)
Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
2024-09-28 17:31:24 +02:00
metalgearsloth
1c3ea968e4 Version: 236.0.0 2024-09-28 19:16:35 +10:00
Pieter-Jan Briers
f0ed3537ee Duplicate dependency field analyzer (#5463)
* Duplicate dependency field analyzer

Detects cases of duplicate [Dependency] fields in a type. We apparently have 27 of these across RT + SS14.

* Fix duplicate dependencies in Robust
2024-09-28 15:35:18 +10:00
metalgearsloth
74e7e61a98 Revert "Make resetting contacts on the client only set is touching if it is true" (#5469)
This reverts commit cdb94748c8.
2024-09-28 14:33:37 +10:00
metalgearsloth
fb9b0ae89b Remove IsTouching set on physics prediction (#5468)
Just because an entity sleeps doesn't mean it's not touching necessarily. This causes client to mispredict against server and continuously fire collision events if we try to move into an entity.

Easiest way to reproduced is to walk into a locked airlock and watch it flicker constantly.
2024-09-28 14:13:05 +10:00
Stalen
dbe297b1fc Activate XAML hot reload on file rename (for VS support) (#5429) 2024-09-24 09:43:00 +10:00
Leon Friedrich
b84917e8e4 Obsolete some static localization methods (#5460) 2024-09-24 09:40:42 +10:00
Leon Friedrich
abb3f65fe4 Make EnsureEntityDictionary use TryAdd (#5461) 2024-09-24 09:40:16 +10:00
Leon Friedrich
41ec2dc131 Try improve PVS exception tolerance a bit more (#5454) 2024-09-24 09:39:33 +10:00
eoineoineoin
e714dcc83c Fix TabContainer click detection when UIScale was not == 1.0 (#5456)
* Fix tabcontainer click detection when UIScale was not == 1.0

* Remove whitespace

---------

Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
2024-09-22 14:41:27 +02:00
ShadowCommander
46291af1be Add ProtoId parser to Toolshed (#5220)
* Add ProtoId parser to Toolshed

* Change obsolete FromMarkup to FromMarkupOrThrow
2024-09-21 21:57:08 +10:00
Leon Friedrich
ad929c9955 Fix ICommonSession.Ping (#5453) 2024-09-20 16:43:12 +02:00
metalgearsloth
c86cb0b795 Version: 235.0.0 2024-09-18 12:13:35 +10:00
metalgearsloth
8d03feb84f Transform precision thing (#5451)
Just noticed it but probably doesn't affect anything really, we'll go from 64bit to 32bit after the math operations and not before.
2024-09-18 12:08:36 +10:00
Plykiya
0fa21ee2d2 Completely obsolete noSpawn (#5364) 2024-09-18 11:48:13 +10:00
eoineoineoin
9be0f032e8 Fix DistanceJoints drawn by physics debug system (#5439)
Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
2024-09-18 11:45:01 +10:00
Leon Friedrich
afffb33446 Stop empty audio system filters from playing sounds for all players (#5444)
* Fix audio system empty filter bug

* The nullable attributes are lying
2024-09-18 11:44:16 +10:00
Leon Friedrich
19a87fb67a Remove incorrect NotNullIfNotNull attributes in SharedAudioSystem (#5449) 2024-09-18 11:43:49 +10:00
DrSmugleaf
2fda62a274 Fix physics.maxlinvelocity not being a replicated cvar (#5445) 2024-09-17 12:51:54 +10:00
ike709
5218bf70b0 Bump cefglue (#5441)
* Bump cefglue

* Another bump

* Third time's the charm

---------

Co-authored-by: ike709 <ike709@github.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-09-16 22:31:10 +02:00
Pieter-Jan Briers
4f95c07ab3 Add missing Roslyn components to solution 2024-09-16 21:34:59 +02:00
Lgibb18
786acae47a Fix tags with controls in RichText and OutputPanel (#5428)
* Controls in RichText fixes

* useless

* Get FormattedMessage from RichTextLabel

* dont go through nodes

* Comments and minor changes

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2024-09-16 14:13:59 +10:00
Leon Friedrich
f81e30a031 Try fix invalid PVS index bug (#5422)
* Try fix invalid PVS index bug

* bounds check

* More Asserts

* fix assert?

* remove deletion

* a

* A!
2024-09-16 14:12:20 +10:00
Leon Friedrich
f5c1d870f9 Improve FlushEntities() error logs (#5427)
* Improve FlushEntities() error logs

* log count before flush
2024-09-16 14:06:04 +10:00
Leon Friedrich
4949b34c88 Fix "to" and "take" toolshed commands (#5438) 2024-09-13 22:34:30 +10:00
metalgearsloth
0f60ad9018 Version: 234.1.0 2024-09-12 17:56:50 +10:00
metalgearsloth
f7287b181d Fix audioparams for playglobal (#5437) 2024-09-12 17:48:37 +10:00
Fildrance
45b7500d93 feat: added audio system predicted method for only one receiver (#5435)
* feat: added audio system predicted method for only one receiver

* renamed to PlayLocal

* tweak

---------

Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-09-12 17:41:40 +10:00
metalgearsloth
dbe6f65880 Version: 234.0.0 2024-09-12 13:56:27 +10:00
metalgearsloth
4faef1bfd3 Add another lookup override (#5436) 2024-09-12 13:53:36 +10:00
Kara
48d70a09c6 Remove most fully-obsoleted code (#5433) 2024-09-11 19:38:26 +10:00
Pieter-Jan Briers
f682fb9cc7 Obsolete some useless type proxies on IResourceCache
These aren't even used, but they're pretty objectively bad ideas so let's obsolete them so we can get rid of them later.
2024-09-09 11:22:07 +02:00
Pieter-Jan Briers
814e5bcf17 Mark large replays as requiring Server GC.
This should significantly improve loading performance of large replays.

System can be controlled by replay.server_gc_size_threshold, which defaults to 50 MiB.

This is the engine-side of https://github.com/space-wizards/SS14.Launcher/issues/177
2024-09-09 08:23:20 +02:00
metalgearsloth
dbc4e80e61 Version: 233.1.0 2024-09-08 17:55:56 +10:00
metalgearsloth
5eb5ddd96e Add some entitylookup methods (#5431) 2024-09-08 17:22:48 +10:00
Leon Friedrich
405ed378c0 Re-attempt FlushEntities() on failure (#5423) 2024-09-06 20:51:34 +10:00
Leon Friedrich
be9db264dd Minor toolshed fixes / tweaks (#5315)
* Don't use markup for type names

* Cache TypeTypeParser completions

* Cache all type parsers

* Release notes

* More IConError fixes

* a
2024-09-06 10:48:50 +10:00
Pieter-Jan Briers
2f73f6190d Fix warning in ScriptGlobalsShared.cs 2024-09-04 21:31:37 +02:00
metalgearsloth
f3dfa1f666 Move testbed command to benchmarks (#5424) 2024-09-03 22:02:00 +10:00
Pieter-Jan Briers
b0d17e9527 Fix dead code equals method in Polygon
Fixes #5420

Sloth clarified it's dead code from copy pasting.
2024-09-02 13:17:02 +02:00
Pieter-Jan Briers
4c81e68bf1 Remove last FormattedMessage.FromMarkup calls 2024-09-02 07:36:34 +02:00
Pieter-Jan Briers
4490751001 Fix warnings in FormattedMessageSerializerTest.cs 2024-09-02 07:33:26 +02:00
Pieter-Jan Briers
bc8d2c154c Fix warnings in EntityManager_Components_Tests.cs 2024-09-02 07:32:47 +02:00
Pieter-Jan Briers
3c83f8e62a Make Rider not complain about Is. in Robust.UnitTesting, globally. 2024-09-02 07:27:23 +02:00
Pieter-Jan Briers
c36919d76a Fix warnings in ArithmeticTest.cs 2024-09-02 05:45:08 +02:00
Pieter-Jan Briers
70a853cdd5 Fix most warnings in AnchoredSystemTests.cs
Remaining warnings are cases where AnchorEntity, SetWorldPosition and SetLocalPosition subtly different from their component counterparts, and this triggers a test failure. Some help would be appreciated here.
2024-09-02 05:38:40 +02:00
Pieter-Jan Briers
fd3eb092cc EntityUid-only overloads for some TransformSystem methods
AnchorEntity and Unanchor
2024-09-02 05:37:49 +02:00
Pieter-Jan Briers
c740026014 Entity<T> overloads for some MapSystem methods.
GetTileRef, TileIndicesFor, and GetAnchoredEntities
2024-09-02 05:36:11 +02:00
Pieter-Jan Briers
44f9262d1a SimulationExtensions helpers for RobustServerSimulation
No more need to manually resolve IEntityManager in every test.
2024-09-02 05:30:10 +02:00
Pieter-Jan Briers
df2160b151 Fix warnings in Broadphase_Test.cs 2024-09-02 04:10:54 +02:00
Pieter-Jan Briers
5c7b1e6823 Fix warnings in ToolshedTest.cs 2024-09-02 04:01:24 +02:00
Pieter-Jan Briers
eaf7a6ba0f Fix warnings in ToolshedTypesTest.cs 2024-09-02 03:59:55 +02:00
Pieter-Jan Briers
9ab4286592 Wait why is that even returning a task in the first place 2024-09-02 03:59:25 +02:00
Pieter-Jan Briers
3f02ef3730 Fix warnings in PvsSystemTests.cs 2024-09-02 03:57:54 +02:00
Pieter-Jan Briers
2f17cbb1dc Fix warnings in ToolshedTypesTest.BugCheck.cs 2024-09-02 03:57:09 +02:00
Pieter-Jan Briers
c2657812f5 Fix warnings in GridTraversalTest.cs 2024-09-02 03:56:42 +02:00
Pieter-Jan Briers
f17f077849 Fix warnings in FormattedMessage_Test.cs 2024-09-02 03:55:14 +02:00
Pieter-Jan Briers
306deddbd2 Fix warnings in TransformComponent_Tests.cs 2024-09-02 03:54:52 +02:00
Pieter-Jan Briers
cdd8df743a Fix warnings in Transform_Test.cs 2024-09-02 03:49:06 +02:00
Pieter-Jan Briers
e7ac5ad047 Fix warnings in UserInterfaceManagerTest.cs 2024-09-02 03:45:46 +02:00
Pieter-Jan Briers
0e621a26be Fix warnings in ControlTest.cs 2024-09-02 03:45:46 +02:00
Pieter-Jan Briers
bbcc7cfe1f Fix warnings in GameLoop_Test.cs 2024-09-02 03:45:46 +02:00
Mervill
1208c25dcd resolve instances of the CS8974 warning (#5418) 2024-09-02 03:29:10 +02:00
Pieter-Jan Briers
38c227b692 Fix MarkupNode equality
Implement GetHashCode()

Fix Equals doing reference equality on the attributes.

Small code cleanup.

Actually mark it as IEquatable<MarkupNode> because we already implement Equals() so there's no reason not to.
2024-09-02 03:27:25 +02:00
Mervill
4e73d72753 Remove unused IoC dependencies (#5419) 2024-09-01 23:23:02 +02:00
Pieter-Jan Briers
b1e1a0cd88 Quick warning fixes (#5417) 2024-09-01 04:54:28 +02:00
metalgearsloth
6e25ead588 Version: 233.0.2 2024-08-31 18:39:01 +10:00
metalgearsloth
cfae6e1f95 Don't rely on client for grid fixture rebuilds (#5348)
* Don't rely on client for grid fixture rebuilds

Server is already networking fixture data and this has a chance to go bad.
Easier to just stop this entirely and remove the fixture references to just network the relevant ones for each chunk. Performance impact should pretty much be non-existent and it should be less buggy.

* a

* weh notes

* fix aabb update

* Fix AABB gen

* weh

* More networking
2024-08-31 18:37:43 +10:00
metalgearsloth
da56851846 Version: 233.0.1 2024-08-31 18:22:39 +10:00
metalgearsloth
69ed2c3c33 Fix IsHardCollidable (#5416) 2024-08-31 17:49:59 +10:00
metalgearsloth
c558a0327b Version: 233.0.0 2024-08-31 14:37:23 +10:00
metalgearsloth
3bb7df3254 Relative lookup fix (#5415)
* Relative lookup fix

Some of the transforms weren't being transformed, added another test.

* test

* better test

* Reduce any-entities-intersecting tests
2024-08-31 14:35:17 +10:00
metalgearsloth
ab6bd19817 Fix mouse hover not updating for new controls (#5313)
* Fix mouse hover not updating for new controls

It only ever updated on movement previously. The issue is for example if a new window shows up where the mouse is (or any control) it doesn't handle it. Just checking it every frame AFAIK shouldn't be that expensive. Worst case we just have some flag to check it that gets set on <mouse movement OR controls changed>.

* review
2024-08-31 11:45:59 +10:00
Leon Friedrich
73ef69aa94 Try and ensure that parents are always initialized before children (#5343)
* Try and ensure that parents are always initialized before children

* release notes
2024-08-31 11:05:56 +10:00
nikthechampiongr
656835e7fa Change EntityRenamedEvents arguments and make it broadcast (#5413) 2024-08-31 11:04:44 +10:00
Pieter-Jan Briers
26c87b5858 Make tests run parallelizable (#5412)
Hope this won't cause issues.

Massively improves test speed.
2024-08-31 11:04:21 +10:00
Pieter-Jan Briers
be36001ab8 Add thread check assert to core entity mutation commands. (#5411)
* Add thread check assert to core entity mutation commands.

Creation of entities and components is now checked to happen from the main thread.

This is already catching multiple buggy integration tests, these will be addressed in separate commits.

* Fix broken tests directly mutating entities from wrong thread.
2024-08-31 11:04:02 +10:00
Pieter-Jan Briers
2002402af8 Version script now supports dash versions 2024-08-29 12:52:52 +02:00
metalgearsloth
f659b2b58c Version: 232.0.0 2024-08-29 12:55:51 +10:00
metalgearsloth
b1e13f5b13 Fix BUI interfaces not deep copying (#5410)
* Fix BUI interfaces not deep copying

Didn't think I'd need to on a getstate but client state moment.

* less shitcodey
2024-08-29 12:47:32 +10:00
SlamBamActionman
e5995d4edc Add ObjectSerializer, AppearanceComponent.AppearanceDataInit, and AppearanceSystem.AppendData (#5324)
* V1 commit

* V2 Commit, ObjectSerializer

* Make sure write for objects have the !type:<T> set

* Added AppearanceDataInit

* Change to AppearanceDataInit setting to AppearanceData the moment it itself gets set; ComponentInit is too late. Forgive me sloth.

* RELEASE-NOTES.md

* Fix release notes

* Fix release-notes for realsies
2024-08-28 22:43:58 +10:00
Winkarst
6eb080a277 Add Robust.Xaml.csproj to the solution (#5408) 2024-08-28 13:49:42 +02:00
metalgearsloth
b0cb41e94a Version: 231.1.1 2024-08-28 12:23:04 +10:00
Leon Friedrich
23a23f7c22 Misc toolshed fixes (#5340)
* Prevent map/emplace command errors from locking up the server

* Fix EmplaceCommand

* Fix sort commands

* Fix JoinCommand

* changelog

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-08-28 12:22:47 +10:00
Pieter-Jan Briers
ec3a74d268 Version: 231.1.0 2024-08-27 17:47:25 +02:00
Pieter-Jan Briers
12b0bc4e0a Add way for content to write arbitrary files into replay. (#5405)
Added a new RecordingStopped2 event that receives a IReplayFileWriter object that can be used to write arbitrary files into the replay zip file.

Fixes #5261
2024-08-27 17:38:48 +02:00
metalgearsloth
903041dfd1 Add storage BUI bandaid (#5401) 2024-08-27 17:36:54 +02:00
metalgearsloth
b96419f0b2 Add mapmanager query tests (#5403)
Sanity
2024-08-28 00:24:24 +10:00
metalgearsloth
fe33ad2652 Add physicshull tests (#5404) 2024-08-27 23:24:23 +10:00
metalgearsloth
057a68b366 Minor allocs reductions (#5330)
* Minor allocs reductions

Added a poly struct with the intention of replacing the existing one whenever I finish box2c port.

* fix merges

* Revert some stuff

* Poly tests
2024-08-27 22:58:42 +10:00
metalgearsloth
1a2c9008fe Add Box matrix tests (#5402)
Thought we had but apparently not.
2024-08-27 22:21:48 +10:00
metalgearsloth
cd95929ebe Heavily optimise entitylookup (#5400)
* Heavily optimise entitylookup

Previously I made it so MOST of entitylookup goes through the same 4 or 5 codepaths and uses shapes. The downside was some codepaths were made much slower in the process.

This fixes most of the going up and down lookups that some codepaths did. It should also be faster than the pre-shapes version because GetLocalPhysicsTransform is being used for the non-approx queries and most entities are parented directly to their broadphase.

* Tests and confidence

* code

* dang
2024-08-27 21:32:50 +10:00
Nyeogmi
6396ec472d XAML hot reloading (#5350)
* Move RobustXaml to a shared package

In a near-future change, I'll make it possible to optionally link to
this from Robust.Client, which will allow JIT compiling XAML.

Also upgrade it to a version of .NET that supports nullability
annotations.

* Re-namespace packages

* Add a JIT compiler, plus hooks that call into it

In Debug, after this change, all XAML will be hot reloaded once every
time an assembly is reloaded.

The new code is compiled with SRE and is _not_ sandboxed -- this is not
suitable to run against prod.

In Release, the hot reload path is totally skipped, using the same trick
as SmugLeaf used in an earlier attempt to implement this functionality.

* Hot reload: watcher

This is a bit of a horror, but there's not in-engine support for
identifying the source tree or the XAML files in it.

* Put everything dangerous behind conditional comp

* Code cleanup, docs

* Fix a bad comment

* Deal a little better with crashes in the watcher

* Make reload failures Info, since they're expected

They were previously causing the integration tests to flag, even though
"a few types fail hot reloading because they're internal" is expected
behavior.

* Fix an unnecessary null check

I removed the ability for CompileCore to return null.

* injectors: null! strings, default primitives

* Tidy documentation (thanks, PJB!)

* Reinstate netstandard2.0, abolish Pidgin

* Internal-ize all of Robust.Xaml

* Add a cautionary note to Sandbox.yml

* Shuffle around where conditional compilation occurs

* Privatize fields in XamlImplementationStorage

* Internalize XamlJitDelegate

* Inline some remarks. No cond. comp in Robust.Xaml

* Use file-scoped namespaces

They aren't allowed at Language Level 8.0. (which I arbitrarily picked
for Robust.Xaml because it's the oldest one that would work)

* Bump language level for R.Xaml, file namespaces

* Force hot reloading off for integration tests

* Fix bizarre comment/behavior in XamlImplementationStorage

* Consistently use interfaces, even in generated code

* Update Robust.Client/ClientIoC.cs

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2024-08-27 02:16:57 +02:00
Stalen
d7aa5daf6a Add decimal type to sandbox whitelist (#5396) 2024-08-27 01:35:42 +02:00
metalgearsloth
e3819f8245 Network interfacedata (#5399)
If UIs are dynamically changed this fixes it.
2024-08-26 18:48:15 +10:00
metalgearsloth
57f133b742 Version: 231.0.1 2024-08-26 14:41:40 +10:00
metalgearsloth
04344ffe19 Make PVS exception log better (#5397) 2024-08-26 14:31:05 +10:00
metalgearsloth
f2ee9a43f9 Version: 231.0.0 2024-08-25 22:48:51 +10:00
metalgearsloth
8d5ebd830a Add CompRegistry methods to EntManager / CompFac (#5379)
* Add CompRegistry methods to EntManager / CompFac

CompRegistries are nice to use and this just makes it a bit easier to extend functionality.

* fix bad pull
2024-08-25 21:37:14 +10:00
metalgearsloth
4d265b2210 Add methods to get entity sprite position (#5381)
* Add methods to get entity sprite position

No easy way to get this in world-terms and I want a control to track it.

* invalid
2024-08-25 21:24:37 +10:00
metalgearsloth
e04caf7eb4 Add OpenScreenAt for windows (#5387)
If I want to open it at a particular position. Takes in clyde ref so not every single screen needs to keep the ref.
2024-08-25 20:32:28 +10:00
metalgearsloth
a4c54d3602 Make pointlight setting use an attempt event (#5378)
Makes it easy for content to add functionality if multiple things try to set it without having to funnel every piece of code through a content system.
2024-08-25 20:27:58 +10:00
metalgearsloth
43fd6bc764 Add FixturesChangeComponent (#5383)
* Add FixturesChangeComponent

Adds / removes fixtures. Useful when used in conjunction with EntProtoId to dynamically add / remove fixtures.

* Move to system

* Fix allcomps test
2024-08-25 20:25:19 +10:00
metalgearsloth
ff056552fe Don't spam BUI closing in state handling (#5382)
We just do what content does and defer it until update. Saves performance + we don't have some BUIs that do special logic on open re-running it constantly (e.g. open on mouse position).
2024-08-25 20:08:19 +10:00
metalgearsloth
3014d9880e Fix prototype flag add not actually working (#5376)
tryindex was inverted and causing issues with tools.
2024-08-25 20:03:09 +10:00
metalgearsloth
679c31199d Add comments to sermanager attributes (#5384)
I always forget which is which.
2024-08-25 20:02:31 +10:00
metalgearsloth
0bc3c51707 Contact QOL stuff (#5385)
* Contact QOL stuff

Only just made the enumerator version need to test with AI branch had an IEnumerable before.

* Fix
2024-08-25 11:54:05 +10:00
metalgearsloth
140767c262 Move viewsubscriber to shared + handle eye targets (#5362)
* Move viewsubscriber to shared + handle eye targets

Need this stuff for AI.

* Fix dumb

* review
2024-08-23 18:29:57 +10:00
metalgearsloth
36a5b672e5 Version: 230.2.0 2024-08-23 14:36:00 +10:00
metalgearsloth
1eb63cb616 Stop inheriting IThreadPoolWorkitem (#5377)
More annoying with internal changes.
2024-08-23 14:33:36 +10:00
metalgearsloth
c14689f233 Add more directions for Vector2i (#5386)
Convenient.
2024-08-23 14:33:08 +10:00
Pieter-Jan Briers
f03c006129 Version: 230.1.0 2024-08-22 01:49:43 +02:00
qwerltaz
0d53c5e329 add bool[] support to shaders (#5373)
* Add bool array support to shaders

* better setUniform

* less unsafe

* stackalloc, less pointer
2024-08-21 22:36:03 +02:00
metalgearsloth
5cb1901870 Fix local tile enlargement (#5349)
* Fix local tile enlargement

Not used.

* release notes

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-08-21 02:52:41 +02:00
Pieter-Jan Briers
d46885b96d Fix LocalizedEntityCommands breaking content unit tests. (#5375)
They are now not loaded inside content unit tests.

Fixes #5374
2024-08-21 02:43:46 +02:00
DrSmugleaf
0553600c9a Add cvar to limit entities passed into nearby command (#5355)
* Add cvar to limit entities passed into nearby command

* Fix double enumeration
2024-08-21 02:43:19 +02:00
DrSmugleaf
580dd5f1a6 Fix RichTextLabel.Text property not setting _message if null (#5361) 2024-08-21 02:40:50 +02:00
metalgearsloth
d47d488ce7 Broadphase init fixes (#5367)
1 for replays 1 for loadmap.
The replay one is kinda sussy but physicamap is supposed to get dumped at some point so.
2024-08-21 02:39:23 +02:00
Pieter-Jan Briers
d584e51de6 Revert LocalizedCommands Loc change.
This is what I get for ragecoding at 1 AM.
2024-08-21 02:03:16 +02:00
Pieter-Jan Briers
2f85b94ea2 Use new LocalizedCommands.Loc property in engine
Also made BaseReplayCommand.Loc an explicit hide of the base property.
2024-08-21 01:23:25 +02:00
Pieter-Jan Briers
046f7a2e55 Add "Loc" property to LocalizedCommands
Intended to match the localization manager in EntitySystem, much more convenient than the existing one.
2024-08-21 01:16:43 +02:00
c4llv07e
5f2881e3e4 Add completion support for the change state command (#5368) 2024-08-20 22:38:28 +02:00
DrSmugleaf
cdb94748c8 Make resetting contacts on the client only set is touching if it is true (#5372) 2024-08-20 19:35:14 +10:00
Stalen
53516d6389 Fixed client crash on devwindow inspect control's property which throws exception (#5370)
* Fixed client crash on devwindow inspect control property which throws exception

* Display inner exception only in case of TargetInvocationException
2024-08-19 00:55:04 +02:00
metalgearsloth
efa3e010a6 Add Flip method to SplitContainer (#5333) 2024-08-14 22:59:20 +02:00
faint
4b12ff8574 fix loadprototype (#5359) 2024-08-14 14:52:09 +02:00
Pieter-Jan Briers
59ed76c66f Remove obsolete usages of *Variant prototype manager functions
Replaced with *Kind
2024-08-13 11:45:40 +02:00
Tayrtahn
9781405f5e Better location reporting for DataField analyzers (#5344)
* Better location reporting for DataField analyzers

* Update test

* Use const string in both methods
2024-08-13 11:40:23 +02:00
Pieter-Jan Briers
2178707937 Version: 230.0.1 2024-08-11 19:49:23 +02:00
Pieter-Jan Briers
0284eb0430 Use absolute path for explorer.exe
frick me
2024-08-11 19:48:48 +02:00
Pieter-Jan Briers
2c3cc070a6 Fix oopsie from me using version.py on an existing version 2024-08-11 16:33:00 +02:00
Pieter-Jan Briers
6599f9565e Version: 230.0.0 2024-08-11 16:26:47 +02:00
Pieter-Jan Briers
85abcff5ea Version: 223.0.0 2024-08-11 16:26:32 +02:00
Pieter-Jan Briers
5b5894e2d5 Release notes 2024-08-11 16:26:25 +02:00
Pieter-Jan Briers
7d778248ee Security updates (#5353)
* Fix security bug in WritableDirProvider.OpenOsWindow()

Reported by @NarryG and @nyeogmi

* Sandbox updates

* Update ImageSharp again
2024-08-11 16:21:54 +02:00
DrSmugleaf
672819d525 Add missing return calls for positions and angles in SolveIsland (#5327) 2024-08-10 11:15:02 +10:00
Nemanja
99e4910440 Fix TimedDespawnComponent causing a crash if spawning another entity with TimedDespawnComponent (#5345) 2024-08-10 11:12:44 +10:00
Leon Friedrich
b503390837 Add InterpolatedStringHandlerArgumentAttribute to sandbox whitelist (#5339) 2024-08-08 19:10:23 +02:00
Repo
87725f27c3 Add a copy to clipboard button on alert popups. (#5336)
* Add a copy button to clipboard on Alert Popups.

* ButtonFlag and better formatting.

* Localization and style cleanup
2024-08-08 18:56:32 +02:00
621 changed files with 27670 additions and 17716 deletions

View File

@@ -5,30 +5,30 @@ on:
- cron: "0 0 * * 0"
jobs:
docfx:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.6.0
with:
submodules: true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 8.0.x
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore
- name: Install dependencies
run: dotnet restore
- name: Build Project
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
- name: Build Project
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
- name: Build DocFX
uses: nikeee/docfx-action@v1.0.0
with:
args: Robust.Docfx/docfx.json
- name: Build DocFX
uses: nikeee/docfx-action@v1.0.0
with:
args: Robust.Docfx/docfx.json
- name: Publish Docfx Documentation on GitHub Pages
uses: maxheld83/ghpages@master
env:
BUILD_DIR: Robust.Docfx/_robust-site
GH_PAT: ${{ secrets.GH_PAT }}
- name: Publish Docfx Documentation on GitHub Pages
uses: maxheld83/ghpages@master
env:
BUILD_DIR: Robust.Docfx/_robust-site
GH_PAT: ${{ secrets.GH_PAT }}

View File

@@ -2,33 +2,32 @@ name: Build & Test
on:
push:
branches: [ master ]
branches: [master]
pull_request:
branches: [ master ]
branches: [master]
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] - temporarily disabled due to libfreetype.dll errors.
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3.6.0
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 8.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
- name: Robust.UnitTesting
run: dotnet test --no-build Robust.UnitTesting/Robust.UnitTesting.csproj -- NUnit.ConsoleOut=0
- name: Robust.Analyzers.Tests
run: dotnet test --no-build Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj -- NUnit.ConsoleOut=0
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
- name: Robust.UnitTesting
run: dotnet test --no-build Robust.UnitTesting/Robust.UnitTesting.csproj -- NUnit.ConsoleOut=0
- name: Robust.Analyzers.Tests
run: dotnet test --no-build Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj -- NUnit.ConsoleOut=0

View File

@@ -11,14 +11,8 @@
#
name: "CodeQL"
#on:
# push:
# branches: [ master ]
# pull_request:
# # The branches below must be a subset of the branches above
# branches: [ master ]
# schedule:
# - cron: '30 18 * * 6'
on:
workflow_dispatch
jobs:
analyze:
@@ -28,50 +22,50 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'csharp' ]
language: ["csharp"]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v3.6.0
with:
submodules: true
- name: Checkout repository
uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 7.0.x
- name: Build
run: dotnet build
- name: Build
run: dotnet build
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -3,51 +3,50 @@
on:
push:
tags:
- 'v*'
- "v*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Parse version
id: parse_version
shell: pwsh
run: |
$ver = [regex]::Match($env:GITHUB_REF, "refs/tags/v?(.+)").Groups[1].Value
echo ("::set-output name=version::{0}" -f $ver)
- name: Parse version
id: parse_version
shell: pwsh
run: |
$ver = [regex]::Match($env:GITHUB_REF, "refs/tags/v?(.+)").Groups[1].Value
echo ("::set-output name=version::{0}" -f $ver)
- uses: actions/checkout@v3.6.0
with:
submodules: true
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 8.0.x
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
- name: Package client
run: Tools/package_client_build.py -p windows mac linux
- name: Package client
run: Tools/package_client_build.py -p windows mac linux
- name: Shuffle files around
run: |
mkdir "release/${{ steps.parse_version.outputs.version }}"
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
- name: Shuffle files around
run: |
mkdir "release/${{ steps.parse_version.outputs.version }}"
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
- name: Upload files to Suns
uses: appleboy/scp-action@master
with:
host: suns.spacestation14.com
username: robust-build-push
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
source: "release/${{ steps.parse_version.outputs.version }}"
target: "/var/lib/robust-builds/builds/"
strip_components: 1
- name: Update manifest JSON
uses: appleboy/ssh-action@master
with:
host: suns.spacestation14.com
username: robust-build-push
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }}
- name: Upload files to Suns
uses: appleboy/scp-action@master
with:
host: suns.spacestation14.com
username: robust-build-push
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
source: "release/${{ steps.parse_version.outputs.version }}"
target: "/var/lib/robust-builds/builds/"
strip_components: 1
- name: Update manifest JSON
uses: appleboy/ssh-action@master
with:
host: suns.spacestation14.com
username: robust-build-push
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }}

View File

@@ -2,40 +2,39 @@ name: Test content master against engine
on:
push:
branches: [ master ]
branches: [master]
pull_request:
branches: [ master ]
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out content
uses: actions/checkout@v3.6.0
with:
repository: space-wizards/space-station-14
submodules: recursive
- name: Check out content
uses: actions/checkout@v4.2.2
with:
repository: space-wizards/space-station-14
submodules: recursive
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 8.0.x
- name: Disable submodule autoupdate
run: touch BuildChecker/DISABLE_SUBMODULE_AUTOUPDATE
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
- name: Disable submodule autoupdate
run: touch BuildChecker/DISABLE_SUBMODULE_AUTOUPDATE
- name: Check out engine version
run: |
cd RobustToolbox
git fetch origin ${{ github.sha }}
git checkout FETCH_HEAD
git submodule update --init --recursive
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Tools --no-restore
- name: Content.Tests
run: dotnet test --no-build Content.Tests/Content.Tests.csproj -v n
- name: Content.IntegrationTests
run: COMPlus_gcServer=1 dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
- name: Check out engine version
run: |
cd RobustToolbox
git fetch origin ${{ github.sha }}
git checkout FETCH_HEAD
git submodule update --init --recursive
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Tools --no-restore
- name: Content.Tests
run: dotnet test --no-build Content.Tests/Content.Tests.csproj -v n
- name: Content.IntegrationTests
run: COMPlus_gcServer=1 dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n

View File

@@ -10,66 +10,69 @@
<ManagePackageVersionsCentrally />
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="ILReader.Core" Version="1.0.0.4" />
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageVersion Include="JetBrains.Profiler.Api" Version="1.4.0" />
<PackageVersion Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageVersion Include="JetBrains.Profiler.Api" Version="1.4.8" />
<PackageVersion Include="Linguini.Bundle" Version="0.8.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit" Version="1.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit" Version="1.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeCoverage" Version="17.8.0" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="8.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing" Version="1.1.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" Version="1.1.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeCoverage" Version="17.12.0" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="9.0.0" />
<PackageVersion Include="Microsoft.DotNet.RemoteExecutor" Version="8.0.0-beta.24059.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Primitives" Version="8.0.0" />
<PackageVersion Include="Microsoft.ILVerification" Version="8.0.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
<PackageVersion Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Primitives" Version="9.0.0" />
<PackageVersion Include="Microsoft.ILVerification" Version="9.0.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageVersion Include="Microsoft.NET.ILLink.Tasks" Version="9.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageVersion Include="Moq" Version="4.20.70" />
<PackageVersion Include="NUnit" Version="4.0.1" />
<PackageVersion Include="NUnit.Analyzers" Version="3.10.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="NUnit" Version="4.3.2" />
<PackageVersion Include="NUnit.Analyzers" Version="4.5.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageVersion Include="Nett" Version="0.15.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2" />
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
<PackageVersion Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
<PackageVersion Include="Pidgin" Version="3.2.2" />
<PackageVersion Include="Pidgin" Version="3.3.0" />
<PackageVersion Include="Robust.Natives" Version="0.1.1" />
<PackageVersion Include="Robust.Natives.Cef" Version="120.1.9" />
<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.7" />
<PackageVersion Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.7" />
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
<PackageVersion Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.10" />
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.4" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.6" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageVersion Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageVersion Include="System.Memory" Version="4.5.5" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.22621.5" />
<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" />
<PackageVersion Include="YamlDotNet" Version="13.7.1" />
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="prometheus-net" Version="8.2.1" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
<PackageVersion Include="PolySharp" Version="1.14.1" />
<PackageVersion Include="PolySharp" Version="1.15.0" />
<!-- Transitive deps that we need to pin versions for to avoid NuGet warnings. -->
<PackageVersion Include="System.Formats.Asn1" Version="9.0.0" />
<PackageVersion Include="System.Reflection.Metadata" Version="9.0.0" />
<PackageVersion Include="System.Text.Json" Version="9.0.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.12.0" />
</ItemGroup>
</Project>

View File

@@ -12,7 +12,7 @@
<SkipRobustAnalyzer>true</SkipRobustAnalyzer>
<Nullable>enable</Nullable>
<LangVersion>12.0</LangVersion>
<LangVersion>13.0</LangVersion>
</PropertyGroup>
<ItemGroup>

View File

@@ -1,4 +1,4 @@
<Project>
<!-- This file automatically reset by Tools/version.py -->
<!-- This file automatically reset by Tools/version.py -->

View File

@@ -1,8 +1,8 @@
<Project>
<!-- Engine-specific properties. Content should not use this file. -->
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12</LangVersion>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>13</LangVersion>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
</PropertyGroup>

View File

@@ -3,7 +3,7 @@
<!-- Import this at the end of any project files in Robust and Content. -->
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>

View File

@@ -1,4 +1,4 @@
# Release notes for RobustToolbox.
# Release notes for RobustToolbox.
<!--
NOTE: automatically updated sometimes by version.py.
@@ -54,6 +54,521 @@ END TEMPLATE-->
*None yet*
## 240.1.3-source-gen-debug
## 240.1.2-source-gen-debug
## 240.1.2
## 240.1.1
### Bugfixes
* Fixed one of the `IOverlayManager.RemoveOverlay` overrides not fully removing the overlay.
## 240.1.0
### New features
* Added an `AsNullable` extension method for converting an `Entity<T>` into an `Entity<T?>`
### Bugfixes
* Fixed an exception in `PhysicsSystem.DestroyContacts()` that could result in entities getting stuck with broken physics.
### Other
* `GamePrototypeLoadManager` will now send all uploaded prototypes to connecting players in a single `GamePrototypeLoadMessage`, as opposed to one message per upload.
## 240.0.1
### Bugfixes
* Fixed `SharedBroadphaseSystem.GetBroadphases()` not returning the map itself, which was causing physics to not work properly off-grid.
## 240.0.0
### Breaking changes
* `ComponentRegistry` no longer implements `ISerializationContext`
* Tickrate values are now `ushort`, allowing them to go up to 65535.
### New features
* Console completion options now have new flags for preventing suggestions from being escaped or quoted.
* Added `ILocalizationManager.HasCulture()`.
* Static `EntProtoId<T>` fields are now validated to exist.
### Bugfixes
* Fixed a state handling bug in replays, which was causing exceptions to be thrown when applying delta states.
### Other
* Reduced amount of `DynamicMethod`s used by serialization system. This should improve performance somewhat.
### Internal
* Avoided sorting overlays every render frame.
* Various clean up to grid fixture code/adding asserts.
## 239.0.1
### Bugfixes
* Fix logging of received packets with `net.packet` logging level.
* Downgrade `VorbisPizza` to fix audio playback for systems without AVX2 support.
### Other
* Improved performance of some Roslyn analyzers and source generators, which should significantly improve compile times and IDE performance.
## 239.0.0
### Breaking changes
* Robust now uses **.NET 9**.
* `ISerializationManager` will now log errors if it encounters `Entity<T>` data-fields.
* To be clear, this has never been supported and is not really a breaking change, but this will likely require manual intervention to prevent tests from failing.
* `IClyde.TextInputSetRect`, `TextInputStart` and `TextInputStop` have been moved to be on `IClydeWindow`.
* Updated various NuGet dependencies and removed some other ones, of note:
* `FastAccessors`, which is a transitive dep we never used, is now gone. It might have snuck into some `using` statement thanks to your IDE, and those will now fail to compile. Remove them.
* NUnit `Is.EqualTo(default)` seems to have ambiguous overload resolution in some cases now, this can be fixed by using an explicit `default(type)` syntax.
* This also fixed various false-positive warnings reported by NuGet.
### New features
* Added `MockInterfaces.MakeConfigurationManager` for creating functional configuration managers for unit test mocking.
* Added `ISawmill.IsLogLevelEnabled()` to avoid doing expensive verbose logging operations when not necessary.
* ``string[] Split(System.ReadOnlySpan`1<char>)`` is now available in sandbox.
### Bugfixes
* Fixed auto-generated component delta-states not raising `AfterAutoHandleStateEvent`
* Fixed auto-generated component delta-states improperly implementing `IComponentDeltaState` methods. May have caused bugs in replays.
* Fixed `Robust.Client.WebView` on the launcher via a new release.
* Fixed an exception that could occur when saving a map that had tiles migrated by alias.
### Other
* The `loglevel` command now properly shows the "`null`" log level that resets the level to inheriting from parent. This was already supported by it, but the completions didn't list it.
### Internal
* Experimental SDL2 windowing backend has been replaced with SDL3. SDL3 backend is also more feature-complete, though it is still not in use.
* Updated CEF used by Robust.Client.WebView to 131.3.5.
## 238.0.1
### Bugfixes
* Fixed source generation for auto-networked EntityUid Dictionaries missing a semicolon
* Fixed PlacementManager using the wrong coordinates when deleting entities in an area.
## 238.0.0
### Breaking changes
* Some toolshed command syntax/parsing has changed slightly, and several toolshed related classes and interfaces have changed significantly, including ToolshedManager, type parsers, invocation contexts, and parser contexts. For more detail see the the description of PR #5455
## 237.4.0
### New features
* Implement automatic field-level delta states via AutoGenerateComponentState via opt-in.
### Bugfixes
* Remove redundant TransformComponentState bool.
## 237.3.0
### New features
* Added stack-like functions to `ValueList<T>` and added an `AddRange(ReadOnlySpan<T>)` overload.
* Added new `AssetPassFilterDrop`.
* Added a new RayCastSystem with the latest Box2D raycast + shapecasts implemented.
### Bugfixes
* Fixed `IPrototypeManager.TryGetKindFrom()` not working for prototypes with automatically inferred kind names.
### Other
* Sandbox error reference locator now works with generic method calls.
## 237.2.0
### Breaking changes
* `SharedEyeSystem..SetTarget()` will now also automatically remove the old target from the session's ViewSubscriptions
### New features
* `ImmutableArray<T>` can now be serialized by `RobustSerializer`.
* `RequiresLocationAttribute`, used by `ref readonly`, is now allowed by the sandbox.
* Added `DAT-OBJ()` localization function, for the dative case in certain languages.
* Client builds for FreeBSD are now made.
* Added `FormattedMessage.TrimEnd()`.
* Added Toolshed `with` for `ProtoId<T>`.
### Bugfixes
* Fix `UniqueIndex<,>.RemoveRange()` and`UniqueIndexHkm<,>.RemoveRange()` clearing the whole set instead of just removing the specified values.
* Avoid server crashes on some weird console setups (notably Pterodactyl).
* Avoid unhandled exceptions during server shutdown getting swallowed due logging into a disposed logger.
* Fix sandbox definitions for `Regex` functions returning `MatchCollection`.
* Fix minor layout bugs with `SplitContainer` and `BoxContainer`.
### Other
* Changed how multi-window rendering presents to the screen with a new CVar `display.thread_unlock_before_swap`. This is an experiment to see if it solves some synchronization issues.
* View Variables no longer clears the window on refresh while waiting on response from server.
* `SpinBox` buttons now have a `+` prefix for the positive ones.
* Improve Toolshed type intersection mechanism
### Internal
* Warning cleanup.
## 237.1.0
### New features
* csi's auto import-system can now handle generic types.
* csi's reflection helpers (like `fld()`) handle private members up the inheritance chain.
### Bugfixes
* Fix `UniqueIndexHkm<,>` and, by extension, entity data storage memory leaking.
* Fix bugs related to UIScale on `OSWindow`s.
## 237.0.0
### Breaking changes
* `IClydeWindow.Size` is now settable, allowing window sizes to be changed after creation.
### New features
* The game server's `/update` endpoint now supports passing more information on why an update is available.
* This information is accessible via `IWatchdogApi.RestartRequested`.
* Information can be specified by passing a JSON object with a `Reason` code and `Message` field.
* Added an "Erase" button to the tile spawn menu.
* Added `OSWindow.Create()`, which allows OS windows to be created & initialised without immediately opening/showing them.
### Other
* Made `WatchdogApi` and some members of `IWatchdogApi` private. These symbols should never have been accessed by content.
## 236.1.0
### New features
* `RequiredMemberAttribute` and `SetsRequiredMembersAttribute` have been added to the sandbox whitelist. I.e., you can now use the `required` keyword in client/shared code.
* Added `SwitchExpressionException` to sandbox. This type gets used if you have a `switch` expression with no default case.
* Added `LineEdit.SelectAllOnFocus`.
* `GameTitle`, `WindowIconSet` and `SplashLogo` are exposed in `IGameController`. These will return said information set in game options or whatever is set in `manifest.yml`.
* `BoundUserInterface` inheritors now have access to `PlayerManager`.
* Added `MuteSounds` bool to `BaseButton`.
* The engine has a new future-proof HWID system.
* The auth server now manages HWIDs. This avoids HWID impersonation attacks.
* The auth server can return multiple HWIDs. They are accessible in `NetUserData.ModernHWIds`.
* The auth server also returns a trust score factor, accessible as `NetUserData.Trust`.
* HWID can be disabled client side (`ROBUST_AUTH_ALLOW_HWID` env var) or server side (`net.hwid` cvar).
* The old HWID system is still in place. It is intended that content switches to placing new bans against the new HWIDs.
* Old HWIDs no longer work if the connection is not authenticated.
* `launchauth` command now recognizes `SS14_LAUNCHER_APPDATA_NAME`.
* Added new overload to `EntityLookupSystem.GetEntitiesIntersecting`.
* Added `Control.RemoveChild(int childIndex)`.
* `build.entities_category_filter` allows filtering the entity spawn panel to a specific category.
### Bugfixes
* Fixed `SpriteView` offset calculations when scaled.
### Other
* Sprite flicks are applied immediately when started.
* More warning fixes.
* If the server gets shut down before finishing startup, the reason is now logged properly.
## 236.0.0
### Breaking changes
* Revert IsTouching only being set to true if the contact were laready touching in clientside physics prediction.
* Don't touch IsTouching if both bodies are asleep for clientside physics contacts. This change and the one above should fix a lot of clientside contact issues, particularly around repeated incorrect clientside contact events.
### New features
* Added an analyzer to detect duplicate Dependency fields.
### Bugfixes
* Auto-networked dictionaries now use `TryAdd()` to avoid duplicate key errors when a dictionary contains multiple unknown networked entities.
* Fixed `ICommonSession.Ping` always returning zero instead of the ping. Note that this will still return zero for client-side code when trying to get the ping of other players.
* Hot reload XAML files on rename to fix them potentially not being reloaded with Visual Studio.
* Fix TabContainer click detection for non-1.0 UI scales.
### Other
* Obsolete some static localization methods.
* Tried to improve PVS tolerance to exceptions occurring.
## 235.0.0
### Breaking changes
* Several different `AudioSystem` methods were incorrectly given a `[return: NotNullIfNotNull]` attribute. Content code that uses these methods needs to be updated to perform null checks.
* noSpawn is no longer obsolete and is now removed in lieu of the EntityCategory HideSpawnMenu.
### Bugfixes
* physics.maxlinvelocity is now a replicated cvar.
* Fix DistanceJoint debug drawing in physics not using the local anchors.
* Fixed filtered AudioSystem methods playing a sound for all players when given an empty filter.
* Fixed equality checks for `MarkupNode` not properly handling attributes.
* Fixed `MarkupNode` not having a `GetHashCode()` implementation.
* Fixed a PVS error that could occur when trying to delete the first entity that gets created in a round.
* Fixed the "to" and "take" toolshed commands not working as intended.
* Rich text controls within an `OutputPanel` control will now become invisible when they are out of view.
### Other
* Improve precision for Quaternion2D constructor from angles.
## 234.1.0
### New features
* SharedAudioSystem now has PlayLocal which only runs audio locally on the client.
### Bugfixes
* Fix AudioParams not being passed through on PlayGlobal methods.
## 234.0.0
### Breaking changes
* Remove a lot of obsoleted code that has been obsoleted for a while.
### New features
* Add another GetLocalEntitiesIntersecting override.
### Other
* Mark large replays as requiring Server GC.
* Obsolete some IResourceCache proxies.
## 233.1.0
### New features
* Add GetGridEntities and another GetEntitiesIntersecting overload to EntityLookupSystem.
* `MarkupNode` is now `IEquatable<MarkupNode>`. It already supported equality checks, now it implements the interface.
* Added `Entity<T>` overloads to the following `SharedMapSystem` methods: `GetTileRef`, `GetAnchoredEntities`, `TileIndicesFor`.
* Added `EntityUid`-only overloads to the following `SharedTransformSystem` methods: `AnchorEntity`, `Unanchor`.
### Bugfixes
* Fixed equality checks for `MarkupNode` not properly handling attributes.
* Fixed toolshed commands failing to generate error messages when working with array types
* Fixed `MarkupNode` not having a `GetHashCode()` implementation.
### Other
* If `EntityManager.FlushEntities()` fails to delete all entities, it will now attempt to do so a second time before throwing an exception.
## 233.0.2
### Bugfixes
* Fix exceptions in client game state handling for grids. Now they will rely upon the networked fixture data and not try to rebuild in the grid state handler.
## 233.0.1
### Bugfixes
* Fix IsHardCollidable component to EntityUid references.
## 233.0.0
### Breaking changes
* Made EntityRenamed a broadcast event & added additional args.
* Made test runs parallelizable.
* Added a debug assert that other threads aren't touching entities.
### Bugfixes
* Fix some entitylookup method transformations and add more tests.
* Fix mousehover not updating if new controls showed up under the mouse.
### Internal
* `ClientGameStateManager` now only initialises or starts entities after their parents have already been initialized. There are also some new debug asserts to try ensure that this rule isn't broken elsewhere.
* Engine version script now supports dashes.
## 232.0.0
### Breaking changes
* Obsolete method `AppearanceComponent.TryGetData` is now access-restricted to `SharedAppearanceSystem`; use `SharedAppearanceSystem.TryGetData` instead.
### New features
* Added `SharedAppearanceSystem.AppendData`, which appends non-existing `AppearanceData` from one `AppearanceComponent` to another.
* Added `AppearanceComponent.AppearanceDataInit`, which can be used to set initial `AppearanceData` entries in .yaml.
### Bugfixes
* Fix BUI interfaces not deep-copying in state handling.
* Add Robust.Xaml.csproj to the solution to fix some XAML issues.
### Other
* Serialization will now add type tags (`!type:<T>`) for necessary `NodeData` when writing (currently only for `object` nodes).
### Internal
* Added `ObjectSerializer`, which handles serialization of the generic `object` type.
## 231.1.1
### Bugfixes
* Fixed a bug where the client might not add entities to the broadphase/lookup components.
* Fixed various toolshed commands not working, including `sort`, `sortdown` `join` (for strings), and `emplace`
### Other
* Toolshed command blocks now stop executing if previous errors were not handled / cleared.
## 231.1.0
### New features
* Network `InterfaceData` on `UserInterfaceComponent`.
* Added `System.Decimal` to sandbox.
* Added XAML hot reloading.
* Added API for content to write custom files into replay through `IReplayFileWriter`.
### Other
* Optimized `EntityLookup` and other physics systems.
### Internal
* Added more tests related to physics.
## 231.0.1
### Other
* Add better logging to failed PVS sends.
## 231.0.0
### Breaking changes
* ViewSubscriber has been moved to shared; it doesn't actually do anything on the client but makes shared code easier.
### New features
* ContactEnumreator exists to iterate the contacts of a particular entity.
* Add FixturesChangeComponent as a generic way to add and remove fixtures easily.
* PointLightComponent enabling / disabling now has an attempt event if you wish to block it on content side.
* There's an OpenScreenAt overload for screen-relative coordinates.
* SpriteSystem has methods to get an entity's position in sprite terms.
* EntityManager and ComponentFactory now have additional methods that interact with ComponentRegistry and ComponentRegistryEntry.
### Bugfixes
* Fix PrototypeFlags Add not actually working.
* Fix BUIs going BRRT opening and closing repeatedly upon prediction. The closing now gets deferred to the update loop if it's still closed at the end of prediction.
## 230.2.0
### New features
* Add ProcessNow for IRobustJob as a convenience method where you may not want to run a job in the background sometimes.
* Add Vector2i helpers to all 8 neighbouring directions.
### Other
* Remove IThreadPoolWorkItem interface from IRobustJob.
## 230.1.0
### New features
* You can now pass `bool[]` parameters to shaders.
* Added `toolshed.nearby_entities_limit` CVar.
* Fix `RichTextLabel.Text` to clear and reset the message properly in all cases.
* `scene` command has tab completion now.
* `devwindow` UI inspector property catches exceptions for read properties.
* `SplitContainer.Flip()`
### Bugfixes
* Fix tile enlargement not being applied for some EntityLookup queries.
* `LocalizedEntityCommands` are now not initialized inside `RobustUnitTest`, fixing guaranteed test failures.
* Fixed issues with broadphase init breaking replays frequently.
* Fix uploaded prototypes and resources for clients connecting to a server.
### Other
* Improved error reporting for DataField analyzer.
## 230.0.1
## 230.0.0
### New features
* Added `InterpolatedStringHandlerArgumentAttribute` to the sandbox whitelist.
* `IUserInterfaceManager.Popup()` popups now have a copy to clipboard button.
### Bugfixes
* Security fixes
* Fix exception in `TimedDespawnComponent` spawning another `TimedDespawnComponent`.
* Fixed pool memory leak in physics `SolveIsland`.
## 229.1.2
### Bugfixes

View File

@@ -18,3 +18,9 @@
description: entity-category-desc-hide
hideSpawnMenu: true
inheritable: false
# Entity prototypes added by the fork. With CVar you can hide all entities without this category
- type: entityCategory
id: ForkFiltered
name: entity-category-name-fork
description: entity-category-desc-fork

View File

@@ -20,6 +20,15 @@ zzzz-object-pronoun = { GENDER($ent) ->
*[neuter] it
}
# Used internally by the DAT-OBJ() function.
# Not used in en-US. Created for supporting other languages.
zzzz-dat-object = { GENDER($ent) ->
[male] him
[female] her
[epicene] them
*[neuter] it
}
# Used internally by the POSS-PRONOUN() function.
zzzz-possessive-pronoun = { GENDER($ent) ->
[male] his

View File

@@ -4,9 +4,16 @@ entity-spawn-window-title = Entity Spawn Panel
entity-spawn-window-search-bar-placeholder = search
entity-spawn-window-clear-button = Clear
entity-spawn-window-replace-button-text = Replace
entity-spawn-window-erase-button-text = Erase Mode
entity-spawn-window-override-menu-tooltip = Override placement
## TileSpawnWindow
tile-spawn-window-title = Place Tiles
## Console
console-line-edit-placeholder = Command Here
## Common Used
window-erase-button-text = Erase Mode

View File

@@ -7,3 +7,6 @@ entity-category-desc-spawner = Entity prototypes that spawn other entities.
entity-category-name-hide = Hidden
entity-category-desc-hide = Entity prototypes that should be hidden from entity spawn menus
entity-category-name-fork = Fork Filtered
entity-category-desc-fork = Entity prototypes added by the fork. With CVar you can hide all entities without this category

View File

@@ -1,4 +1,8 @@
command-description-tpto =
command-help-usage =
Usage:
command-help-invertible =
The behaviour of this command can be inverted using the "not" prefix.
command-description-tpto =
Teleport the given entities to some target entity.
command-description-player-list =
Returns a list of all player sessions.
@@ -19,7 +23,7 @@ command-description-buildinfo =
command-description-cmd-list =
Returns a list of all commands, for this side.
command-description-explain =
Explains the given expression, providing command descriptions and signatures.
Explains the given expression, providing command descriptions and signatures. This only works for valid expressions, it can't explain commands that it fails to parse.
command-description-search =
Searches through the input for the provided value.
command-description-stopwatch =
@@ -53,10 +57,8 @@ command-description-entities =
Returns all entities on the server.
command-description-paused =
Filters the input entities by whether or not they are paused.
This command can be inverted with not.
command-description-with =
Filters the input entities by whether or not they have the given component.
This command can be inverted with not.
command-description-fuck =
Throws an exception.
command-description-ecscomp-listty =
@@ -95,6 +97,8 @@ command-description-vars =
Provides a list of all variables set in this session.
command-description-any =
Returns true if there's any values in the input, otherwise false.
command-description-contains =
Returns whether the input enumerable contains the specified value.
command-description-ArrowCommand =
Assigns the input to a variable.
command-description-isempty =
@@ -119,6 +123,8 @@ command-description-splat =
"Splats" a block, value, or variable, creating N copies of it in a list.
command-description-val =
Casts the given value, block, or variable to the given type. This is mostly a workaround for current limitations of variables.
command-description-var =
Returns the contents of the given variable. This will attempt to automatically infer a variables type. Compound commands that modify a variable may need to use the 'val' command instead.
command-description-actor-controlled =
Filters entities by whether or not they're actively controlled.
command-description-actor-session =
@@ -219,9 +225,9 @@ command-description-MulVecCommand =
command-description-DivVecCommand =
Divides every element in the input by a scalar (single value).
command-description-rng-to =
Returns a number from its input to its argument (i.e. n..m inclusive)
Returns a number between the input (inclusive) and the argument (exclusive).
command-description-rng-from =
Returns a number to its input from its argument (i.e. m..n inclusive)
Returns a number between the argument (inclusive) and the input (exclusive))
command-description-rng-prob =
Returns a boolean based on the input probability/chance (from 0 to 1)
command-description-sum =

View File

@@ -0,0 +1,2 @@
popup-copy-button = Copy
popup-title = Alert!

View File

@@ -1,12 +1,8 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using Robust.Analyzers;
using VerifyCS = Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.AccessAnalyzer>;
using static Microsoft.CodeAnalysis.Testing.DiagnosticResult;
using VerifyCS = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.AccessAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
@@ -16,7 +12,7 @@ public sealed class AccessAnalyzer_Test
{
public Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<AccessAnalyzer, NUnitVerifier>()
var test = new CSharpAnalyzerTest<AccessAnalyzer, DefaultVerifier>()
{
TestState =
{

View File

@@ -0,0 +1,114 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.ByRefEventAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture, TestOf(typeof(ByRefEventAnalyzer))]
public sealed class ByRefEventAnalyzerTest
{
private const string EventBusDef = """
namespace Robust.Shared.GameObjects;
public readonly struct EntityUid;
public sealed class EntitySystem
{
public void RaiseLocalEvent<TEvent>(EntityUid uid, ref TEvent args, bool broadcast = false)
where TEvent : notnull { }
public void RaiseLocalEvent<TEvent>(EntityUid uid, TEvent args, bool broadcast = false)
where TEvent : notnull { }
}
public sealed class EntityEventBus
{
public void RaiseLocalEvent<TEvent>(EntityUid uid, ref TEvent args, bool broadcast = false)
where TEvent : notnull { }
public void RaiseLocalEvent<TEvent>(EntityUid uid, TEvent args, bool broadcast = false)
where TEvent : notnull { }
}
""";
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<ByRefEventAnalyzer, DefaultVerifier>()
{
TestState =
{
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.GameObjects.EventBusAttributes.cs"
);
test.TestState.Sources.Add(("EntityEventBus.cs", EventBusDef));
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task TestSuccess()
{
const string code = """
using Robust.Shared.GameObjects;
[ByRefEvent]
public readonly struct RefEvent;
public readonly struct ValueEvent;
public static class Foo
{
public static void Bar(EntityEventBus bus)
{
bus.RaiseLocalEvent(default(EntityUid), new ValueEvent());
var refEv = new RefEvent();
bus.RaiseLocalEvent(default(EntityUid), ref refEv);
}
}
""";
await Verifier(code);
}
[Test]
public async Task TestWrong()
{
const string code = """
using Robust.Shared.GameObjects;
[ByRefEvent]
public readonly struct RefEvent;
public readonly struct ValueEvent;
public static class Foo
{
public static void Bar(EntityEventBus bus)
{
bus.RaiseLocalEvent(default(EntityUid), new RefEvent());
var valueEv = new ValueEvent();
bus.RaiseLocalEvent(default(EntityUid), ref valueEv);
}
}
""";
await Verifier(
code,
// /0/Test0.cs(11,49): error RA0015: Tried to raise a by-ref event 'RefEvent' by value
VerifyCS.Diagnostic(ByRefEventAnalyzer.ByRefEventRaisedByValueRule).WithSpan(11, 49, 11, 63).WithArguments("RefEvent"),
// /0/Test0.cs(13,49): error RA0016: Tried to raise a value event 'ValueEvent' by-ref
VerifyCS.Diagnostic(ByRefEventAnalyzer.ByValueEventRaisedByRefRule).WithSpan(13, 49, 13, 60).WithArguments("ValueEvent")
);
}
}

View File

@@ -66,6 +66,7 @@ public sealed class ComponentPauseGeneratorTest
public partial class FooComponent
{
[RobustAutoGenerated]
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public sealed class FooComponent_AutoPauseSystem : EntitySystem
{
public override void Initialize()
@@ -106,6 +107,7 @@ public sealed class ComponentPauseGeneratorTest
public partial class FooComponent
{
[RobustAutoGenerated]
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public sealed class FooComponent_AutoPauseSystem : EntitySystem
{
public override void Initialize()
@@ -147,6 +149,7 @@ public sealed class ComponentPauseGeneratorTest
public partial class FooComponent
{
[RobustAutoGenerated]
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public sealed class FooComponent_AutoPauseSystem : EntitySystem
{
public override void Initialize()
@@ -188,6 +191,7 @@ public sealed class ComponentPauseGeneratorTest
public partial class FooComponent
{
[RobustAutoGenerated]
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public sealed class FooComponent_AutoPauseSystem : EntitySystem
{
public override void Initialize()

View File

@@ -1,10 +1,9 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.DataDefinitionAnalyzer>;
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.DataDefinitionAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
@@ -14,7 +13,7 @@ public sealed class DataDefinitionAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<DataDefinitionAnalyzer, NUnitVerifier>()
var test = new CSharpAnalyzerTest<DataDefinitionAnalyzer, DefaultVerifier>()
{
TestState =
{
@@ -84,8 +83,8 @@ public sealed class DataDefinitionAnalyzerTest
""";
await Verifier(code,
// /0/Test0.cs(35,5): info RA0028: Data field Bad in data definition Foo has ViewVariables attribute with ReadWrite access, which is redundant
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldNoVVReadWriteRule).WithSpan(35, 5, 36, 20).WithArguments("Bad", "Foo")
// /0/Test0.cs(35,17): info RA0028: Data field Bad in data definition Foo has ViewVariables attribute with ReadWrite access, which is redundant
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldNoVVReadWriteRule).WithSpan(35, 17, 35, 50).WithArguments("Bad", "Foo")
);
}
}

View File

@@ -1,10 +1,9 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.DependencyAssignAnalyzer>;
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.DependencyAssignAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
@@ -14,7 +13,7 @@ public sealed class DependencyAssignAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<DependencyAssignAnalyzer, NUnitVerifier>()
var test = new CSharpAnalyzerTest<DependencyAssignAnalyzer, DefaultVerifier>()
{
TestState =
{

View File

@@ -0,0 +1,62 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.DuplicateDependencyAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
[TestOf(typeof(DuplicateDependencyAnalyzer))]
public sealed class DuplicateDependencyAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<DuplicateDependencyAnalyzer, DefaultVerifier>()
{
TestState =
{
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.IoC.DependencyAttribute.cs"
);
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task Test()
{
const string code = """
using Robust.Shared.IoC;
public sealed class Foo
{
[Dependency]
private object? Field;
[Dependency]
private object? Field2;
[Dependency]
private string? DifferentField;
private string? NonDependency1;
private string? NonDependency2;
}
""";
await Verifier(code,
// /0/Test0.cs(9,21): warning RA0032: Another [Dependency] field of type 'object?' already exists in this type as field 'Field'
VerifyCS.Diagnostic().WithSpan(9, 21, 9, 27).WithArguments("object?", "Field"));
}
}

View File

@@ -1,10 +1,9 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.MustCallBaseAnalyzer>;
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.MustCallBaseAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
@@ -14,7 +13,7 @@ public sealed class MustCallBaseAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<MustCallBaseAnalyzer, NUnitVerifier>()
var test = new CSharpAnalyzerTest<MustCallBaseAnalyzer, DefaultVerifier>()
{
TestState =
{

View File

@@ -1,10 +1,9 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.NoUncachedRegexAnalyzer>;
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.NoUncachedRegexAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
@@ -14,7 +13,7 @@ public sealed class NoUncachedRegexAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<NoUncachedRegexAnalyzer, NUnitVerifier>()
var test = new CSharpAnalyzerTest<NoUncachedRegexAnalyzer, DefaultVerifier>()
{
TestState =
{

View File

@@ -1,10 +1,9 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.PreferNonGenericVariantForAnalyzer>;
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PreferNonGenericVariantForAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
@@ -14,7 +13,7 @@ public sealed class PreferNonGenericVariantForTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<PreferNonGenericVariantForAnalyzer, NUnitVerifier>()
var test = new CSharpAnalyzerTest<PreferNonGenericVariantForAnalyzer, DefaultVerifier>()
{
TestState =
{

View File

@@ -1,10 +1,9 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.PreferOtherTypeAnalyzer>;
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PreferOtherTypeAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
@@ -14,7 +13,7 @@ public sealed class PreferOtherTypeAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<PreferOtherTypeAnalyzer, NUnitVerifier>()
var test = new CSharpAnalyzerTest<PreferOtherTypeAnalyzer, DefaultVerifier>()
{
TestState =
{

View File

@@ -1,10 +1,9 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.PreferOtherTypeAnalyzer>;
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PreferOtherTypeAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
@@ -12,7 +11,7 @@ public sealed class PreferOtherTypeFixerTest
{
private static Task Verifier(string code, string fixedCode, params DiagnosticResult[] expected)
{
var test = new CSharpCodeFixTest<PreferOtherTypeAnalyzer, PreferOtherTypeFixer, NUnitVerifier>()
var test = new CSharpCodeFixTest<PreferOtherTypeAnalyzer, PreferOtherTypeFixer, DefaultVerifier>()
{
TestState =
{

View File

@@ -14,6 +14,7 @@
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferNonGenericVariantForAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferOtherTypeAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\GameObjects\EventBusAttributes.cs" LogicalName="Robust.Shared.GameObjects.EventBusAttributes.cs" LinkBase="Implementations" />
</ItemGroup>
<PropertyGroup>
@@ -27,13 +28,17 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzer.Testing"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing"/>
<PackageReference Include="NUnit"/>
<PackageReference Include="NUnit3TestAdapter"/>
<PackageReference Include="NUnit.Analyzers"/>
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
<!-- Needed to fix transitive dependency versions -->
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
<PackageReference Include="System.Formats.Asn1" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,4 +1,4 @@
#nullable enable
#nullable enable
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
@@ -24,7 +24,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
"Make sure that methods subscribing to a ref event have the ref keyword for the event argument."
);
private static readonly DiagnosticDescriptor ByRefEventRaisedByValueRule = new(
public static readonly DiagnosticDescriptor ByRefEventRaisedByValueRule = new(
Diagnostics.IdByRefEventRaisedByValue,
"By-ref event raised by value",
"Tried to raise a by-ref event '{0}' by value",
@@ -34,7 +34,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
"Make sure to use the ref keyword when raising ref events."
);
private static readonly DiagnosticDescriptor ByValueEventRaisedByRefRule = new(
public static readonly DiagnosticDescriptor ByValueEventRaisedByRefRule = new(
Diagnostics.IdValueEventRaisedByRef,
"Value event raised by-ref",
"Tried to raise a value event '{0}' by-ref",
@@ -54,32 +54,44 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();
context.RegisterOperationAction(CheckEventRaise, OperationKind.Invocation);
context.RegisterCompilationStartAction(compilationContext =>
{
var raiseMethods = compilationContext.Compilation
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntitySystem")?
.GetMembers()
.Where(m => m.Name.Contains("RaiseLocalEvent") && m.Kind == SymbolKind.Method)
.Cast<IMethodSymbol>();
var busRaiseMethods = compilationContext.Compilation
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntityEventBus")?
.GetMembers()
.Where(m => m.Name.Contains("RaiseLocalEvent") && m.Kind == SymbolKind.Method)
.Cast<IMethodSymbol>();
if (raiseMethods == null)
return;
if (busRaiseMethods != null)
raiseMethods = raiseMethods.Concat(busRaiseMethods);
var raiseMethodsArray = raiseMethods.ToArray();
compilationContext.RegisterOperationAction(
ctx => CheckEventRaise(ctx, raiseMethodsArray),
OperationKind.Invocation);
});
}
private void CheckEventRaise(OperationAnalysisContext context)
private static void CheckEventRaise(
OperationAnalysisContext context,
IReadOnlyCollection<IMethodSymbol> raiseMethods)
{
if (context.Operation is not IInvocationOperation operation)
return;
var raiseMethods = context.Compilation
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntitySystem")?
.GetMembers()
.Where(m => m.Name.Contains("RaiseLocalEvent") && m.Kind == SymbolKind.Method)
.Cast<IMethodSymbol>();
var busRaiseMethods = context.Compilation
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntityEventBus")?
.GetMembers()
.Where(m => m.Name.Contains("RaiseLocalEvent") && m.Kind == SymbolKind.Method)
.Cast<IMethodSymbol>();
if (raiseMethods == null)
if (!operation.TargetMethod.Name.Contains("RaiseLocalEvent"))
return;
if (busRaiseMethods != null)
raiseMethods = raiseMethods.Concat(busRaiseMethods);
if (!raiseMethods.Any(m => m.Equals(operation.TargetMethod.OriginalDefinition, Default)))
{
// If you try to do this normally by concatenating like busRaiseMethods above

View File

@@ -18,6 +18,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private const string ImplicitDataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.ImplicitDataDefinitionForInheritorsAttribute";
private const string DataFieldBaseNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataFieldBaseAttribute";
private const string ViewVariablesNamespace = "Robust.Shared.ViewVariables.ViewVariablesAttribute";
private const string DataFieldAttributeName = "DataField";
private const string ViewVariablesAttributeName = "ViewVariables";
private static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
Diagnostics.IdDataDefinitionPartial,
@@ -152,12 +154,14 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (HasRedundantTag(fieldSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
TryGetAttributeLocation(field, DataFieldAttributeName, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, location, fieldSymbol.Name, type.Name));
}
if (HasVVReadWrite(fieldSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldNoVVReadWriteRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
TryGetAttributeLocation(field, ViewVariablesAttributeName, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldNoVVReadWriteRule, location, fieldSymbol.Name, type.Name));
}
}
}
@@ -186,12 +190,14 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (HasRedundantTag(propertySymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
TryGetAttributeLocation(property, DataFieldAttributeName, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, location, propertySymbol.Name, type.Name));
}
if (HasVVReadWrite(propertySymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldNoVVReadWriteRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
TryGetAttributeLocation(property, ViewVariablesAttributeName, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldNoVVReadWriteRule, location, propertySymbol.Name, type.Name));
}
}
@@ -261,6 +267,24 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
return false;
}
private static bool TryGetAttributeLocation(MemberDeclarationSyntax syntax, string attributeName, out Location location)
{
foreach (var attributeList in syntax.AttributeLists)
{
foreach (var attribute in attributeList.Attributes)
{
if (attribute.Name.ToString() != attributeName)
continue;
location = attribute.GetLocation();
return true;
}
}
// Default to the declaration syntax's location
location = syntax.GetLocation();
return false;
}
private static bool IsReadOnlyMember(ITypeSymbol type, ISymbol member)
{
if (member is IFieldSymbol field)

View File

@@ -0,0 +1,126 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
#nullable enable
/// <summary>
/// Analyzer that detects duplicate <c>[Dependency]</c> fields inside a single type.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class DuplicateDependencyAnalyzer : DiagnosticAnalyzer
{
private const string DependencyAttributeType = "Robust.Shared.IoC.DependencyAttribute";
private static readonly DiagnosticDescriptor Rule = new(
Diagnostics.IdDuplicateDependency,
"Duplicate dependency field",
"Another [Dependency] field of type '{0}' already exists in this type with field '{1}'",
"Usage",
DiagnosticSeverity.Warning,
true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(compilationContext =>
{
var dependencyAttributeType = compilationContext.Compilation.GetTypeByMetadataName(DependencyAttributeType);
if (dependencyAttributeType == null)
return;
compilationContext.RegisterSymbolStartAction(symbolContext =>
{
var typeSymbol = (INamedTypeSymbol)symbolContext.Symbol;
// Only deal with non-static classes, doesn't make sense to have dependencies in anything else.
if (typeSymbol.TypeKind != TypeKind.Class || typeSymbol.IsStatic)
return;
var state = new AnalyzerState(dependencyAttributeType);
symbolContext.RegisterSyntaxNodeAction(state.AnalyzeField, SyntaxKind.FieldDeclaration);
symbolContext.RegisterSymbolEndAction(state.End);
},
SymbolKind.NamedType);
});
}
private sealed class AnalyzerState(INamedTypeSymbol dependencyAttributeType)
{
private readonly Dictionary<ITypeSymbol, List<IFieldSymbol>> _dependencyFields = new(SymbolEqualityComparer.Default);
public void AnalyzeField(SyntaxNodeAnalysisContext context)
{
var field = (FieldDeclarationSyntax)context.Node;
if (field.AttributeLists.Count == 0)
return;
if (context.ContainingSymbol is not IFieldSymbol fieldSymbol)
return;
// Can't have [Dependency]s for non-reference types.
if (!fieldSymbol.Type.IsReferenceType)
return;
if (!IsDependency(context.ContainingSymbol))
return;
lock (_dependencyFields)
{
if (!_dependencyFields.TryGetValue(fieldSymbol.Type, out var dependencyFields))
{
dependencyFields = [];
_dependencyFields.Add(fieldSymbol.Type, dependencyFields);
}
dependencyFields.Add(fieldSymbol);
}
}
private bool IsDependency(ISymbol symbol)
{
foreach (var attributeData in symbol.GetAttributes())
{
if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, dependencyAttributeType))
return true;
}
return false;
}
public void End(SymbolAnalysisContext context)
{
lock (_dependencyFields)
{
foreach (var pair in _dependencyFields)
{
var fieldType = pair.Key;
var fields = pair.Value;
if (fields.Count <= 1)
continue;
// Sort so we can have deterministic order to skip reporting for a single field.
// Whichever sorts first doesn't get reported.
fields.Sort(static (a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
// Start at index 1 to skip first field.
var firstField = fields[0];
for (var i = 1; i < fields.Count; i++)
{
var field = fields[i];
context.ReportDiagnostic(
Diagnostic.Create(Rule, field.Locations[0], fieldType.ToDisplayString(), firstField.Name));
}
}
}
}
}
}

View File

@@ -0,0 +1,96 @@
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.Configuration;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Systems;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.Physics;
[Virtual]
[MediumRunJob]
public class PhysicsBoxStackBenchmark
{
private ISimulation _sim = default!;
[GlobalSetup]
public void Setup()
{
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = _sim.Resolve<IEntityManager>();
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
SetupTumbler(entManager, mapId);
for (var i = 0; i < 30; i++)
{
entManager.TickUpdate(0.016f, false);
}
}
[Benchmark]
public void BoxStack()
{
var entManager = _sim.Resolve<IEntityManager>();
for (var i = 0; i < 10000; i++)
{
entManager.TickUpdate(0.016f, false);
}
}
private void SetupTumbler(IEntityManager entManager, MapId mapId)
{
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10));
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
var xs = new[]
{
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
};
var columnCount = 1;
var rowCount = 15;
PolygonShape shape;
for (var j = 0; j < columnCount; j++)
{
for (var i = 0; i < rowCount; i++)
{
var x = 0.0f;
var boxUid = entManager.SpawnEntity(null,
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 1.1f * i), mapId));
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
shape = new PolygonShape();
shape.SetAsBox(0.5f, 0.5f);
physics.SetFixedRotation(boxUid, false, body: box);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true), body: box);
physics.WakeBody(boxUid, body: box);
physics.SetSleepingAllowed(boxUid, box, false);
}
}
physics.WakeBody(groundUid, body: ground);
}
}

View File

@@ -0,0 +1,92 @@
using System.Numerics;
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Systems;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.Physics;
[Virtual]
public class PhysicsCircleStackBenchmark
{
private ISimulation _sim = default!;
[GlobalSetup]
public void Setup()
{
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = _sim.Resolve<IEntityManager>();
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
SetupTumbler(entManager, mapId);
for (var i = 0; i < 30; i++)
{
entManager.TickUpdate(0.016f, false);
}
}
[Benchmark]
public void CircleStack()
{
var entManager = _sim.Resolve<IEntityManager>();
for (var i = 0; i < 10000; i++)
{
entManager.TickUpdate(0.016f, false);
}
}
private void SetupTumbler(IEntityManager entManager, MapId mapId)
{
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
var vertical = new EdgeShape(new Vector2(20, 0), new Vector2(20, 20));
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
var xs = new[]
{
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
};
var columnCount = 1;
var rowCount = 15;
PhysShapeCircle shape;
for (var j = 0; j < columnCount; j++)
{
for (var i = 0; i < rowCount; i++)
{
var x = 0.0f;
var boxUid = entManager.SpawnEntity(null,
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 2.1f * i), mapId));
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
shape = new PhysShapeCircle(0.5f);
physics.SetFixedRotation(boxUid, false, body: box);
// TODO: Need to detect shape and work out if we need to use fixedrotation
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f));
physics.WakeBody(boxUid, body: box);
physics.SetSleepingAllowed(boxUid, box, false);
}
}
physics.WakeBody(groundUid, body: ground);
}
}

View File

@@ -0,0 +1,91 @@
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Systems;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.Physics;
[Virtual]
public class PhysicsPyramidBenchmark
{
private ISimulation _sim = default!;
[GlobalSetup]
public void Setup()
{
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = _sim.Resolve<IEntityManager>();
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
SetupTumbler(entManager, mapId);
for (var i = 0; i < 300; i++)
{
entManager.TickUpdate(0.016f, false);
}
}
[Benchmark]
public void Pyramid()
{
var entManager = _sim.Resolve<IEntityManager>();
for (var i = 0; i < 5000; i++)
{
entManager.TickUpdate(0.016f, false);
}
}
private void SetupTumbler(IEntityManager entManager, MapId mapId)
{
const byte count = 20;
// Setup ground
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(40, 0), new Vector2(-40, 0));
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
physics.WakeBody(groundUid, body: ground);
// Setup boxes
float a = 0.5f;
PolygonShape shape = new();
shape.SetAsBox(a, a);
var x = new Vector2(-7.0f, 0.75f);
Vector2 y;
Vector2 deltaX = new Vector2(0.5625f, 1.25f);
Vector2 deltaY = new Vector2(1.125f, 0.0f);
for (var i = 0; i < count; ++i)
{
y = x;
for (var j = i; j < count; ++j)
{
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(y, mapId));
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f), body: box);
y += deltaY;
physics.WakeBody(boxUid, body: box);
physics.SetSleepingAllowed(boxUid, box, false);
}
x += deltaX;
}
}
}

View File

@@ -0,0 +1,105 @@
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Systems;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.Physics;
[Virtual]
public class PhysicsTumblerBenchmark
{
private ISimulation _sim = default!;
[GlobalSetup]
public void Setup()
{
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = _sim.Resolve<IEntityManager>();
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
SetupTumbler(entManager, mapId);
for (var i = 0; i < 800; i++)
{
entManager.TickUpdate(0.016f, false);
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
physics.SetFixedRotation(boxUid, false, body: box);
var shape = new PolygonShape();
shape.SetAsBox(0.125f, 0.125f);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 0.0625f), body: box);
physics.WakeBody(boxUid, body: box);
physics.SetSleepingAllowed(boxUid, box, false);
}
}
[Benchmark]
public void Tumbler()
{
var entManager = _sim.Resolve<IEntityManager>();
for (var i = 0; i < 5000; i++)
{
entManager.TickUpdate(0.016f, false);
}
}
private void SetupTumbler(IEntityManager entManager, MapId mapId)
{
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
var joints = entManager.System<SharedJointSystem>();
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 0f, mapId));
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
// Due to lookup changes fixtureless bodies are invalid, so
var cShape = new PhysShapeCircle(1f);
fixtures.CreateFixture(groundUid, "fix1", new Fixture(cShape, 0, 0, false));
var bodyUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
var body = entManager.AddComponent<PhysicsComponent>(bodyUid);
physics.SetBodyType(bodyUid, BodyType.Dynamic, body: body);
physics.SetSleepingAllowed(bodyUid, body, false);
physics.SetFixedRotation(bodyUid, false, body: body);
// TODO: Box2D just deref, bleh shape structs someday
var shape1 = new PolygonShape();
shape1.SetAsBox(0.5f, 10.0f, new Vector2(10.0f, 0.0f), 0.0f);
fixtures.CreateFixture(bodyUid, "fix1", new Fixture(shape1, 2, 0, true, 20f));
var shape2 = new PolygonShape();
shape2.SetAsBox(0.5f, 10.0f, new Vector2(-10.0f, 0.0f), 0f);
fixtures.CreateFixture(bodyUid, "fix2", new Fixture(shape2, 2, 0, true, 20f));
var shape3 = new PolygonShape();
shape3.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, 10.0f), 0f);
fixtures.CreateFixture(bodyUid, "fix3", new Fixture(shape3, 2, 0, true, 20f));
var shape4 = new PolygonShape();
shape4.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, -10.0f), 0f);
fixtures.CreateFixture(bodyUid, "fix4", new Fixture(shape4, 2, 0, true, 20f));
physics.WakeBody(groundUid, body: ground);
physics.WakeBody(bodyUid, body: body);
var revolute = joints.CreateRevoluteJoint(groundUid, bodyUid);
revolute.LocalAnchorA = new Vector2(0f, 10f);
revolute.LocalAnchorB = new Vector2(0f, 0f);
revolute.ReferenceAngle = 0f;
revolute.MotorSpeed = 0.05f * MathF.PI;
revolute.MaxMotorTorque = 100000000f;
revolute.EnableMotor = true;
}
}

View File

@@ -19,6 +19,10 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
<!-- Needed to pin transitive dependency versions. -->
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" />
<PackageReference Include="System.Formats.Asn1" />
</ItemGroup>
<Import Project="..\MSBuild\Robust.Properties.targets" />

View File

@@ -1,8 +1,8 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Robust.Xaml;
namespace Robust.Build.Tasks
{
@@ -37,10 +37,12 @@ namespace Robust.Build.Tasks
var msg = $"CompileRobustXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{OutputPath}";
BuildEngine.LogMessage(msg, MessageImportance.High);
var res = XamlCompiler.Compile(BuildEngine, input,
var res = XamlAotCompiler.Compile(
BuildEngine, input,
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
ProjectDirectory, OutputPath,
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null);
OutputPath,
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null
);
if (!res.success)
return false;
if (!res.writtentofile)
@@ -65,22 +67,24 @@ namespace Robust.Build.Tasks
return true;
}
// PYREX NOTE: This project was comically null-unsafe before I touched it. I'm just marking what it did accurately
[Required]
public string ReferencesFilePath { get; set; }
public string ReferencesFilePath { get; set; } = null!;
[Required]
public string ProjectDirectory { get; set; }
public string ProjectDirectory { get; set; } = null!;
[Required]
public string AssemblyFile { get; set; }
public string AssemblyFile { get; set; } = null!;
[Required]
public string OriginalCopyPath { get; set; }
public string? OriginalCopyPath { get; set; } = null;
public string OutputPath { get; set; }
public string UpdateBuildIndicator { get; set; }
public string? OutputPath { get; set; }
public string UpdateBuildIndicator { get; set; } = null!;
public string AssemblyOriginatorKeyFile { get; set; }
public string AssemblyOriginatorKeyFile { get; set; } = null!;
public bool SignAssembly { get; set; }
public bool DelaySign { get; set; }
@@ -95,7 +99,7 @@ namespace Robust.Build.Tasks
return rv;
}
public IBuildEngine BuildEngine { get; set; }
public ITaskHost HostObject { get; set; }
public IBuildEngine BuildEngine { get; set; } = null!;
public ITaskHost HostObject { get; set; } = null!;
}
}

View File

@@ -1,37 +0,0 @@
using System.Linq;
using Pidgin;
using static Pidgin.Parser;
namespace Robust.Build.Tasks
{
public static class MathParsing
{
public static Parser<char, float> Single { get; } = Real.Select(c => (float) c);
public static Parser<char, float> Single1 { get; }
= Single.Between(SkipWhitespaces);
public static Parser<char, (float, float)> Single2 { get; }
= Single.Before(SkipWhitespaces).Repeat(2).Select(e =>
{
var arr = e.ToArray();
return (arr[0], arr[1]);
});
public static Parser<char, (float, float, float, float)> Single4 { get; }
= Single.Before(SkipWhitespaces).Repeat(4).Select(e =>
{
var arr = e.ToArray();
return (arr[0], arr[1], arr[2], arr[3]);
});
public static Parser<char, float[]> Thickness { get; }
= SkipWhitespaces.Then(
OneOf(
Try(Single4.Select(c => new[] {c.Item1, c.Item2, c.Item3, c.Item4})),
Try(Single2.Select(c => new[] {c.Item1, c.Item2})),
Try(Single1.Select(c => new[] {c}))
));
}
}

View File

@@ -55,9 +55,11 @@ namespace Robust.Build.Tasks
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties,
IDictionary targetOutputs) => throw new NotSupportedException();
public bool ContinueOnError { get; }
public int LineNumberOfTaskNode { get; }
public int ColumnNumberOfTaskNode { get; }
public string ProjectFileOfTaskNode { get; }
// PYREX NOTE: This project was extremely null-unsafe before I touched it. I'm just marking what it did already
// Here's the broken interface of IBuildEngine that we started with
public bool ContinueOnError => default;
public int LineNumberOfTaskNode => default;
public int ColumnNumberOfTaskNode => default;
public string ProjectFileOfTaskNode => null!;
}
}

View File

@@ -1,17 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\MSBuild\Robust.Engine.props" />
<!--
PJB3005 (2024-08-24)
So the reason that Robust.Client.Injectors is NS2.0 is that Visual Studio
still ships a .NET FX based MSBuild for some godforsaken reason. This means
that when having Robust.Client.Injectors loaded directly by the main MSBuild
process... that would break.
Except we don't do that anyways right now due to file locking issues, so maybe
it's fine to give up on that. Whatever.
-->
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<TargetFramework>netstandard2.0</TargetFramework>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="17.8.3" />
<PackageReference Include="Mono.Cecil" Version="0.11.5" />
<PackageReference Include="Pidgin" Version="2.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\XamlX\src\XamlX.IL.Cecil\XamlX.IL.Cecil.csproj" />
<ProjectReference Include="..\Robust.Xaml\Robust.Xaml.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,389 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using Pidgin;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Parsers;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Robust.Build.Tasks
{
/// <summary>
/// Based on https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
/// Adjusted for our UI-Framework
/// </summary>
public partial class XamlCompiler
{
public static (bool success, bool writtentofile) Compile(IBuildEngine engine, string input, string[] references,
string projectDirectory, string output, string strongNameKey)
{
var typeSystem = new CecilTypeSystem(references
.Where(r => !r.ToLowerInvariant().EndsWith("robust.build.tasks.dll"))
.Concat(new[] { input }), input);
var asm = typeSystem.TargetAssemblyDefinition;
if (asm.MainModule.GetType("CompiledRobustXaml", "XamlIlContext") != null)
{
// If this type exists, the assembly has already been processed by us.
// Do not run again, it would corrupt the file.
// This *shouldn't* be possible due to Inputs/Outputs dependencies in the build system,
// but better safe than sorry eh?
engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", "", 0, 0, 0, 0, "Ran twice on same assembly file; ignoring.", "", ""));
return (true, false);
}
var compileRes = CompileCore(engine, typeSystem);
if (compileRes == null)
return (true, false);
if (compileRes == false)
return (false, false);
var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols };
if (!string.IsNullOrWhiteSpace(strongNameKey))
writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
asm.Write(output, writerParameters);
return (true, true);
}
static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem)
{
var asm = typeSystem.TargetAssemblyDefinition;
var embrsc = new EmbeddedResources(asm);
if (embrsc.Resources.Count(CheckXamlName) == 0)
// Nothing to do
return null;
var xamlLanguage = new XamlLanguageTypeMappings(typeSystem)
{
XmlnsAttributes =
{
typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute"),
},
ContentAttributes =
{
typeSystem.GetType("Avalonia.Metadata.ContentAttribute")
},
UsableDuringInitializationAttributes =
{
typeSystem.GetType("Robust.Client.UserInterface.XAML.UsableDuringInitializationAttribute")
},
DeferredContentPropertyAttributes =
{
typeSystem.GetType("Robust.Client.UserInterface.XAML.DeferredContentAttribute")
},
RootObjectProvider = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestRootObjectProvider"),
UriContextProvider = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestUriContext"),
ProvideValueTarget = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestProvideValueTarget"),
};
var emitConfig = new XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult>
{
ContextTypeBuilderCallback = (b,c) => EmitNameScopeField(xamlLanguage, typeSystem, b, c)
};
var transformerconfig = new TransformerConfiguration(
typeSystem,
typeSystem.TargetAssembly,
xamlLanguage,
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage), CustomValueConverter);
var contextDef = new TypeDefinition("CompiledRobustXaml", "XamlIlContext",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(contextDef);
var contextClass = XamlILContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
xamlLanguage, emitConfig);
var compiler =
new RobustXamlILCompiler(transformerconfig, emitConfig, true);
bool CompileGroup(IResourceGroup group)
{
var typeDef = new TypeDefinition("CompiledRobustXaml", "!" + group.Name, TypeAttributes.Class,
asm.MainModule.TypeSystem.Object);
//typeDef.CustomAttributes.Add(new CustomAttribute(ed));
asm.MainModule.Types.Add(typeDef);
var builder = typeSystem.CreateTypeBuilder(typeDef);
foreach (var res in group.Resources.Where(CheckXamlName))
{
try
{
engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", MessageImportance.Low);
var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
var parsed = XDocumentXamlParser.Parse(xaml);
var initialRoot = (XamlAstObjectNode) parsed.Root;
var classDirective = initialRoot.Children.OfType<XamlAstXmlDirective>()
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class");
string classname;
if (classDirective != null && classDirective.Values[0] is XamlAstTextNode tn)
{
classname = tn.Text;
}
else
{
classname = res.Name.Replace(".xaml","");
}
var classType = typeSystem.TargetAssembly.FindType(classname);
if (classType == null)
throw new Exception($"Unable to find type '{classname}'");
compiler.Transform(parsed);
var populateName = $"Populate:{res.Name}";
var buildName = $"Build:{res.Name}";
var classTypeDefinition = typeSystem.GetTypeReference(classType).Resolve();
var populateBuilder = typeSystem.CreateTypeBuilder(classTypeDefinition);
compiler.Compile(parsed, contextClass,
compiler.DefinePopulateMethod(populateBuilder, parsed, populateName,
classTypeDefinition == null),
compiler.DefineBuildMethod(builder, parsed, buildName, true),
null,
(closureName, closureBaseType) =>
populateBuilder.DefineSubType(closureBaseType, closureName, false),
res.Uri, res
);
//add compiled populate method
var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve().Methods
.First(m => m.Name == populateName);
const string TrampolineName = "!XamlIlPopulateTrampoline";
var trampoline = new MethodDefinition(TrampolineName,
MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void);
trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition));
classTypeDefinition.Methods.Add(trampoline);
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull));
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod));
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
var foundXamlLoader = false;
// Find RobustXamlLoader.Load(this) and replace it with !XamlIlPopulateTrampoline(this)
foreach (var method in classTypeDefinition.Methods
.Where(m => !m.Attributes.HasFlag(MethodAttributes.Static)))
{
var i = method.Body.Instructions;
for (var c = 1; c < i.Count; c++)
{
if (i[c].OpCode == OpCodes.Call)
{
var op = i[c].Operand as MethodReference;
if (op != null
&& op.Name == TrampolineName)
{
foundXamlLoader = true;
break;
}
if (op != null
&& op.Name == "Load"
&& op.Parameters.Count == 1
&& op.Parameters[0].ParameterType.FullName == "System.Object"
&& op.DeclaringType.FullName == "Robust.Client.UserInterface.XAML.RobustXamlLoader")
{
if (MatchThisCall(i, c - 1))
{
i[c].Operand = trampoline;
foundXamlLoader = true;
}
}
}
}
}
if (!foundXamlLoader)
{
var ctors = classTypeDefinition.GetConstructors()
.Where(c => !c.IsStatic).ToList();
// We can inject xaml loader into default constructor
if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o=>o.OpCode != OpCodes.Nop) == 3)
{
var i = ctors[0].Body.Instructions;
var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret));
i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampoline));
i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0));
}
else
{
throw new InvalidProgramException(
$"No call to RobustXamlLoader.Load(this) call found anywhere in the type {classType.FullName} and type seems to have custom constructors.");
}
}
}
catch (Exception e)
{
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
}
res.Remove();
}
return true;
}
if (embrsc.Resources.Count(CheckXamlName) != 0)
{
if (!CompileGroup(embrsc))
return false;
}
return true;
}
private static bool CustomValueConverter(
AstTransformationContext context,
IXamlAstValueNode node,
IXamlType type,
out IXamlAstValueNode result)
{
if (!(node is XamlAstTextNode textNode))
{
result = null;
return false;
}
var text = textNode.Text;
var types = context.GetRobustTypes();
if (type.Equals(types.Vector2))
{
var foo = MathParsing.Single2.Parse(text);
if (!foo.Success)
throw new XamlLoadException($"Unable to parse \"{text}\" as a Vector2", node);
var (x, y) = foo.Value;
result = new RXamlSingleVecLikeConstAstNode(
node,
types.Vector2, types.Vector2ConstructorFull,
types.Single, new[] {x, y});
return true;
}
if (type.Equals(types.Thickness))
{
var foo = MathParsing.Thickness.Parse(text);
if (!foo.Success)
throw new XamlLoadException($"Unable to parse \"{text}\" as a Thickness", node);
var val = foo.Value;
float[] full;
if (val.Length == 1)
{
var u = val[0];
full = new[] {u, u, u, u};
}
else if (val.Length == 2)
{
var h = val[0];
var v = val[1];
full = new[] {h, v, h, v};
}
else // 4
{
full = val;
}
result = new RXamlSingleVecLikeConstAstNode(
node,
types.Thickness, types.ThicknessConstructorFull,
types.Single, full);
return true;
}
if (type.Equals(types.Thickness))
{
var foo = MathParsing.Thickness.Parse(text);
if (!foo.Success)
throw new XamlLoadException($"Unable to parse \"{text}\" as a Thickness", node);
var val = foo.Value;
float[] full;
if (val.Length == 1)
{
var u = val[0];
full = new[] {u, u, u, u};
}
else if (val.Length == 2)
{
var h = val[0];
var v = val[1];
full = new[] {h, v, h, v};
}
else // 4
{
full = val;
}
result = new RXamlSingleVecLikeConstAstNode(
node,
types.Thickness, types.ThicknessConstructorFull,
types.Single, full);
return true;
}
if (type.Equals(types.Color))
{
// TODO: Interpret these colors at XAML compile time instead of at runtime.
result = new RXamlColorAstNode(node, types, text);
return true;
}
result = null;
return false;
}
public const string ContextNameScopeFieldName = "RobustNameScope";
private static void EmitNameScopeField(XamlLanguageTypeMappings xamlLanguage, CecilTypeSystem typeSystem, IXamlTypeBuilder<IXamlILEmitter> typeBuilder, IXamlILEmitter constructor)
{
var nameScopeType = typeSystem.FindType("Robust.Client.UserInterface.XAML.NameScope");
var field = typeBuilder.DefineField(nameScopeType,
ContextNameScopeFieldName, true, false);
constructor
.Ldarg_0()
.Newobj(nameScopeType.GetConstructor())
.Stfld(field);
}
}
interface IResource : IFileSource
{
string Uri { get; }
string Name { get; }
void Remove();
}
interface IResourceGroup
{
string Name { get; }
IEnumerable<IResource> Resources { get; }
}
}

View File

@@ -15,6 +15,9 @@ using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
// Yes dude I know this source generator isn't incremental, I'll fix it eventually.
#pragma warning disable RS1035
namespace Robust.Client.NameGenerator
{
/// <summary>

View File

@@ -64,6 +64,8 @@ internal abstract class BaseRobustCefClient : CefClient
string title,
string defaultFilePath,
string[] acceptFilters,
string[] acceptExtensions,
string[] acceptDescriptions,
CefFileDialogCallback callback)
{
callback.Cancel();

View File

@@ -44,6 +44,8 @@ namespace Robust.Client.WebView.Cef
//commandLine.AppendSwitch("--disable-gpu-compositing");
//commandLine.AppendSwitch("--in-process-gpu");
commandLine.AppendSwitch("--off-screen-rendering-enabled");
commandLine.AppendSwitch("disable-threaded-scrolling", "1");
commandLine.AppendSwitch("disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents");

View File

@@ -23,6 +23,7 @@ namespace Robust.Client.WebView.Cef
var info = CefWindowInfo.Create();
info.Bounds = new CefRectangle(0, 0, createParams.Width, createParams.Height);
info.SetAsPopup(mainHWnd, "ss14cef");
info.RuntimeStyle = CefRuntimeStyle.Alloy;
var impl = new WebViewWindowImpl(this);

View File

@@ -484,27 +484,27 @@ namespace Robust.Client.WebView.Cef
public void FocusEntered()
{
if (_textInputActive)
_clyde.TextInputStart();
Owner.Root?.Window?.TextInputStart();
}
public void FocusExited()
{
if (_textInputActive)
_clyde.TextInputStop();
Owner.Root?.Window?.TextInputStop();
}
public void TextInputStart()
{
_textInputActive = true;
if (Owner.HasKeyboardFocus())
_clyde.TextInputStart();
Owner.Root?.Window?.TextInputStart();
}
public void TextInputStop()
{
_textInputActive = false;
if (Owner.HasKeyboardFocus())
_clyde.TextInputStop();
Owner.Root?.Window?.TextInputStop();
}
private sealed class LiveData
@@ -587,8 +587,11 @@ namespace Robust.Client.WebView.Cef
}
}
protected override void OnAcceleratedPaint(CefBrowser browser, CefPaintElementType type,
CefRectangle[] dirtyRects, IntPtr sharedHandle)
protected override void OnAcceleratedPaint(
CefBrowser browser,
CefPaintElementType type,
CefRectangle[] dirtyRects,
in CefAcceleratedPaintInfo info)
{
// Unused, but we're forced to implement it so.. NOOP.
}

View File

@@ -5,6 +5,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>WinExe</OutputType>
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
<CETCompat>false</CETCompat>
</PropertyGroup>
<ItemGroup>

View File

@@ -453,6 +453,17 @@ public sealed partial class AudioSystem : SharedAudioSystem
return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio....
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayLocal(
SoundSpecifier? sound,
EntityUid source,
EntityUid? soundInitiator,
AudioParams? audioParams = null
)
{
return PlayPredicted(sound, source, soundInitiator, audioParams);
}
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null)
{
if (Timing.IsFirstTimePredicted && sound != null)
@@ -493,7 +504,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
var (entity, component) = CreateAndStartPlayingStream(audioParams, stream);
component.Global = true;
component.Source.Global = true;
Dirty(entity, component);
DirtyField(entity, component, nameof(AudioComponent.Global));
return (entity, component);
}
@@ -658,7 +669,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
// TODO clamp the offset inside of SetPlaybackPosition() itself.
var offset = audioP.PlayOffsetSeconds;
offset = Math.Clamp(offset, 0f, (float) stream.Length.TotalSeconds - 0.01f);
var maxOffset = Math.Max((float) stream.Length.TotalSeconds - 0.01f, 0f);
offset = Math.Clamp(offset, 0f, maxOffset);
source.PlaybackPosition = offset;
// For server we will rely on the adjusted one but locally we will have to adjust it ourselves.

View File

@@ -33,12 +33,6 @@ public interface IMidiRenderer : IDisposable
/// </summary>
bool LoopMidi { get; set; }
/// <summary>
/// This increases all note on velocities to 127.
/// </summary>
[Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")]
bool VolumeBoost { get; set; }
/// <summary>
/// The midi program (instrument) the renderer is using.
/// </summary>

View File

@@ -205,14 +205,6 @@ internal sealed class MidiRenderer : IMidiRenderer
}
}
[ViewVariables(VVAccess.ReadWrite)]
[Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")]
public bool VolumeBoost
{
get => VelocityOverride == 127;
set => VelocityOverride = value ? 127 : null;
}
[ViewVariables(VVAccess.ReadWrite)]
public EntityUid? TrackingEntity { get; set; } = null;

View File

@@ -88,10 +88,10 @@ namespace Robust.Client
{
if (GameInfo != null)
{
GameInfo.TickRate = (byte) tickrate;
GameInfo.TickRate = (ushort) tickrate;
}
_timing.SetTickRateAt((byte) tickrate, info.TickChanged);
_timing.SetTickRateAt((ushort) tickrate, info.TickChanged);
_logger.Info($"Tickrate changed to: {tickrate} on tick {_timing.CurTick}");
}
@@ -395,6 +395,6 @@ namespace Robust.Client
/// </summary>
public int ServerMaxPlayers { get; set; }
public byte TickRate { get; internal set; }
public uint TickRate { get; internal set; }
}
}

View File

@@ -8,6 +8,7 @@ using Robust.Client.GameObjects;
using Robust.Client.GameStates;
using Robust.Client.Graphics;
using Robust.Client.Graphics.Clyde;
using Robust.Client.HWId;
using Robust.Client.Input;
using Robust.Client.Map;
using Robust.Client.Placement;
@@ -26,6 +27,7 @@ using Robust.Client.Upload;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.RichText;
using Robust.Client.UserInterface.Themes;
using Robust.Client.UserInterface.XAML.Proxy;
using Robust.Client.Utility;
using Robust.Client.ViewVariables;
using Robust.Shared;
@@ -146,7 +148,18 @@ namespace Robust.Client
deps.Register<IConfigurationManagerInternal, ClientNetConfigurationManager>();
deps.Register<IClientNetConfigurationManager, ClientNetConfigurationManager>();
deps.Register<INetConfigurationManagerInternal, ClientNetConfigurationManager>();
#if TOOLS
deps.Register<IXamlProxyManager, XamlProxyManager>();
deps.Register<IXamlHotReloadManager, XamlHotReloadManager>();
#else
deps.Register<IXamlProxyManager, XamlProxyManagerStub>();
deps.Register<IXamlHotReloadManager, XamlHotReloadManagerStub>();
#endif
deps.Register<IXamlProxyHelper, XamlProxyHelper>();
deps.Register<MarkupTagManager>();
deps.Register<IHWId, BasicHWId>();
}
}
}

View File

@@ -579,7 +579,20 @@ namespace Robust.Client.Console.Commands
private static string GetMemberValue(MemberInfo? member, Control control, string separator, string
wrap = "{0}")
{
var value = member?.GetValue(control);
object? value = null;
try
{
value = member?.GetValue(control);
}
catch (TargetInvocationException exception)
{
var exceptionToPrint = exception.InnerException ?? exception;
value = $"{exceptionToPrint.GetType()}: {exceptionToPrint.Message}";
}
catch (Exception exception)
{
value = $"{exception.GetType()}: {exception.Message}";
}
var o = value switch
{
ICollection<Control> controls => string.Join(separator,

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.Console.Commands
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var type = Type.GetType(args[0]);
var type = GetType(args[0]);
if (type == null)
{
@@ -25,6 +25,17 @@ namespace Robust.Client.Console.Commands
shell.WriteLine(sig);
}
}
private Type? GetType(string name)
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.GetType(name) is { } type)
return type;
}
return null;
}
}
#endif
}

View File

@@ -20,8 +20,9 @@ namespace Robust.Client.Console.Commands
{
var wantName = args.Length > 0 ? args[0] : null;
var basePath = Path.GetDirectoryName(UserDataDir.GetUserDataDir(_gameController))!;
var dbPath = Path.Combine(basePath, "launcher", "settings.db");
var basePath = UserDataDir.GetRootUserDataDir(_gameController);
var launcherDirName = Environment.GetEnvironmentVariable("SS14_LAUNCHER_APPDATA_NAME") ?? "launcher";
var dbPath = Path.Combine(basePath, launcherDirName, "settings.db");
#if USE_SYSTEM_SQLITE
SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3());

View File

@@ -14,15 +14,6 @@ namespace Robust.Client.Credits
/// </summary>
public static class CreditsManager
{
/// <summary>
/// Gets a list of open source software used in the engine and their license.
/// </summary>
[Obsolete("Use overload that takes in an explicit resource manager instead.")]
public static IEnumerable<LicenseEntry> GetLicenses()
{
return GetLicenses(IoCManager.Resolve<IResourceManager>());
}
/// <summary>
/// Gets a list of open source software used in the engine and their license.
/// </summary>

View File

@@ -544,7 +544,7 @@ namespace Robust.Client.Debugging
switch (joint)
{
case DistanceJoint:
worldHandle.DrawLine(xf1, xf2, JointColor);
worldHandle.DrawLine(p1, p2, JointColor);
break;
case PrismaticJoint prisma:
var pA = Transform.Mul(xfa, joint.LocalAnchorA);

View File

@@ -23,6 +23,7 @@ namespace Robust.Client
private Thread? _gameThread;
private ISawmill _logger = default!;
[STAThread]
public static void Main(string[] args)
{
Start(args, new GameControllerOptions());

View File

@@ -19,6 +19,7 @@ using Robust.Client.State;
using Robust.Client.Upload;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.RichText;
using Robust.Client.UserInterface.XAML.Proxy;
using Robust.Client.Utility;
using Robust.Client.ViewVariables;
using Robust.Client.WebViewHook;
@@ -53,6 +54,8 @@ namespace Robust.Client
[Dependency] private readonly IResourceCacheInternal _resourceCache = default!;
[Dependency] private readonly IResourceManagerInternal _resManager = default!;
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly IXamlProxyManager _xamlProxyManager = default!;
[Dependency] private readonly IXamlHotReloadManager _xamlHotReloadManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IClientNetManager _networkManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
@@ -109,14 +112,28 @@ namespace Robust.Client
_commandLineArgs = args;
}
public string GameTitle()
{
return Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox";
}
public string WindowIconSet()
{
return Options.WindowIconSet?.ToString() ?? _resourceManifest!.WindowIconSet ?? "";
}
public string SplashLogo()
{
return Options.SplashLogo?.ToString() ?? _resourceManifest!.SplashLogo ?? "";
}
internal bool StartupContinue(DisplayMode displayMode)
{
DebugTools.AssertNotNull(_resourceManifest);
_clyde.InitializePostWindowing();
_audio.InitializePostWindowing();
_clyde.SetWindowTitle(
Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox");
_clyde.SetWindowTitle(GameTitle());
_taskManager.Initialize();
_parallelMgr.Initialize();
@@ -171,6 +188,8 @@ namespace Robust.Client
_reflectionManager.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDefaultPrototypes();
_xamlProxyManager.Initialize();
_xamlHotReloadManager.Initialize();
_userInterfaceManager.Initialize();
_eyeManager.Initialize();
_entityManager.Initialize();
@@ -394,10 +413,8 @@ namespace Robust.Client
// Handle GameControllerOptions implicit CVar overrides.
_configurationManager.OverrideConVars(new[]
{
(CVars.DisplayWindowIconSet.Name,
options.WindowIconSet?.ToString() ?? _resourceManifest.WindowIconSet ?? ""),
(CVars.DisplaySplashLogo.Name,
options.SplashLogo?.ToString() ?? _resourceManifest.SplashLogo ?? "")
(CVars.DisplayWindowIconSet.Name, WindowIconSet()),
(CVars.DisplaySplashLogo.Name, SplashLogo())
});
}

View File

@@ -99,15 +99,6 @@ namespace Robust.Client.GameObjects
Play(new Entity<AnimationPlayerComponent>(uid, component), animation, key);
}
/// <summary>
/// Start playing an animation.
/// </summary>
[Obsolete("Use Play(EntityUid<AnimationPlayerComponent> ent, Animation animation, string key) instead")]
public void Play(AnimationPlayerComponent component, Animation animation, string key)
{
Play(new Entity<AnimationPlayerComponent>(component.Owner, component), animation, key);
}
public void Play(Entity<AnimationPlayerComponent> ent, Animation animation, string key)
{
AddComponent(ent);
@@ -152,6 +143,14 @@ namespace Robust.Client.GameObjects
}
#endif
foreach (var track in animation.AnimationTracks)
{
if (track is not AnimationTrackSpriteFlick)
continue;
track.AdvancePlayback(ent.Owner, 0, 0, 0f);
}
ent.Comp.PlayingAnimations.Add(key, playback);
}

View File

@@ -26,6 +26,8 @@ namespace Robust.Client.GameObjects
protected override void OnAppearanceGetState(EntityUid uid, AppearanceComponent component, ref ComponentGetState args)
{
// TODO Game State
// Force the client to serialize & de-serialize implicitly generated component states.
var clone = CloneAppearanceData(component.AppearanceData);
args.State = new AppearanceComponentState(clone);
}

View File

@@ -5,7 +5,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Utility;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -17,7 +16,6 @@ namespace Robust.Client.GameObjects
{
public sealed class ContainerSystem : SharedContainerSystem
{
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
[Dependency] private readonly PointLightSystem _lightSys = default!;
@@ -242,7 +240,7 @@ namespace Robust.Client.GameObjects
#if DEBUG
var uid = GetEntity(netEntity);
if (TryComp<MetaDataComponent>(uid, out var meta))
if (TryComp(uid, out MetaDataComponent? meta))
{
DebugTools.Assert((meta.Flags & ( MetaDataFlags.Detached | MetaDataFlags.InContainer) ) == MetaDataFlags.Detached,
$"Adding entity {ToPrettyString(uid)} to list of expected entities for container {container.ID} in {ToPrettyString(container.Owner)}, despite it already being in a container.");

View File

@@ -82,6 +82,7 @@ namespace Robust.Client.GameObjects
var viewport = args.WorldBounds;
var worldHandle = args.WorldHandle;
var fixturesQuery = _entityManager.GetEntityQuery<FixturesComponent>();
_grids.Clear();
_mapManager.FindGridsIntersecting(currentMap, viewport, ref _grids);
foreach (var grid in _grids)
@@ -89,13 +90,15 @@ namespace Robust.Client.GameObjects
var worldMatrix = _transformSystem.GetWorldMatrix(grid);
worldHandle.SetTransform(worldMatrix);
var transform = new Transform(Vector2.Zero, Angle.Zero);
var fixtures = fixturesQuery.Comp(grid.Owner);
var chunkEnumerator = _mapSystem.GetMapChunks(grid.Owner, grid.Comp, viewport);
while (chunkEnumerator.MoveNext(out var chunk))
{
foreach (var fixture in chunk.Fixtures.Values)
foreach (var id in chunk.Fixtures)
{
var fixture = fixtures.Fixtures[id];
var poly = (PolygonShape) fixture.Shape;
var verts = new Vector2[poly.VertexCount];

View File

@@ -20,7 +20,6 @@ namespace Robust.Client.GameObjects
/// </summary>
public sealed class InputSystem : SharedInputSystem, IPostInjectInit
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientGameStateManager _stateManager = default!;

View File

@@ -0,0 +1,43 @@
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
namespace Robust.Client.GameObjects;
public sealed partial class SpriteSystem
{
/// <summary>
/// Gets an entity's sprite position in world terms.
/// </summary>
public Vector2 GetSpriteWorldPosition(Entity<SpriteComponent?, TransformComponent?> entity)
{
if (!Resolve(entity, ref entity.Comp2))
return Vector2.Zero;
var (worldPos, worldRot) = _xforms.GetWorldPositionRotation(entity.Owner);
if (!Resolve(entity, ref entity.Comp1, false))
{
return worldPos;
}
if (entity.Comp1.NoRotation)
{
return worldPos + entity.Comp1.Offset;
}
return worldPos + worldRot.RotateVec(entity.Comp1.Rotation.RotateVec(entity.Comp1.Offset));
}
/// <summary>
/// Gets an entity's sprite position in screen coordinates.
/// </summary>
public ScreenCoordinates GetSpriteScreenCoordinates(Entity<SpriteComponent?, TransformComponent?> entity)
{
if (!Resolve(entity, ref entity.Comp2))
return ScreenCoordinates.Invalid;
var spriteCoords = GetSpriteWorldPosition(entity);
return _eye.MapToScreen(new MapCoordinates(spriteCoords, entity.Comp2.MapID));
}
}

View File

@@ -30,10 +30,12 @@ namespace Robust.Client.GameObjects
public sealed partial class SpriteSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IEyeManager _eye = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly SharedTransformSystem _xforms = default!;
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();

View File

@@ -0,0 +1,5 @@
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects;
public sealed class ViewSubscriberSystem : SharedViewSubscriberSystem;

View File

@@ -399,7 +399,7 @@ namespace Robust.Client.GameStates
using (_prof.Group("MergeImplicitData"))
{
MergeImplicitData(createdEntities);
GenerateImplicitStates(createdEntities);
}
if (_lastProcessedInput < curState.LastProcessedInput)
@@ -671,7 +671,7 @@ namespace Robust.Client.GameStates
/// initial server state for any newly created entity. It does this by simply using the standard <see
/// cref="IEntityManager.GetComponentState"/>.
/// </remarks>
private void MergeImplicitData(IEnumerable<NetEntity> createdEntities)
public void GenerateImplicitStates(IEnumerable<NetEntity> createdEntities)
{
var bus = _entityManager.EventBus;
@@ -960,7 +960,7 @@ namespace Robust.Client.GameStates
// Initialize and start the newly created entities.
if (_toCreate.Count > 0)
InitializeAndStart(_toCreate);
InitializeAndStart(_toCreate, metas, xforms);
_prof.WriteValue("State Size", ProfData.Int32(curSpan.Length));
_prof.WriteValue("Entered PVS", ProfData.Int32(enteringPvs));
@@ -1188,7 +1188,10 @@ namespace Robust.Client.GameStates
}
}
private void InitializeAndStart(Dictionary<NetEntity, EntityState> toCreate)
private void InitializeAndStart(
Dictionary<NetEntity, EntityState> toCreate,
EntityQuery<MetaDataComponent> metas,
EntityQuery<TransformComponent> xforms)
{
_toStart.Clear();
@@ -1197,22 +1200,8 @@ namespace Robust.Client.GameStates
EntityUid entity = default;
foreach (var netEntity in toCreate.Keys)
{
try
{
(entity, var meta) = _entityManager.GetEntityData(netEntity);
_entities.InitializeEntity(entity, meta);
_toStart.Add((entity, netEntity));
}
catch (Exception e)
{
_sawmill.Error($"Server entity threw in Init: nent={netEntity}, ent={_entities.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
_toCreate.Remove(netEntity);
_brokenEnts.Add(entity);
#if !EXCEPTION_TOLERANCE
throw;
#endif
}
(entity, var meta) = _entityManager.GetEntityData(netEntity);
InitializeRecursive(entity, meta, metas, xforms);
}
}
@@ -1244,6 +1233,44 @@ namespace Robust.Client.GameStates
_brokenEnts.Clear();
}
private void InitializeRecursive(
EntityUid entity,
MetaDataComponent meta,
EntityQuery<MetaDataComponent> metas,
EntityQuery<TransformComponent> xforms)
{
var xform = xforms.GetComponent(entity);
if (xform.ParentUid is {Valid: true} parent)
{
var parentMeta = metas.GetComponent(parent);
if (parentMeta.EntityLifeStage < EntityLifeStage.Initialized)
InitializeRecursive(parent, parentMeta, metas, xforms);
}
if (meta.EntityLifeStage >= EntityLifeStage.Initialized)
{
// Was probably already initialized because one of its children appeared earlier in the list.
DebugTools.AssertEqual(_toStart.Count(x => x.Item1 == entity), 1);
return;
}
try
{
_entities.InitializeEntity(entity, meta);
_toStart.Add((entity, meta.NetEntity));
}
catch (Exception e)
{
_sawmill.Error($"Server entity threw in Init: nent={meta.NetEntity}, ent={_entities.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
_toCreate.Remove(meta.NetEntity);
_brokenEnts.Add(entity);
#if !EXCEPTION_TOLERANCE
throw;
#endif
}
}
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
EntityState? nextState, GameTick lastApplied, GameTick toTick, bool enteringPvs)
{

View File

@@ -34,9 +34,6 @@ namespace Robust.Client.GameStates
/// </summary>
int GetApplicableStateCount();
[Obsolete("use GetApplicableStateCount()")]
int CurrentBufferSize => GetApplicableStateCount();
/// <summary>
/// Total number of game states currently in the state buffer.
/// </summary>
@@ -85,6 +82,12 @@ namespace Robust.Client.GameStates
/// </summary>
IEnumerable<NetEntity> ApplyGameState(GameState curState, GameState? nextState);
/// <summary>
/// Generates implicit component states for newly created entities.
/// This should always be called after running <see cref="ApplyGameState(GameState, GameState)"/>.
/// </summary>
void GenerateImplicitStates(IEnumerable<NetEntity> states);
/// <summary>
/// Resets any entities that have changed while predicting future ticks.
/// </summary>

View File

@@ -26,6 +26,7 @@ namespace Robust.Client.GameStates
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly IConsoleHost _host = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
private const int HistorySize = 60 * 5; // number of ticks to keep in history.
@@ -46,7 +47,6 @@ namespace Robust.Client.GameStates
// sum of all data point sizes in bytes
private int _totalHistoryPayload;
private int _totalUncompressed;
public EntityUid WatchEntId { get; set; }
@@ -79,7 +79,7 @@ namespace Robust.Client.GameStates
string? entStateString = null;
string? entDelString = null;
var conShell = IoCManager.Resolve<IConsoleHost>().LocalShell;
var conShell = _host.LocalShell;
var entStates = args.AppliedState.EntityStates;
if (entStates.HasContents)

View File

@@ -15,7 +15,6 @@ namespace Robust.Client.GameStates
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly EntityLookupSystem _lookup;

View File

@@ -109,6 +109,9 @@ namespace Robust.Client.Graphics.Clyde
private void SendWindowResized(WindowReg reg, Vector2i oldSize)
{
if (!reg.IsVisible) // Only send this for open windows
return;
var loaded = RtToLoaded(reg.RenderTarget);
loaded.Size = reg.FramebufferSize;

View File

@@ -215,8 +215,6 @@ namespace Robust.Client.Graphics.Clyde
}
}
_overlays.Sort(OverlayComparer.Instance);
return _overlays;
}
@@ -574,17 +572,5 @@ namespace Robust.Client.Graphics.Clyde
return new Box2Rotated(aabb, rotation, aabb.Center);
}
private sealed class OverlayComparer : IComparer<Overlay>
{
public static readonly OverlayComparer Instance = new();
public int Compare(Overlay? x, Overlay? y)
{
var zX = x?.ZIndex ?? 0;
var zY = y?.ZIndex ?? 0;
return zX.CompareTo(zY);
}
}
}
}

View File

@@ -362,6 +362,11 @@ namespace Robust.Client.Graphics.Clyde
rect.BottomLeft, rect.BottomRight, color, subRegion);
}
public override void DrawTexture(Texture texture, Vector2 position, Color? modulate = null)
{
base.DrawTexture(texture, position, modulate);
}
/// <summary>
/// Draws an entity.
/// </summary>

View File

@@ -496,6 +496,9 @@ namespace Robust.Client.Graphics.Clyde
case bool b:
program.SetUniform(name, b ? 1 : 0);
break;
case bool[] bArr:
program.SetUniform(name, bArr);
break;
case Matrix3x2 matrix3:
program.SetUniform(name, matrix3);
break;

View File

@@ -506,6 +506,14 @@ namespace Robust.Client.Graphics.Clyde
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, bool[] value)
{
var data = Parent._shaderInstances[Handle];
data.ParametersDirty = true;
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, in Matrix3x2 value)
{
var data = Parent._shaderInstances[Handle];

View File

@@ -109,8 +109,8 @@ namespace Robust.Client.Graphics.Clyde
case "glfw":
winImpl = new GlfwWindowingImpl(this, _deps);
break;
case "sdl2":
winImpl = new Sdl2WindowingImpl(this, _deps);
case "sdl3":
winImpl = new Sdl3WindowingImpl(this, _deps);
break;
default:
_logManager.GetSawmill("clyde.win").Log(
@@ -343,6 +343,8 @@ namespace Robust.Client.Graphics.Clyde
if (isMain)
_mainWindow = reg;
reg.IsVisible = parameters.Visible;
_windows.Add(reg);
_windowHandles.Add(reg.Handle);
@@ -444,6 +446,12 @@ namespace Robust.Client.Graphics.Clyde
_windowing!.CursorSet(_mainWindow!, cursor);
}
private void SetWindowSize(WindowReg reg, Vector2i size)
{
DebugTools.AssertNotNull(_windowing);
_windowing!.WindowSetSize(reg, size);
}
private void SetWindowVisible(WindowReg reg, bool visible)
{
@@ -459,26 +467,7 @@ namespace Robust.Client.Graphics.Clyde
_windowing!.RunOnWindowThread(a);
}
public void TextInputSetRect(UIBox2i rect)
{
DebugTools.AssertNotNull(_windowing);
_windowing!.TextInputSetRect(rect);
}
public void TextInputStart()
{
DebugTools.AssertNotNull(_windowing);
_windowing!.TextInputStart();
}
public void TextInputStop()
{
DebugTools.AssertNotNull(_windowing);
_windowing!.TextInputStop();
}
public IFileDialogManager? FileDialogImpl => _windowing as IFileDialogManager;
private abstract class WindowReg
{
@@ -533,7 +522,11 @@ namespace Robust.Client.Graphics.Clyde
_clyde.DoDestroyWindow(Reg);
}
public Vector2i Size => Reg.FramebufferSize;
public Vector2i Size
{
get => Reg.FramebufferSize;
set => _clyde.SetWindowSize(Reg, value);
}
public IRenderTarget RenderTarget => Reg.RenderTarget;
@@ -578,6 +571,27 @@ namespace Robust.Client.Graphics.Clyde
remove => Reg.Resized -= value;
}
public void TextInputSetRect(UIBox2i rect, int cursor)
{
DebugTools.AssertNotNull(_clyde._windowing);
_clyde._windowing!.TextInputSetRect(Reg, rect, cursor);
}
public void TextInputStart()
{
DebugTools.AssertNotNull(_clyde._windowing);
_clyde._windowing!.TextInputStart(Reg);
}
public void TextInputStop()
{
DebugTools.AssertNotNull(_clyde._windowing);
_clyde._windowing!.TextInputStop(Reg);
}
public nint? WindowsHWnd => _clyde._windowing!.WindowGetWin32Window(Reg);
}

View File

@@ -32,7 +32,6 @@ namespace Robust.Client.Graphics.Clyde
internal sealed partial class Clyde : IClydeInternal, IPostInjectInit, IEntityEventSubscriber
{
[Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly ILightManager _lightManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;

View File

@@ -7,6 +7,7 @@ using JetBrains.Annotations;
using Robust.Client.Audio;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
@@ -284,6 +285,8 @@ namespace Robust.Client.Graphics.Clyde
action();
}
public IFileDialogManager? FileDialogImpl => null;
private sealed class DummyCursor : ICursor
{
public void Dispose()
@@ -361,6 +364,10 @@ namespace Robust.Client.Graphics.Clyde
{
}
private protected override void SetParameterImpl(string name, bool[] value)
{
}
private protected override void SetParameterImpl(string name, in Matrix3x2 value)
{
}
@@ -513,7 +520,7 @@ namespace Robust.Client.Graphics.Clyde
RenderTarget = renderTarget;
}
public Vector2i Size { get; } = default;
public Vector2i Size { get; set; } = default;
public bool IsDisposed { get; private set; }
public WindowId Id { get; set; }
public IRenderTarget RenderTarget { get; }
@@ -527,6 +534,21 @@ namespace Robust.Client.Graphics.Clyde
public event Action<WindowDestroyedEventArgs>? Destroyed;
public event Action<WindowResizedEventArgs>? Resized { add { } remove { } }
public void TextInputSetRect(UIBox2i rect, int cursor)
{
// Nop.
}
public void TextInputStart()
{
// Nop.
}
public void TextInputStop()
{
// Nop.
}
public void MaximizeOnMonitor(IClydeMonitor monitor)
{
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Channels;
@@ -176,6 +176,7 @@ namespace Robust.Client.Graphics.Clyde
window.BlitDoneEvent!.Reset();
window.BlitStartEvent!.Set();
window.BlitDoneEvent.Wait();
window.UnlockBeforeSwap = Clyde._cfg.GetCVar(CVars.DisplayThreadUnlockBeforeSwap);
}
}
else
@@ -212,8 +213,15 @@ namespace Robust.Client.Graphics.Clyde
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
Clyde.CheckGlError();
window.BlitDoneEvent?.Set();
if (window.UnlockBeforeSwap)
{
window.BlitDoneEvent?.Set();
}
Clyde._windowing!.WindowSwapBuffers(window.Reg);
if (!window.UnlockBeforeSwap)
{
window.BlitDoneEvent?.Set();
}
}
private unsafe void BlitThreadInit(WindowData reg)
@@ -336,6 +344,7 @@ namespace Robust.Client.Graphics.Clyde
public Thread? BlitThread;
public ManualResetEventSlim? BlitStartEvent;
public ManualResetEventSlim? BlitDoneEvent;
public bool UnlockBeforeSwap;
}
}
}

View File

@@ -418,6 +418,37 @@ namespace Robust.Client.Graphics.Clyde
}
}
public void SetUniform(string uniformName, bool[] bools)
{
var uniformId = GetUniform(uniformName);
SetUniformDirect(uniformId, bools);
}
public void SetUniform(int uniformName, bool[] bools)
{
var uniformId = GetUniform(uniformName);
SetUniformDirect(uniformId, bools);
}
private void SetUniformDirect(int slot, bool[] bools)
{
Span<int> intBools = stackalloc int[bools.Length];
for (var i = 0; i < bools.Length; i++)
{
intBools[i] = bools[i] ? 1 : 0;
}
unsafe
{
fixed (int* intBoolsPtr = intBools)
{
GL.Uniform1(slot, bools.Length, intBoolsPtr);
_clyde.CheckGlError();
}
}
}
public void SetUniformTexture(string uniformName, TextureUnit textureUnit)
{
var uniformId = GetUniform(uniformName);

View File

@@ -96,7 +96,8 @@ namespace Robust.Client.Graphics.Clyde
private void ProcessEventChar(EventChar ev)
{
if (!_textInputActive)
var windowReg = FindWindow(ev.Window);
if (windowReg is not { TextInputActive: true })
return;
_clyde.SendText(new TextEnteredEventArgs(new Rune(ev.CodePoint).ToString()));

View File

@@ -85,6 +85,10 @@ namespace Robust.Client.Graphics.Clyde
WinThreadWinSetMonitor(cmd);
break;
case CmdWinSetSize cmd:
WinThreadWinSetSize(cmd);
break;
case CmdWinSetVisible cmd:
WinThreadWinSetVisible(cmd);
break;
@@ -234,6 +238,11 @@ namespace Robust.Client.Graphics.Clyde
nint Window
) : CmdBase;
private sealed record CmdWinSetSize(
nint Window,
int W, int H
) : CmdBase;
private sealed record CmdWinSetVisible(
nint Window,
bool Visible

View File

@@ -84,6 +84,13 @@ namespace Robust.Client.Graphics.Clyde
);
}
public void WindowSetSize(WindowReg window, Vector2i size)
{
var reg = (GlfwWindowReg) window;
SendCmd(new CmdWinSetSize((nint) reg.GlfwWindow, size.X, size.Y));
}
public void WindowSetVisible(WindowReg window, bool visible)
{
var reg = (GlfwWindowReg) window;
@@ -92,6 +99,13 @@ namespace Robust.Client.Graphics.Clyde
SendCmd(new CmdWinSetVisible((nint) reg.GlfwWindow, visible));
}
private void WinThreadWinSetSize(CmdWinSetSize cmd)
{
var win = (Window*) cmd.Window;
GLFW.SetWindowSize(win, cmd.W, cmd.H);
}
private void WinThreadWinSetVisible(CmdWinSetVisible cmd)
{
var win = (Window*) cmd.Window;
@@ -454,6 +468,8 @@ namespace Robust.Client.Graphics.Clyde
GLFW.WindowHint(WindowHintInt.AlphaBits, 8);
GLFW.WindowHint(WindowHintInt.StencilBits, 8);
GLFW.WindowHint(WindowHintBool.Decorated, (parameters.Styles & OSWindowStyles.NoTitleBar) == 0);
var window = GLFW.CreateWindow(
parameters.Width, parameters.Height,
parameters.Title,
@@ -471,23 +487,12 @@ namespace Robust.Client.Graphics.Clyde
GLFW.MaximizeWindow(window);
}
if ((parameters.Styles & OSWindowStyles.NoTitleBar) != 0)
{
GLFW.WindowHint(WindowHintBool.Decorated, false);
}
if ((parameters.Styles & OSWindowStyles.NoTitleOptions) != 0)
{
if (OperatingSystem.IsWindows())
{
var hWnd = (HWND) GLFW.GetWin32Window(window);
DebugTools.Assert(hWnd != HWND.NULL);
Windows.SetWindowLongPtrW(
hWnd,
GWL.GWL_STYLE,
// Cast to long here to work around a bug in rider with nint bitwise operators.
(nint)((long)Windows.GetWindowLongPtrW(hWnd, GWL.GWL_STYLE) & ~WS.WS_SYSMENU));
WsiShared.SetWindowStyleNoTitleOptionsWindows(hWnd);
}
else if (OperatingSystem.IsLinux())
{
@@ -495,23 +500,7 @@ namespace Robust.Client.Graphics.Clyde
{
var x11Window = (X11Window)GLFW.GetX11Window(window);
var x11Display = (Display*) GLFW.GetX11Display(window);
DebugTools.Assert(x11Window != X11Window.NULL);
// https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html#idm46181547486832
var newPropValString = Marshal.StringToCoTaskMemUTF8("_NET_WM_WINDOW_TYPE_DIALOG");
var newPropVal = Xlib.XInternAtom(x11Display, (sbyte*)newPropValString, Xlib.False);
DebugTools.Assert(newPropVal != Atom.NULL);
var propNameString = Marshal.StringToCoTaskMemUTF8("_NET_WM_WINDOW_TYPE");
#pragma warning disable CA1806
// [display] [window] [property] [type] [format (8, 16,32)] [mode] [data] [element count]
Xlib.XChangeProperty(x11Display, x11Window,
Xlib.XInternAtom(x11Display, (sbyte*)propNameString, Xlib.False), // should never be null; part of spec
Xlib.XA_ATOM, 32, Xlib.PropModeReplace,
(byte*)&newPropVal, 1);
#pragma warning restore CA1806
Marshal.FreeCoTaskMem(newPropValString);
Marshal.FreeCoTaskMem(propNameString);
WsiShared.SetWindowStyleNoTitleOptionsX11(x11Display, x11Window);
}
catch (EntryPointNotFoundException)
{
@@ -629,16 +618,16 @@ namespace Robust.Client.Graphics.Clyde
return reg;
}
private WindowReg? FindWindow(nint window) => FindWindow((Window*) window);
private GlfwWindowReg? FindWindow(nint window) => FindWindow((Window*) window);
private WindowReg? FindWindow(Window* window)
private GlfwWindowReg? FindWindow(Window* window)
{
foreach (var windowReg in _clyde._windows)
{
var glfwReg = (GlfwWindowReg) windowReg;
if (glfwReg.GlfwWindow == window)
{
return windowReg;
return glfwReg;
}
}
@@ -725,23 +714,23 @@ namespace Robust.Client.Graphics.Clyde
return (void*) GLFW.GetProcAddress(procName);
}
public void TextInputSetRect(UIBox2i rect)
public void TextInputSetRect(WindowReg reg, UIBox2i rect, int cursor)
{
// Not supported on GLFW.
}
public void TextInputStart()
public void TextInputStart(WindowReg reg)
{
// Not properly supported on GLFW.
_textInputActive = true;
((GlfwWindowReg)reg).TextInputActive = true;
}
public void TextInputStop()
public void TextInputStop(WindowReg reg)
{
// Not properly supported on GLFW.
_textInputActive = false;
((GlfwWindowReg)reg).TextInputActive = false;
}
private void CheckWindowDisposed(WindowReg reg)
@@ -756,6 +745,10 @@ namespace Robust.Client.Graphics.Clyde
// Kept around to avoid it being GCd.
public CursorImpl? Cursor;
// While GLFW does not provide proper IME APIs, we can at least emulate SDL3's StartTextInput() system.
// This will ensure some level of consistency between the backends.
public bool TextInputActive;
}
}
}

View File

@@ -25,10 +25,6 @@ namespace Robust.Client.Graphics.Clyde
private bool _glfwInitialized;
private bool _win32Experience;
// While GLFW does not provide proper IME APIs, we can at least emulate SDL2's StartTextInput() system.
// This will ensure some level of consistency between the backends.
private bool _textInputActive;
public GlfwWindowingImpl(Clyde clyde, IDependencyCollection deps)
{
_clyde = clyde;

View File

@@ -39,6 +39,7 @@ namespace Robust.Client.Graphics.Clyde
void WindowDestroy(WindowReg reg);
void WindowSetTitle(WindowReg window, string title);
void WindowSetMonitor(WindowReg window, IClydeMonitor monitor);
void WindowSetSize(WindowReg window, Vector2i size);
void WindowSetVisible(WindowReg window, bool visible);
void WindowRequestAttention(WindowReg window);
void WindowSwapBuffers(WindowReg window);
@@ -65,9 +66,9 @@ namespace Robust.Client.Graphics.Clyde
void RunOnWindowThread(Action a);
// IME
void TextInputSetRect(UIBox2i rect);
void TextInputStart();
void TextInputStop();
void TextInputSetRect(WindowReg reg, UIBox2i rect, int cursor);
void TextInputStart(WindowReg reg);
void TextInputStop(WindowReg reg);
string GetDescription();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
/* 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>
*
*/

View File

@@ -0,0 +1,56 @@
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

View File

@@ -1,224 +0,0 @@
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using Robust.Client.Input;
using Robust.Shared;
using static SDL2.SDL;
using static SDL2.SDL.SDL_Scancode;
using Key = Robust.Client.Input.Keyboard.Key;
using Button = Robust.Client.Input.Mouse.Button;
namespace Robust.Client.Graphics.Clyde;
internal partial class Clyde
{
private sealed partial class Sdl2WindowingImpl
{
// Indices are values of SDL_Scancode
private static readonly Key[] KeyMap;
private static readonly FrozenDictionary<Key, SDL_Scancode> KeyMapReverse;
private static readonly Button[] MouseButtonMap;
// TODO: to avoid having to ask the windowing thread, key names are cached.
private readonly Dictionary<Key, string> _printableKeyNameMap = new();
private void ReloadKeyMap()
{
// This may be ran concurrently from the windowing thread.
lock (_printableKeyNameMap)
{
_printableKeyNameMap.Clear();
// List of mappable keys from SDL2's source appears to be:
// entries in SDL_default_keymap that aren't an SDLK_ enum reference.
// (the actual logic is more nuanced, but it appears to match the above)
// Comes out to these two ranges:
for (var k = SDL_SCANCODE_A; k <= SDL_SCANCODE_0; k++)
{
CacheKey(k);
}
for (var k = SDL_SCANCODE_MINUS; k <= SDL_SCANCODE_SLASH; k++)
{
CacheKey(k);
}
void CacheKey(SDL_Scancode scancode)
{
var rKey = ConvertSdl2Scancode(scancode);
if (rKey == Key.Unknown)
return;
var name = SDL_GetKeyName(SDL_GetKeyFromScancode(scancode));
if (!string.IsNullOrEmpty(name))
_printableKeyNameMap.Add(rKey, name);
}
}
}
public string? KeyGetName(Key key)
{
lock (_printableKeyNameMap)
{
if (_printableKeyNameMap.TryGetValue(key, out var name))
return name;
return null;
}
}
internal static Key ConvertSdl2Scancode(SDL_Scancode scancode)
{
return KeyMap[(int) scancode];
}
public static Button ConvertSdl2Button(int button)
{
return MouseButtonMap[button];
}
static Sdl2WindowingImpl()
{
MouseButtonMap = new Button[6];
MouseButtonMap[SDL_BUTTON_LEFT] = Button.Left;
MouseButtonMap[SDL_BUTTON_RIGHT] = Button.Right;
MouseButtonMap[SDL_BUTTON_MIDDLE] = Button.Middle;
MouseButtonMap[SDL_BUTTON_X1] = Button.Button4;
MouseButtonMap[SDL_BUTTON_X2] = Button.Button5;
KeyMap = new Key[(int) SDL_NUM_SCANCODES];
MapKey(SDL_SCANCODE_A, Key.A);
MapKey(SDL_SCANCODE_B, Key.B);
MapKey(SDL_SCANCODE_C, Key.C);
MapKey(SDL_SCANCODE_D, Key.D);
MapKey(SDL_SCANCODE_E, Key.E);
MapKey(SDL_SCANCODE_F, Key.F);
MapKey(SDL_SCANCODE_G, Key.G);
MapKey(SDL_SCANCODE_H, Key.H);
MapKey(SDL_SCANCODE_I, Key.I);
MapKey(SDL_SCANCODE_J, Key.J);
MapKey(SDL_SCANCODE_K, Key.K);
MapKey(SDL_SCANCODE_L, Key.L);
MapKey(SDL_SCANCODE_M, Key.M);
MapKey(SDL_SCANCODE_N, Key.N);
MapKey(SDL_SCANCODE_O, Key.O);
MapKey(SDL_SCANCODE_P, Key.P);
MapKey(SDL_SCANCODE_Q, Key.Q);
MapKey(SDL_SCANCODE_R, Key.R);
MapKey(SDL_SCANCODE_S, Key.S);
MapKey(SDL_SCANCODE_T, Key.T);
MapKey(SDL_SCANCODE_U, Key.U);
MapKey(SDL_SCANCODE_V, Key.V);
MapKey(SDL_SCANCODE_W, Key.W);
MapKey(SDL_SCANCODE_X, Key.X);
MapKey(SDL_SCANCODE_Y, Key.Y);
MapKey(SDL_SCANCODE_Z, Key.Z);
MapKey(SDL_SCANCODE_0, Key.Num0);
MapKey(SDL_SCANCODE_1, Key.Num1);
MapKey(SDL_SCANCODE_2, Key.Num2);
MapKey(SDL_SCANCODE_3, Key.Num3);
MapKey(SDL_SCANCODE_4, Key.Num4);
MapKey(SDL_SCANCODE_5, Key.Num5);
MapKey(SDL_SCANCODE_6, Key.Num6);
MapKey(SDL_SCANCODE_7, Key.Num7);
MapKey(SDL_SCANCODE_8, Key.Num8);
MapKey(SDL_SCANCODE_9, Key.Num9);
MapKey(SDL_SCANCODE_KP_0, Key.NumpadNum0);
MapKey(SDL_SCANCODE_KP_1, Key.NumpadNum1);
MapKey(SDL_SCANCODE_KP_2, Key.NumpadNum2);
MapKey(SDL_SCANCODE_KP_3, Key.NumpadNum3);
MapKey(SDL_SCANCODE_KP_4, Key.NumpadNum4);
MapKey(SDL_SCANCODE_KP_5, Key.NumpadNum5);
MapKey(SDL_SCANCODE_KP_6, Key.NumpadNum6);
MapKey(SDL_SCANCODE_KP_7, Key.NumpadNum7);
MapKey(SDL_SCANCODE_KP_8, Key.NumpadNum8);
MapKey(SDL_SCANCODE_KP_9, Key.NumpadNum9);
MapKey(SDL_SCANCODE_ESCAPE, Key.Escape);
MapKey(SDL_SCANCODE_LCTRL, Key.Control);
MapKey(SDL_SCANCODE_RCTRL, Key.Control);
MapKey(SDL_SCANCODE_RSHIFT, Key.Shift);
MapKey(SDL_SCANCODE_LSHIFT, Key.Shift);
MapKey(SDL_SCANCODE_LALT, Key.Alt);
MapKey(SDL_SCANCODE_RALT, Key.Alt);
MapKey(SDL_SCANCODE_LGUI, Key.LSystem);
MapKey(SDL_SCANCODE_RGUI, Key.RSystem);
MapKey(SDL_SCANCODE_MENU, Key.Menu);
MapKey(SDL_SCANCODE_LEFTBRACKET, Key.LBracket);
MapKey(SDL_SCANCODE_RIGHTBRACKET, Key.RBracket);
MapKey(SDL_SCANCODE_SEMICOLON, Key.SemiColon);
MapKey(SDL_SCANCODE_COMMA, Key.Comma);
MapKey(SDL_SCANCODE_PERIOD, Key.Period);
MapKey(SDL_SCANCODE_APOSTROPHE, Key.Apostrophe);
MapKey(SDL_SCANCODE_SLASH, Key.Slash);
MapKey(SDL_SCANCODE_BACKSLASH, Key.BackSlash);
MapKey(SDL_SCANCODE_GRAVE, Key.Tilde);
MapKey(SDL_SCANCODE_EQUALS, Key.Equal);
MapKey(SDL_SCANCODE_SPACE, Key.Space);
MapKey(SDL_SCANCODE_RETURN, Key.Return);
MapKey(SDL_SCANCODE_KP_ENTER, Key.NumpadEnter);
MapKey(SDL_SCANCODE_BACKSPACE, Key.BackSpace);
MapKey(SDL_SCANCODE_TAB, Key.Tab);
MapKey(SDL_SCANCODE_PAGEUP, Key.PageUp);
MapKey(SDL_SCANCODE_PAGEDOWN, Key.PageDown);
MapKey(SDL_SCANCODE_END, Key.End);
MapKey(SDL_SCANCODE_HOME, Key.Home);
MapKey(SDL_SCANCODE_INSERT, Key.Insert);
MapKey(SDL_SCANCODE_DELETE, Key.Delete);
MapKey(SDL_SCANCODE_MINUS, Key.Minus);
MapKey(SDL_SCANCODE_KP_PLUS, Key.NumpadAdd);
MapKey(SDL_SCANCODE_KP_MINUS, Key.NumpadSubtract);
MapKey(SDL_SCANCODE_KP_DIVIDE, Key.NumpadDivide);
MapKey(SDL_SCANCODE_KP_MULTIPLY, Key.NumpadMultiply);
MapKey(SDL_SCANCODE_KP_DECIMAL, Key.NumpadDecimal);
MapKey(SDL_SCANCODE_LEFT, Key.Left);
MapKey(SDL_SCANCODE_RIGHT, Key.Right);
MapKey(SDL_SCANCODE_UP, Key.Up);
MapKey(SDL_SCANCODE_DOWN, Key.Down);
MapKey(SDL_SCANCODE_F1, Key.F1);
MapKey(SDL_SCANCODE_F2, Key.F2);
MapKey(SDL_SCANCODE_F3, Key.F3);
MapKey(SDL_SCANCODE_F4, Key.F4);
MapKey(SDL_SCANCODE_F5, Key.F5);
MapKey(SDL_SCANCODE_F6, Key.F6);
MapKey(SDL_SCANCODE_F7, Key.F7);
MapKey(SDL_SCANCODE_F8, Key.F8);
MapKey(SDL_SCANCODE_F9, Key.F9);
MapKey(SDL_SCANCODE_F10, Key.F10);
MapKey(SDL_SCANCODE_F11, Key.F11);
MapKey(SDL_SCANCODE_F12, Key.F12);
MapKey(SDL_SCANCODE_F13, Key.F13);
MapKey(SDL_SCANCODE_F14, Key.F14);
MapKey(SDL_SCANCODE_F15, Key.F15);
MapKey(SDL_SCANCODE_F16, Key.F16);
MapKey(SDL_SCANCODE_F17, Key.F17);
MapKey(SDL_SCANCODE_F18, Key.F18);
MapKey(SDL_SCANCODE_F19, Key.F19);
MapKey(SDL_SCANCODE_F20, Key.F20);
MapKey(SDL_SCANCODE_F21, Key.F21);
MapKey(SDL_SCANCODE_F22, Key.F22);
MapKey(SDL_SCANCODE_F23, Key.F23);
MapKey(SDL_SCANCODE_F24, Key.F24);
MapKey(SDL_SCANCODE_PAUSE, Key.Pause);
var keyMapReverse = new Dictionary<Key, SDL_Scancode>();
for (var code = 0; code < KeyMap.Length; code++)
{
var key = KeyMap[code];
if (key != Key.Unknown)
keyMapReverse[key] = (SDL_Scancode) code;
}
KeyMapReverse = keyMapReverse.ToFrozenDictionary();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void MapKey(SDL_Scancode code, Key key)
{
KeyMap[(int)code] = key;
}
}
}
}

View File

@@ -1,133 +0,0 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using SDL2;
namespace Robust.Client.Graphics.Clyde;
internal partial class Clyde
{
private sealed partial class Sdl2WindowingImpl
{
// NOTE: SDL2 calls them "displays". GLFW calls them monitors. GLFW's is the one I'm going with.
// Can't use ClydeHandle because it's not thread safe to allocate.
private int _nextMonitorId = 1;
private readonly Dictionary<int, WinThreadMonitorReg> _winThreadMonitors = new();
private readonly Dictionary<int, Sdl2MonitorReg> _monitors = new();
private void InitMonitors()
{
var numDisplays = SDL.SDL_GetNumVideoDisplays();
for (var i = 0; i < numDisplays; i++)
{
// SDL.SDL_GetDisplayDPI(i, out var ddpi, out var hdpi, out var vdpi);
// _sawmill.Info($"[{i}] {ddpi} {hdpi} {vdpi}");
WinThreadSetupMonitor(i);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void WinThreadSetupMonitor(int displayIdx)
{
var id = _nextMonitorId++;
var name = SDL.SDL_GetDisplayName(displayIdx);
var modeCount = SDL.SDL_GetNumDisplayModes(displayIdx);
SDL.SDL_GetCurrentDisplayMode(displayIdx, out var curMode);
var modes = new VideoMode[modeCount];
for (var i = 0; i < modes.Length; i++)
{
SDL.SDL_GetDisplayMode(displayIdx, i, out var mode);
modes[i] = ConvertVideoMode(mode);
}
_winThreadMonitors.Add(id, new WinThreadMonitorReg { Id = id, DisplayIdx = displayIdx });
SendEvent(new EventMonitorSetup(id, name, ConvertVideoMode(curMode), modes));
if (displayIdx == 0)
_clyde._primaryMonitorId = id;
}
private static VideoMode ConvertVideoMode(in SDL.SDL_DisplayMode mode)
{
return new()
{
Width = (ushort)mode.w,
Height = (ushort)mode.h,
RefreshRate = (ushort)mode.refresh_rate,
// TODO: set bits count based on format (I'm lazy)
RedBits = 8,
GreenBits = 8,
BlueBits = 8,
};
}
private void ProcessSetupMonitor(EventMonitorSetup ev)
{
var impl = new MonitorHandle(
ev.Id,
ev.Name,
(ev.CurrentMode.Width, ev.CurrentMode.Height),
ev.CurrentMode.RefreshRate,
ev.AllModes);
_clyde._monitorHandles.Add(ev.Id, impl);
_monitors[ev.Id] = new Sdl2MonitorReg
{
Id = ev.Id,
Handle = impl
};
}
private void WinThreadDestroyMonitor(int displayIdx)
{
var monitorId = 0;
foreach (var (id, monitorReg) in _winThreadMonitors)
{
if (monitorReg.DisplayIdx == displayIdx)
{
monitorId = id;
break;
}
}
if (monitorId == 0)
return;
// So SDL2 doesn't have a very nice indexing system for monitors like GLFW does.
// This means that, when a monitor is disconnected, all monitors *after* it get shifted down one slot.
// Now, this happens *after* the event is fired, to make matters worse.
// So we're basically trying to match unspecified SDL2 internals here. Great.
_winThreadMonitors.Remove(monitorId);
foreach (var (_, reg) in _winThreadMonitors)
{
if (reg.DisplayIdx > displayIdx)
reg.DisplayIdx -= 1;
}
SendEvent(new EventMonitorDestroy(monitorId));
}
private void ProcessEventDestroyMonitor(EventMonitorDestroy ev)
{
_monitors.Remove(ev.Id);
_clyde._monitorHandles.Remove(ev.Id);
}
private sealed class Sdl2MonitorReg : MonitorReg
{
public int Id;
}
private sealed class WinThreadMonitorReg
{
public int Id;
public int DisplayIdx;
}
}
}

View File

@@ -1,316 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Robust.Shared.Maths;
using TerraFX.Interop.Windows;
using static SDL2.SDL;
using static SDL2.SDL.SDL_EventType;
using static SDL2.SDL.SDL_SYSWM_TYPE;
using static SDL2.SDL.SDL_WindowEventID;
namespace Robust.Client.Graphics.Clyde;
internal partial class Clyde
{
private sealed partial class Sdl2WindowingImpl
{
[UnmanagedCallersOnly(CallConvs = new []{typeof(CallConvCdecl)})]
private static unsafe int EventWatch(void* userdata, SDL_Event* sdlevent)
{
var obj = (Sdl2WindowingImpl) GCHandle.FromIntPtr((IntPtr)userdata).Target!;
ref readonly var ev = ref *sdlevent;
obj.ProcessSdl2Event(in ev);
return 0;
}
private void ProcessSdl2Event(in SDL_Event ev)
{
switch (ev.type)
{
case SDL_WINDOWEVENT:
ProcessSdl2EventWindow(in ev.window);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
ProcessSdl2KeyEvent(in ev.key);
break;
case SDL_TEXTINPUT:
ProcessSdl2EventTextInput(in ev.text);
break;
case SDL_TEXTEDITING:
ProcessSdl2EventTextEditing(in ev.edit);
break;
case SDL_KEYMAPCHANGED:
ProcessSdl2EventKeyMapChanged();
break;
case SDL_TEXTEDITING_EXT:
ProcessSdl2EventTextEditingExt(in ev.editExt);
break;
case SDL_MOUSEMOTION:
ProcessSdl2EventMouseMotion(in ev.motion);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
ProcessSdl2EventMouseButton(in ev.button);
break;
case SDL_MOUSEWHEEL:
ProcessSdl2EventMouseWheel(in ev.wheel);
break;
case SDL_DISPLAYEVENT:
ProcessSdl2EventDisplay(in ev.display);
break;
case SDL_SYSWMEVENT:
ProcessSdl2EventSysWM(in ev.syswm);
break;
case SDL_QUIT:
ProcessSdl2EventQuit();
break;
}
}
private void ProcessSdl2EventQuit()
{
SendEvent(new EventQuit());
}
private void ProcessSdl2EventDisplay(in SDL_DisplayEvent evDisplay)
{
switch (evDisplay.displayEvent)
{
case SDL_DisplayEventID.SDL_DISPLAYEVENT_CONNECTED:
WinThreadSetupMonitor((int) evDisplay.display);
break;
case SDL_DisplayEventID.SDL_DISPLAYEVENT_DISCONNECTED:
WinThreadDestroyMonitor((int) evDisplay.display);
break;
}
}
private void ProcessSdl2EventMouseWheel(in SDL_MouseWheelEvent ev)
{
SendEvent(new EventWheel(ev.windowID, ev.preciseX, ev.preciseY));
}
private void ProcessSdl2EventMouseButton(in SDL_MouseButtonEvent ev)
{
SendEvent(new EventMouseButton(ev.windowID, ev.type, ev.button));
}
private void ProcessSdl2EventMouseMotion(in SDL_MouseMotionEvent ev)
{
// _sawmill.Info($"{evMotion.x}, {evMotion.y}, {evMotion.xrel}, {evMotion.yrel}");
SendEvent(new EventMouseMotion(ev.windowID, ev.x, ev.y, ev.xrel, ev.yrel));
}
private unsafe void ProcessSdl2EventTextInput(in SDL_TextInputEvent ev)
{
fixed (byte* text = ev.text)
{
var str = Marshal.PtrToStringUTF8((IntPtr)text) ?? "";
// _logManager.GetSawmill("ime").Debug($"Input: {str}");
SendEvent(new EventText(ev.windowID, str));
}
}
private unsafe void ProcessSdl2EventTextEditing(in SDL_TextEditingEvent ev)
{
fixed (byte* text = ev.text)
{
SendTextEditing(ev.windowID, text, ev.start, ev.length);
}
}
private unsafe void ProcessSdl2EventTextEditingExt(in SDL_TextEditingExtEvent ev)
{
SendTextEditing(ev.windowID, (byte*) ev.text, ev.start, ev.length);
SDL_free(ev.text);
}
private unsafe void SendTextEditing(uint window, byte* text, int start, int length)
{
var str = Marshal.PtrToStringUTF8((nint) text) ?? "";
// _logManager.GetSawmill("ime").Debug($"Editing: '{str}', start: {start}, len: {length}");
SendEvent(new EventTextEditing(window, str, start, length));
}
private void ProcessSdl2EventKeyMapChanged()
{
ReloadKeyMap();
SendEvent(new EventKeyMapChanged());
}
private void ProcessSdl2KeyEvent(in SDL_KeyboardEvent ev)
{
SendEvent(new EventKey(
ev.windowID,
ev.keysym.scancode,
ev.type,
ev.repeat != 0,
ev.keysym.mod));
}
private void ProcessSdl2EventWindow(in SDL_WindowEvent ev)
{
var window = SDL_GetWindowFromID(ev.windowID);
switch (ev.windowEvent)
{
case SDL_WINDOWEVENT_SIZE_CHANGED:
var width = ev.data1;
var height = ev.data2;
SDL_GetWindowSizeInPixels(window, out var fbW, out var fbH);
var (xScale, yScale) = GetWindowScale(window);
_sawmill.Debug($"{width}x{height}, {fbW}x{fbH}, {xScale}x{yScale}");
SendEvent(new EventWindowSize(ev.windowID, width, height, fbW, fbH, xScale, yScale));
break;
default:
SendEvent(new EventWindow(ev.windowID, ev.windowEvent));
break;
}
}
// ReSharper disable once InconsistentNaming
private unsafe void ProcessSdl2EventSysWM(in SDL_SysWMEvent ev)
{
ref readonly var sysWmMessage = ref *(SDL_SysWMmsg*)ev.msg;
if (sysWmMessage.subsystem != SDL_SYSWM_WINDOWS)
return;
ref readonly var winMessage = ref *(SDL_SysWMmsgWin32*)ev.msg;
if (winMessage.msg is WM.WM_KEYDOWN or WM.WM_KEYUP)
{
TryWin32VirtualVKey(in winMessage);
}
}
private void TryWin32VirtualVKey(in SDL_SysWMmsgWin32 msg)
{
// Workaround for https://github.com/ocornut/imgui/issues/2977
// This is gonna bite me in the ass if SDL2 ever fixes this upstream, isn't it...
// (I spent disproportionate amounts of effort on this).
// Code for V key.
if ((int)msg.wParam is not (0x56 or VK.VK_CONTROL))
return;
var scanCode = (msg.lParam >> 16) & 0xFF;
if (scanCode != 0)
return;
SendEvent(new EventWindowsFakeV(msg.hwnd, msg.msg, msg.wParam));
}
private abstract record EventBase;
private record EventWindowCreate(
Sdl2WindowCreateResult Result,
TaskCompletionSource<Sdl2WindowCreateResult> Tcs
) : EventBase;
private record EventKey(
uint WindowId,
SDL_Scancode Scancode,
SDL_EventType Type,
bool Repeat,
SDL_Keymod Mods
) : EventBase;
private record EventMouseMotion(
uint WindowId,
int X, int Y,
int XRel, int YRel
) : EventBase;
private record EventMouseButton(
uint WindowId,
SDL_EventType Type,
byte Button
) : EventBase;
private record EventText(
uint WindowId,
string Text
) : EventBase;
private record EventTextEditing(
uint WindowId,
string Text,
int Start,
int Length
) : EventBase;
private record EventWindowSize(
uint WindowId,
int Width,
int Height,
int FramebufferWidth,
int FramebufferHeight,
float XScale,
float YScale
) : EventBase;
private record EventWheel(
uint WindowId,
float XOffset,
float YOffset
) : EventBase;
// SDL_WindowEvents that don't have special handling like size.
private record EventWindow(
uint WindowId,
SDL_WindowEventID EventId
) : EventBase;
private record EventMonitorSetup
(
int Id,
string Name,
VideoMode CurrentMode,
VideoMode[] AllModes
) : EventBase;
private record EventMonitorDestroy
(
int Id
) : EventBase;
private record EventWindowsFakeV(HWND Window,
uint Message, WPARAM WParam) : EventBase;
private record EventKeyMapChanged : EventBase;
private record EventQuit : EventBase;
[StructLayout(LayoutKind.Sequential)]
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SuppressMessage("ReSharper", "IdentifierTypo")]
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
private struct SDL_SysWMmsg
{
public SDL_version version;
public SDL_SYSWM_TYPE subsystem;
}
[StructLayout(LayoutKind.Sequential)]
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SuppressMessage("ReSharper", "IdentifierTypo")]
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
private struct SDL_SysWMmsgWin32
{
public SDL_version version;
public SDL_SYSWM_TYPE subsystem;
public HWND hwnd;
public uint msg;
public WPARAM wParam;
public LPARAM lParam;
}
}
}

View File

@@ -1,575 +0,0 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using static SDL2.SDL;
using static SDL2.SDL.SDL_bool;
using static SDL2.SDL.SDL_FlashOperation;
using static SDL2.SDL.SDL_GLattr;
using static SDL2.SDL.SDL_GLcontext;
using static SDL2.SDL.SDL_GLprofile;
using static SDL2.SDL.SDL_SYSWM_TYPE;
using static SDL2.SDL.SDL_WindowFlags;
using BOOL = TerraFX.Interop.Windows.BOOL;
using HWND = TerraFX.Interop.Windows.HWND;
using GWLP = TerraFX.Interop.Windows.GWLP;
using Windows = TerraFX.Interop.Windows.Windows;
namespace Robust.Client.Graphics.Clyde;
internal partial class Clyde
{
private sealed partial class Sdl2WindowingImpl
{
private int _nextWindowId = 1;
public (WindowReg?, string? error) WindowCreate(
GLContextSpec? spec,
WindowCreateParameters parameters,
WindowReg? share,
WindowReg? owner)
{
nint shareWindow = 0;
nint shareContext = 0;
if (share is Sdl2WindowReg shareReg)
{
shareWindow = shareReg.Sdl2Window;
shareContext = shareReg.GlContext;
}
nint ownerPtr = 0;
if (owner is Sdl2WindowReg ownerReg)
ownerPtr = ownerReg.Sdl2Window;
var task = SharedWindowCreate(spec, parameters, shareWindow, shareContext, ownerPtr);
// Block the main thread (to avoid stuff like texture uploads being problematic).
WaitWindowCreate(task);
#pragma warning disable RA0004
// Block above ensured task is done, this is safe.
var (reg, error) = task.Result;
#pragma warning restore RA0004
if (reg != null)
{
reg.Owner = reg.Handle;
}
return (reg, error);
}
private void WaitWindowCreate(Task<Sdl2WindowCreateResult> windowTask)
{
while (!windowTask.IsCompleted)
{
// Keep processing events until the window task gives either an error or success.
WaitEvents();
ProcessEvents(single: true);
}
}
private Task<Sdl2WindowCreateResult> SharedWindowCreate(
GLContextSpec? glSpec,
WindowCreateParameters parameters,
nint shareWindow,
nint shareContext,
nint owner)
{
//
// IF YOU'RE WONDERING WHY THIS IS TASK-BASED:
// I originally wanted this to be async so we could avoid blocking the main thread
// while the OS takes its stupid 100~ms just to initialize a fucking GL context.
// This doesn't *work* because
// we have to release the GL context while the shared context is being created.
// (at least on WGL, I didn't test other platforms and I don't care to.)
// Not worth it to avoid a main thread blockage by allowing Clyde to temporarily release the GL context,
// because rendering would be locked up *anyways*.
//
// Basically what I'm saying is that everything about OpenGL is a fucking mistake
// and I should get on either Veldrid or Vulkan some time.
// Probably Veldrid tbh.
//
// Yes we ping-pong this TCS through the window thread and back, deal with it.
var tcs = new TaskCompletionSource<Sdl2WindowCreateResult>();
SendCmd(new CmdWinCreate(glSpec, parameters, shareWindow, shareContext, owner, tcs));
return tcs.Task;
}
private static void FinishWindowCreate(EventWindowCreate ev)
{
var (res, tcs) = ev;
tcs.TrySetResult(res);
}
private void WinThreadWinCreate(CmdWinCreate cmd)
{
var (glSpec, parameters, shareWindow, shareContext, owner, tcs) = cmd;
var (window, context) = CreateSdl2WindowForRenderer(glSpec, parameters, shareWindow, shareContext, owner);
if (window == 0)
{
var err = SDL_GetError();
SendEvent(new EventWindowCreate(new Sdl2WindowCreateResult(null, err), tcs));
return;
}
// We can't invoke the TCS directly from the windowing thread because:
// * it'd hit the synchronization context,
// which would make (blocking) main window init more annoying.
// * it'd not be synchronized to other incoming window events correctly which might be icky.
// So we send the TCS back to the game thread
// which processes events in the correct order and has better control of stuff during init.
var reg = WinThreadSetupWindow(window, context);
SendEvent(new EventWindowCreate(new Sdl2WindowCreateResult(reg, null), tcs));
}
private static void WinThreadWinDestroy(CmdWinDestroy cmd)
{
if (OperatingSystem.IsWindows() && cmd.HadOwner)
{
// On Windows, closing the child window causes the owner to be minimized, apparently.
// Clear owner on close to avoid this.
SDL_SysWMinfo wmInfo = default;
SDL_VERSION(out wmInfo.version);
if (SDL_GetWindowWMInfo(cmd.Window, ref wmInfo) == SDL_TRUE && wmInfo.subsystem == SDL_SYSWM_WINDOWS)
{
var hWnd = (HWND)wmInfo.info.win.window;
DebugTools.Assert(hWnd != HWND.NULL);
Windows.SetWindowLongPtrW(
hWnd,
GWLP.GWLP_HWNDPARENT,
0);
}
}
SDL_DestroyWindow(cmd.Window);
}
private (nint window, nint context) CreateSdl2WindowForRenderer(
GLContextSpec? spec,
WindowCreateParameters parameters,
nint shareWindow,
nint shareContext,
nint ownerWindow)
{
var windowFlags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
if (spec is { } s)
{
windowFlags |= SDL_WINDOW_OPENGL;
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, s.Profile == GLContextProfile.Es ? 0 : 1);
SDL_GLcontext ctxFlags = 0;
#if DEBUG
ctxFlags |= SDL_GL_CONTEXT_DEBUG_FLAG;
#endif
if (s.Profile == GLContextProfile.Core)
ctxFlags |= SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG;
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, (int)ctxFlags);
if (shareContext != 0)
{
SDL_GL_MakeCurrent(shareWindow, shareContext);
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
}
else
{
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0);
}
var profile = s.Profile switch
{
GLContextProfile.Compatibility => SDL_GL_CONTEXT_PROFILE_COMPATIBILITY,
GLContextProfile.Core => SDL_GL_CONTEXT_PROFILE_CORE,
GLContextProfile.Es => SDL_GL_CONTEXT_PROFILE_ES,
_ => SDL_GL_CONTEXT_PROFILE_COMPATIBILITY,
};
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile);
SDL_SetHint("SDL_OPENGL_ES_DRIVER", s.CreationApi == GLContextCreationApi.Egl ? "1" : "0");
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, s.Major);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, s.Minor);
if (s.CreationApi == GLContextCreationApi.Egl)
WsiShared.EnsureEglAvailable();
}
if (OperatingSystem.IsMacOS())
{
windowFlags |= SDL_WINDOW_ALLOW_HIGHDPI;
}
if (parameters.Fullscreen)
{
windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
nint window = SDL_CreateWindow(
"",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
parameters.Width, parameters.Height,
windowFlags);
if (window == 0)
return default;
nint glContext = SDL_GL_CreateContext(window);
if (glContext == 0)
{
SDL_DestroyWindow(window);
return default;
}
// TODO: Monitors, window maximize.
// TODO: a bunch of win32 calls for funny window properties I still haven't ported to other platforms.
// Make sure window thread doesn't keep hold of the GL context.
SDL_GL_MakeCurrent(IntPtr.Zero, IntPtr.Zero);
if (OperatingSystem.IsWindows())
{
SDL_SysWMinfo info = default;
SDL_VERSION(out info.version);
if (SDL_GetWindowWMInfo(window, ref info) == SDL_TRUE && info.subsystem == SDL_SYSWM_WINDOWS)
WsiShared.WindowsSharedWindowCreate((HWND) info.info.win.window, _cfg);
}
if (parameters.Visible)
SDL_ShowWindow(window);
return (window, glContext);
}
private unsafe Sdl2WindowReg WinThreadSetupWindow(nint window, nint context)
{
var reg = new Sdl2WindowReg
{
Sdl2Window = window,
GlContext = context,
WindowId = SDL_GetWindowID(window),
Id = new WindowId(_nextWindowId++)
};
var handle = new WindowHandle(_clyde, reg);
reg.Handle = handle;
SDL_VERSION(out reg.SysWMinfo.version);
var res = SDL_GetWindowWMInfo(window, ref reg.SysWMinfo);
if (res == SDL_FALSE)
_sawmill.Error("Failed to get window WM info: {error}", SDL_GetError());
// LoadWindowIcon(window);
SDL_GetWindowSizeInPixels(window, out var fbW, out var fbH);
reg.FramebufferSize = (fbW, fbH);
reg.WindowScale = GetWindowScale(window);
SDL_GetWindowSize(window, out var w, out var h);
reg.PrevWindowSize = reg.WindowSize = (w, h);
SDL_GetWindowPosition(window, out var x, out var y);
reg.PrevWindowPos = (x, y);
reg.PixelRatio = reg.FramebufferSize / (Vector2) reg.WindowSize;
return reg;
}
public void WindowDestroy(WindowReg window)
{
var reg = (Sdl2WindowReg) window;
SendCmd(new CmdWinDestroy(reg.Sdl2Window, window.Owner != null));
}
public void UpdateMainWindowMode()
{
if (_clyde._mainWindow == null)
return;
var win = (Sdl2WindowReg) _clyde._mainWindow;
SendCmd(new CmdWinWinSetMode(win.Sdl2Window, _clyde._windowMode));
}
private static void WinThreadWinSetMode(CmdWinWinSetMode cmd)
{
var flags = cmd.Mode switch
{
WindowMode.Fullscreen => (uint) SDL_WINDOW_FULLSCREEN_DESKTOP,
_ => 0u
};
SDL_SetWindowFullscreen(cmd.Window, flags);
}
public void WindowSetTitle(WindowReg window, string title)
{
SendCmd(new CmdWinSetTitle(WinPtr(window), title));
}
private static void WinThreadWinSetTitle(CmdWinSetTitle cmd)
{
SDL_SetWindowTitle(cmd.Window, cmd.Title);
}
public void WindowSetMonitor(WindowReg window, IClydeMonitor monitor)
{
// API isn't really used and kinda wack, don't feel like figuring it out for SDL2 yet.
_sawmill.Warning("WindowSetMonitor not implemented on SDL2");
}
public void WindowSetVisible(WindowReg window, bool visible)
{
SendCmd(new CmdWinSetVisible(WinPtr(window), visible));
}
private static void WinThreadWinSetVisible(CmdWinSetVisible cmd)
{
if (cmd.Visible)
SDL_ShowWindow(cmd.Window);
else
SDL_HideWindow(cmd.Window);
}
public void WindowRequestAttention(WindowReg window)
{
SendCmd(new CmdWinRequestAttention(WinPtr(window)));
}
private void WinThreadWinRequestAttention(CmdWinRequestAttention cmd)
{
var res = SDL_FlashWindow(cmd.Window, SDL_FLASH_UNTIL_FOCUSED);
if (res < 0)
_sawmill.Error("Failed to flash window: {error}", SDL_GetError());
}
public unsafe void WindowSwapBuffers(WindowReg window)
{
var reg = (Sdl2WindowReg)window;
var windowPtr = WinPtr(reg);
// 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, SDL2 does not.
//
// Windows DwmFlush logic partly taken from:
// https://github.com/love2d/love/blob/5175b0d1b599ea4c7b929f6b4282dd379fa116b8/src/modules/window/sdl/Window.cpp#L1018
// https://github.com/glfw/glfw/blob/d3ede7b6847b66cf30b067214b2b4b126d4c729b/src/wgl_context.c#L321-L340
// See also: https://github.com/libsdl-org/SDL/issues/5797
var dwmFlush = false;
var swapInterval = 0;
if (OperatingSystem.IsWindows() && !reg.Fullscreen && reg.SwapInterval > 0)
{
BOOL compositing;
// 6.2 is Windows 8
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw
if (OperatingSystem.IsWindowsVersionAtLeast(6, 2)
|| Windows.SUCCEEDED(Windows.DwmIsCompositionEnabled(&compositing)) && compositing)
{
var curCtx = SDL_GL_GetCurrentContext();
var curWin = SDL_GL_GetCurrentWindow();
if (curCtx != reg.GlContext || curWin != reg.Sdl2Window)
throw new InvalidOperationException("Window context must be current!");
SDL_GL_SetSwapInterval(0);
dwmFlush = true;
swapInterval = reg.SwapInterval;
}
}
SDL_GL_SwapWindow(windowPtr);
if (dwmFlush)
{
var i = swapInterval;
while (i-- > 0)
{
Windows.DwmFlush();
}
SDL_GL_SetSwapInterval(swapInterval);
}
}
public uint? WindowGetX11Id(WindowReg window)
{
CheckWindowDisposed(window);
var reg = (Sdl2WindowReg) window;
if (reg.SysWMinfo.subsystem != SDL_SYSWM_X11)
return null;
return (uint?) reg.SysWMinfo.info.x11.window;
}
public nint? WindowGetX11Display(WindowReg window)
{
CheckWindowDisposed(window);
var reg = (Sdl2WindowReg) window;
if (reg.SysWMinfo.subsystem != SDL_SYSWM_X11)
return null;
return reg.SysWMinfo.info.x11.display;
}
public nint? WindowGetWin32Window(WindowReg window)
{
CheckWindowDisposed(window);
var reg = (Sdl2WindowReg) window;
if (reg.SysWMinfo.subsystem != SDL_SYSWM_WINDOWS)
return null;
return reg.SysWMinfo.info.win.window;
}
public void RunOnWindowThread(Action a)
{
SendCmd(new CmdRunAction(a));
}
public void TextInputSetRect(UIBox2i rect)
{
SendCmd(new CmdTextInputSetRect(new SDL_Rect
{
x = rect.Left,
y = rect.Top,
w = rect.Width,
h = rect.Height
}));
}
private static void WinThreadSetTextInputRect(CmdTextInputSetRect cmdTextInput)
{
var rect = cmdTextInput.Rect;
SDL_SetTextInputRect(ref rect);
}
public void TextInputStart()
{
SendCmd(CmdTextInputStart.Instance);
}
private static void WinThreadStartTextInput()
{
SDL_StartTextInput();
}
public void TextInputStop()
{
SendCmd(CmdTextInputStop.Instance);
}
private static void WinThreadStopTextInput()
{
SDL_StopTextInput();
}
public void ClipboardSetText(WindowReg mainWindow, string text)
{
SendCmd(new CmdSetClipboard(text));
}
private void WinThreadSetClipboard(CmdSetClipboard cmd)
{
var res = SDL_SetClipboardText(cmd.Text);
if (res < 0)
_sawmill.Error("Failed to set clipboard text: {error}", SDL_GetError());
}
public Task<string> ClipboardGetText(WindowReg mainWindow)
{
var tcs = new TaskCompletionSource<string>();
SendCmd(new CmdGetClipboard(tcs));
return tcs.Task;
}
private static void WinThreadGetClipboard(CmdGetClipboard cmd)
{
cmd.Tcs.TrySetResult(SDL_GetClipboardText());
}
private static Vector2 GetWindowScale(nint window)
{
// Get scale by diving size in pixels with size in points.
SDL_GetWindowSizeInPixels(window, out var pixW, out var pixH);
SDL_GetWindowSize(window, out var pointW, out var pointH);
// Avoiding degenerate cases, not sure if these can actually happen.
if (pixW == 0 || pixH == 0 || pointW == 0 || pointH == 0)
return new Vector2(1, 1);
var scaleH = pixW / (float) pointW;
var scaleV = pixH / (float) pointH;
// Round to 5% increments to avoid rounding errors causing constantly different scales.
scaleH = MathF.Round(scaleH * 20) / 20;
scaleV = MathF.Round(scaleV * 20) / 20;
return new Vector2(scaleH, scaleV);
}
private static void CheckWindowDisposed(WindowReg reg)
{
if (reg.IsDisposed)
throw new ObjectDisposedException("Window disposed");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nint WinPtr(WindowReg reg) => ((Sdl2WindowReg)reg).Sdl2Window;
private WindowReg? FindWindow(uint windowId)
{
foreach (var windowReg in _clyde._windows)
{
var glfwReg = (Sdl2WindowReg) windowReg;
if (glfwReg.WindowId == windowId)
return windowReg;
}
return null;
}
private sealed class Sdl2WindowReg : WindowReg
{
public nint Sdl2Window;
public uint WindowId;
public nint GlContext;
public SDL_SysWMinfo SysWMinfo;
#pragma warning disable CS0649
public bool Fullscreen;
#pragma warning restore CS0649
public int SwapInterval;
// Kept around to avoid it being GCd.
public CursorImpl? Cursor;
}
}
}

View File

@@ -1,190 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using static SDL2.SDL;
using static SDL2.SDL.SDL_LogCategory;
using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute;
namespace Robust.Client.Graphics.Clyde;
internal partial class Clyde
{
private sealed partial class Sdl2WindowingImpl : IWindowingImpl
{
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly Clyde _clyde;
private GCHandle _selfGCHandle;
private readonly ISawmill _sawmill;
private readonly ISawmill _sawmillSdl2;
public Sdl2WindowingImpl(Clyde clyde, IDependencyCollection deps)
{
_clyde = clyde;
deps.InjectDependencies(this, true);
_sawmill = _logManager.GetSawmill("clyde.win");
_sawmillSdl2 = _logManager.GetSawmill("clyde.win.sdl2");
}
public bool Init()
{
InitChannels();
if (!InitSdl2())
return false;
return true;
}
private unsafe bool InitSdl2()
{
_selfGCHandle = GCHandle.Alloc(this, GCHandleType.Normal);
SDL_LogSetAllPriority(SDL_LogPriority.SDL_LOG_PRIORITY_VERBOSE);
SDL_LogSetOutputFunction(&LogOutputFunction, (void*) GCHandle.ToIntPtr(_selfGCHandle));
SDL_SetHint("SDL_WINDOWS_DPI_SCALING", "1");
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
SDL_SetHint(SDL_HINT_IME_SUPPORT_EXTENDED_TEXT, "1");
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
var res = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
if (res < 0)
{
_sawmill.Fatal("Failed to initialize SDL2: {error}", SDL_GetError());
return false;
}
SDL_GetVersion(out var version);
var videoDriver = SDL_GetCurrentVideoDriver();
_sawmill.Debug(
"SDL2 initialized, version: {major}.{minor}.{patch}, video driver: {videoDriver}", version.major, version.minor, version.patch, videoDriver);
_sdlEventWakeup = SDL_RegisterEvents(1);
SDL_EventState(SDL_EventType.SDL_SYSWMEVENT, SDL_ENABLE);
InitCursors();
InitMonitors();
ReloadKeyMap();
SDL_AddEventWatch(&EventWatch, (void*) GCHandle.ToIntPtr(_selfGCHandle));
// SDL defaults to having text input enabled, so we have to manually turn it off in init for consistency.
// If we don't, text input will remain enabled *until* the user first leaves a LineEdit/TextEdit.
SDL_StopTextInput();
return true;
}
public unsafe void Shutdown()
{
if (_selfGCHandle != default)
{
SDL_DelEventWatch(&EventWatch, (void*) GCHandle.ToIntPtr(_selfGCHandle));
_selfGCHandle.Free();
}
SDL_LogSetOutputFunction(null, null);
if (SDL_WasInit(0) != 0)
{
_sawmill.Debug("Terminating SDL2");
SDL_Quit();
}
}
public void FlushDispose()
{
// Not currently used
}
public void GLMakeContextCurrent(WindowReg? reg)
{
int res;
if (reg is Sdl2WindowReg sdlReg)
res = SDL_GL_MakeCurrent(sdlReg.Sdl2Window, sdlReg.GlContext);
else
res = SDL_GL_MakeCurrent(IntPtr.Zero, IntPtr.Zero);
if (res < 0)
_sawmill.Error("SDL_GL_MakeCurrent failed: {error}", SDL_GetError());
}
public void GLSwapInterval(WindowReg reg, int interval)
{
((Sdl2WindowReg)reg).SwapInterval = interval;
SDL_GL_SetSwapInterval(interval);
}
public unsafe void* GLGetProcAddress(string procName)
{
return (void*) SDL_GL_GetProcAddress(procName);
}
public string GetDescription()
{
SDL_GetVersion(out var version);
_sawmill.Debug(
"SDL2 initialized, version: {major}.{minor}.{patch}", version.major, version.minor, version.patch);
var videoDriver = SDL_GetCurrentVideoDriver();
return $"SDL2 {version.major}.{version.minor}.{version.patch} ({videoDriver})";
}
[UnmanagedCallersOnly(CallConvs = new []{typeof(CallConvCdecl)})]
private static unsafe void LogOutputFunction(
void* userdata,
int category,
SDL_LogPriority priority,
byte* message)
{
var obj = (Sdl2WindowingImpl) GCHandle.FromIntPtr((IntPtr)userdata).Target!;
var level = priority switch
{
SDL_LogPriority.SDL_LOG_PRIORITY_VERBOSE => LogLevel.Verbose,
SDL_LogPriority.SDL_LOG_PRIORITY_DEBUG => LogLevel.Debug,
SDL_LogPriority.SDL_LOG_PRIORITY_INFO => LogLevel.Info,
SDL_LogPriority.SDL_LOG_PRIORITY_WARN => LogLevel.Warning,
SDL_LogPriority.SDL_LOG_PRIORITY_ERROR => LogLevel.Error,
SDL_LogPriority.SDL_LOG_PRIORITY_CRITICAL => LogLevel.Fatal,
_ => LogLevel.Error
};
var msg = Marshal.PtrToStringUTF8((IntPtr) message) ?? "";
if (msg == "That operation is not supported")
{
obj._sawmillSdl2.Info(Environment.StackTrace);
}
var categoryName = SdlLogCategoryName(category);
obj._sawmillSdl2.Log(level, $"[{categoryName}] {msg}");
}
private static string SdlLogCategoryName(int category)
{
return (SDL_LogCategory) category switch {
// @formatter:off
SDL_LOG_CATEGORY_APPLICATION => "application",
SDL_LOG_CATEGORY_ERROR => "error",
SDL_LOG_CATEGORY_ASSERT => "assert",
SDL_LOG_CATEGORY_SYSTEM => "system",
SDL_LOG_CATEGORY_AUDIO => "audio",
SDL_LOG_CATEGORY_VIDEO => "video",
SDL_LOG_CATEGORY_RENDER => "render",
SDL_LOG_CATEGORY_INPUT => "input",
SDL_LOG_CATEGORY_TEST => "test",
_ => "unknown"
// @formatter:on
};
}
}
}

View File

@@ -3,16 +3,15 @@ using System.Collections.Generic;
using Robust.Client.Utility;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SDL3;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using static SDL2.SDL;
using static SDL2.SDL.SDL_SystemCursor;
namespace Robust.Client.Graphics.Clyde;
internal partial class Clyde
{
private sealed partial class Sdl2WindowingImpl : IWindowingImpl
private sealed partial class Sdl3WindowingImpl
{
private readonly Dictionary<ClydeHandle, WinThreadCursorReg> _winThreadCursors = new();
private readonly CursorImpl[] _standardCursors = new CursorImpl[(int)StandardCursorShape.CountCursors];
@@ -28,31 +27,32 @@ internal partial class Clyde
image.GetPixelSpan().CopyTo(cloneImg.GetPixelSpan());
var id = _clyde.AllocRid();
SendCmd(new CmdCursorCreate(cloneImg, hotSpot, id));
SendCmd(new CmdCursorCreate { Bytes = cloneImg, Hotspot = hotSpot, Cursor = id });
return new CursorImpl(this, id, false);
}
private unsafe void WinThreadCursorCreate(CmdCursorCreate cmd)
{
var (img, (hotX, hotY), id) = cmd;
using var img = cmd.Bytes;
fixed (Rgba32* pixPtr = img.GetPixelSpan())
{
var surface = SDL_CreateRGBSurfaceWithFormatFrom(
var surface = SDL.SDL_CreateSurfaceFrom(
img.Width,
img.Height,
SDL.SDL_PixelFormat.SDL_PIXELFORMAT_ABGR8888,
(IntPtr)pixPtr,
img.Width, img.Height, 0,
sizeof(Rgba32) * img.Width,
SDL_PIXELFORMAT_RGBA8888);
sizeof(Rgba32) * img.Width);
var cursor = SDL_CreateColorCursor(surface, hotX, hotY);
var cursor = SDL.SDL_CreateColorCursor(surface, cmd.Hotspot.X, cmd.Hotspot.Y);
if (cursor == 0)
throw new InvalidOperationException("SDL_CreateColorCursor failed");
_winThreadCursors.Add(id, new WinThreadCursorReg { Ptr = cursor });
_winThreadCursors.Add(cmd.Cursor, new WinThreadCursorReg { Ptr = cursor });
SDL_FreeSurface(surface);
SDL.SDL_DestroySurface(surface);
}
img.Dispose();
}
public void CursorSet(WindowReg window, ICursor? cursor)
@@ -62,7 +62,7 @@ internal partial class Clyde
// SDL_SetCursor(NULL) does redraw, not reset.
cursor ??= CursorGetStandard(StandardCursorShape.Arrow);
var reg = (Sdl2WindowReg)window;
var reg = (Sdl3WindowReg)window;
if (reg.Cursor == cursor)
return;
@@ -74,7 +74,7 @@ internal partial class Clyde
throw new ObjectDisposedException(nameof(cursor));
reg.Cursor = impl;
SendCmd(new CmdWinCursorSet(reg.Sdl2Window, impl.Id));
SendCmd(new CmdWinCursorSet { Window = reg.Sdl3Window, Cursor = impl.Id });
}
private void WinThreadWinCursorSet(CmdWinCursorSet cmd)
@@ -83,22 +83,22 @@ internal partial class Clyde
var ptr = _winThreadCursors[cmd.Cursor].Ptr;
// TODO: multi-window??
SDL_SetCursor(ptr);
SDL.SDL_SetCursor(ptr);
}
private void InitCursors()
{
Add(StandardCursorShape.Arrow, SDL_SYSTEM_CURSOR_ARROW);
Add(StandardCursorShape.IBeam, SDL_SYSTEM_CURSOR_IBEAM);
Add(StandardCursorShape.Crosshair, SDL_SYSTEM_CURSOR_CROSSHAIR);
Add(StandardCursorShape.Hand, SDL_SYSTEM_CURSOR_HAND);
Add(StandardCursorShape.HResize, SDL_SYSTEM_CURSOR_SIZEWE);
Add(StandardCursorShape.VResize, SDL_SYSTEM_CURSOR_SIZENS);
Add(StandardCursorShape.Arrow, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_DEFAULT);
Add(StandardCursorShape.IBeam, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_TEXT);
Add(StandardCursorShape.Crosshair, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_CROSSHAIR);
Add(StandardCursorShape.Hand, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_POINTER);
Add(StandardCursorShape.HResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_EW_RESIZE);
Add(StandardCursorShape.VResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NS_RESIZE);
void Add(StandardCursorShape shape, SDL_SystemCursor sysCursor)
void Add(StandardCursorShape shape, SDL.SDL_SystemCursor sysCursor)
{
var id = _clyde.AllocRid();
var cursor = SDL_CreateSystemCursor(sysCursor);
var cursor = SDL.SDL_CreateSystemCursor(sysCursor);
var impl = new CursorImpl(this, id, true);
@@ -107,13 +107,21 @@ internal partial class Clyde
}
}
private void WinThreadCursorDestroy(CmdCursorDestroy cmd)
{
if (!_winThreadCursors.TryGetValue(cmd.Cursor, out var cursor))
return;
SDL.SDL_DestroyCursor(cursor.Ptr);
}
private sealed class CursorImpl : ICursor
{
private readonly bool _standard;
public Sdl2WindowingImpl Owner { get; }
public Sdl3WindowingImpl Owner { get; }
public ClydeHandle Id { get; private set; }
public CursorImpl(Sdl2WindowingImpl clyde, ClydeHandle id, bool standard)
public CursorImpl(Sdl3WindowingImpl clyde, ClydeHandle id, bool standard)
{
_standard = standard;
Owner = clyde;
@@ -127,7 +135,7 @@ internal partial class Clyde
private void DisposeImpl()
{
Owner.SendCmd(new CmdCursorDestroy(Id));
Owner.SendCmd(new CmdCursorDestroy { Cursor = Id });
Id = default;
}
@@ -147,9 +155,5 @@ internal partial class Clyde
{
public nint Ptr;
}
private void WinThreadCursorDestroy(CmdCursorDestroy cmd)
{
}
}
}

View File

@@ -2,18 +2,16 @@
using System.Numerics;
using Robust.Client.Input;
using Robust.Shared.Map;
using TerraFX.Interop.Windows;
using static SDL2.SDL;
using static SDL2.SDL.SDL_EventType;
using static SDL2.SDL.SDL_Keymod;
using static SDL2.SDL.SDL_WindowEventID;
using SDL3;
using Key = Robust.Client.Input.Keyboard.Key;
using ET = SDL3.SDL.SDL_EventType;
using SDL_Keymod = SDL3.SDL.SDL_Keymod;
namespace Robust.Client.Graphics.Clyde;
internal partial class Clyde
{
private sealed partial class Sdl2WindowingImpl
private sealed partial class Sdl3WindowingImpl
{
public void ProcessEvents(bool single = false)
{
@@ -47,15 +45,18 @@ internal partial class Clyde
case EventWindowCreate wCreate:
FinishWindowCreate(wCreate);
break;
case EventWindow ev:
ProcessEventWindow(ev);
case EventWindowMisc ev:
ProcessEventWindowMisc(ev);
break;
case EventKey ev:
ProcessEventKey(ev);
break;
case EventWindowSize ev:
case EventWindowPixelSize ev:
ProcessEventWindowSize(ev);
break;
case EventWindowContentScale ev:
ProcessEventWindowContentScale(ev);
break;
case EventText ev:
ProcessEventText(ev);
break;
@@ -74,9 +75,6 @@ internal partial class Clyde
case EventMonitorSetup ev:
ProcessSetupMonitor(ev);
break;
case EventWindowsFakeV ev:
ProcessWindowsFakeV(ev);
break;
case EventKeyMapChanged:
ProcessKeyMapChanged();
break;
@@ -84,7 +82,7 @@ internal partial class Clyde
ProcessEventQuit();
break;
default:
_sawmill.Error($"Unknown SDL2 event type: {evb.GetType().Name}");
_sawmill.Error($"Unknown SDL3 event type: {evb.GetType().Name}");
break;
}
}
@@ -96,7 +94,7 @@ internal partial class Clyde
_clyde.SendCloseWindow(window, new WindowRequestClosedEventArgs(window.Handle));
}
private void ProcessEventWindow(EventWindow ev)
private void ProcessEventWindowMisc(EventWindowMisc ev)
{
var window = FindWindow(ev.WindowId);
if (window == null)
@@ -104,33 +102,38 @@ internal partial class Clyde
switch (ev.EventId)
{
case SDL_WINDOWEVENT_CLOSE:
case ET.SDL_EVENT_WINDOW_CLOSE_REQUESTED:
_clyde.SendCloseWindow(window, new WindowRequestClosedEventArgs(window.Handle));
break;
case SDL_WINDOWEVENT_ENTER:
case ET.SDL_EVENT_WINDOW_MOUSE_ENTER:
_clyde._currentHoveredWindow = window;
_clyde.SendMouseEnterLeave(new MouseEnterLeaveEventArgs(window.Handle, true));
break;
case SDL_WINDOWEVENT_LEAVE:
case ET.SDL_EVENT_WINDOW_MOUSE_LEAVE:
if (_clyde._currentHoveredWindow == window)
_clyde._currentHoveredWindow = null;
_clyde.SendMouseEnterLeave(new MouseEnterLeaveEventArgs(window.Handle, false));
break;
case SDL_WINDOWEVENT_MINIMIZED:
case ET.SDL_EVENT_WINDOW_MINIMIZED:
window.IsMinimized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
case ET.SDL_EVENT_WINDOW_RESTORED:
window.IsMinimized = false;
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
case ET.SDL_EVENT_WINDOW_FOCUS_GAINED:
window.IsFocused = true;
_clyde.SendWindowFocus(new WindowFocusedEventArgs(true, window.Handle));
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
case ET.SDL_EVENT_WINDOW_FOCUS_LOST:
window.IsFocused = false;
_clyde.SendWindowFocus(new WindowFocusedEventArgs(false, window.Handle));
break;
case ET.SDL_EVENT_WINDOW_MOVED:
window.WindowPos = (ev.Data1, ev.Data2);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
@@ -153,10 +156,9 @@ internal partial class Clyde
if (windowReg == null)
return;
var mods = SDL_GetModState();
var button = ConvertSdl2Button(ev.Button);
var button = ConvertSdl3Button(ev.Button);
var key = Mouse.MouseButtonToKey(button);
EmitKeyEvent(key, ev.Type, false, mods, 0);
EmitKeyEvent(key, ev.Type, false, ev.Mods, 0);
}
private void ProcessEventMouseMotion(EventMouseMotion ev)
@@ -166,8 +168,7 @@ internal partial class Clyde
return;
var newPos = new Vector2(ev.X, ev.Y) * windowReg.PixelRatio;
// SDL2 does give us delta positions, but I'm worried about rounding errors thanks to DPI stuff.
var delta = newPos - windowReg.LastMousePos;
var delta = new Vector2(ev.XRel, ev.YRel);
windowReg.LastMousePos = newPos;
_clyde._currentHoveredWindow = windowReg;
@@ -185,7 +186,7 @@ internal partial class Clyde
_clyde.SendTextEditing(new TextEditingEventArgs(ev.Text, ev.Start, ev.Length));
}
private void ProcessEventWindowSize(EventWindowSize ev)
private void ProcessEventWindowSize(EventWindowPixelSize ev)
{
var window = ev.WindowId;
var width = ev.Width;
@@ -205,28 +206,31 @@ internal partial class Clyde
return;
windowReg.PixelRatio = windowReg.FramebufferSize / (Vector2)windowReg.WindowSize;
var newScale = new Vector2(ev.XScale, ev.YScale);
if (!windowReg.WindowScale.Equals(newScale))
{
windowReg.WindowScale = newScale;
_clyde.SendWindowContentScaleChanged(new WindowContentScaleEventArgs(windowReg.Handle));
}
_clyde.SendWindowResized(windowReg, oldSize);
}
private void ProcessEventKey(EventKey ev)
private void ProcessEventWindowContentScale(EventWindowContentScale ev)
{
EmitKeyEvent(ConvertSdl2Scancode(ev.Scancode), ev.Type, ev.Repeat, ev.Mods, ev.Scancode);
var windowReg = FindWindow(ev.WindowId);
if (windowReg == null)
return;
windowReg.WindowScale = new Vector2(ev.Scale, ev.Scale);
_clyde.SendWindowContentScaleChanged(new WindowContentScaleEventArgs(windowReg.Handle));
}
private void EmitKeyEvent(Key key, SDL_EventType type, bool repeat, SDL_Keymod mods, SDL_Scancode scancode)
private void ProcessEventKey(EventKey ev)
{
var shift = (mods & KMOD_SHIFT) != 0;
var alt = (mods & KMOD_ALT) != 0;
var control = (mods & KMOD_CTRL) != 0;
var system = (mods & KMOD_GUI) != 0;
EmitKeyEvent(ConvertSdl3Scancode(ev.Scancode), ev.Type, ev.Repeat, ev.Mods, ev.Scancode);
}
private void EmitKeyEvent(Key key, ET type, bool repeat, SDL.SDL_Keymod mods, SDL.SDL_Scancode scancode)
{
var shift = (mods & SDL_Keymod.SDL_KMOD_SHIFT) != 0;
var alt = (mods & SDL_Keymod.SDL_KMOD_ALT) != 0;
var control = (mods & SDL_Keymod.SDL_KMOD_CTRL) != 0;
var system = (mods & SDL_Keymod.SDL_KMOD_GUI) != 0;
var ev = new KeyEventArgs(
key,
@@ -236,36 +240,17 @@ internal partial class Clyde
switch (type)
{
case SDL_KEYUP:
case SDL_MOUSEBUTTONUP:
case ET.SDL_EVENT_KEY_UP:
case ET.SDL_EVENT_MOUSE_BUTTON_UP:
_clyde.SendKeyUp(ev);
break;
case SDL_KEYDOWN:
case SDL_MOUSEBUTTONDOWN:
case ET.SDL_EVENT_KEY_DOWN:
case ET.SDL_EVENT_MOUSE_BUTTON_DOWN:
_clyde.SendKeyDown(ev);
break;
}
}
private void ProcessWindowsFakeV(EventWindowsFakeV ev)
{
var type = (int)ev.Message switch
{
WM.WM_KEYUP => SDL_KEYUP,
WM.WM_KEYDOWN => SDL_KEYDOWN,
_ => throw new ArgumentOutOfRangeException()
};
var key = (int)ev.WParam switch
{
0x56 /* V */ => Key.V,
VK.VK_CONTROL => Key.Control,
_ => throw new ArgumentOutOfRangeException()
};
EmitKeyEvent(key, type, false, 0, 0);
}
private void ProcessKeyMapChanged()
{
_clyde.SendInputModeChanged();

View File

@@ -0,0 +1,145 @@
using System;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Robust.Client.UserInterface;
using SDL3;
namespace Robust.Client.Graphics.Clyde;
internal partial class Clyde
{
private sealed partial class Sdl3WindowingImpl : IFileDialogManager
{
public async Task<Stream?> OpenFile(FileDialogFilters? filters = null)
{
var fileName = await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_OPENFILE, filters);
if (fileName == null)
return null;
return File.OpenRead(fileName);
}
public async Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true)
{
var fileName = await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_SAVEFILE, filters);
if (fileName == null)
return null;
try
{
return (File.Open(fileName, truncate ? FileMode.Truncate : FileMode.Open), true);
}
catch (FileNotFoundException)
{
return (File.Open(fileName, FileMode.Create), false);
}
}
private unsafe Task<string?> ShowFileDialogOfType(int type, FileDialogFilters? filters)
{
var props = SDL.SDL_CreateProperties();
SDL.SDL_DialogFileFilter* filtersAlloc = null;
if (filters != null)
{
filtersAlloc = (SDL.SDL_DialogFileFilter*)NativeMemory.Alloc(
(UIntPtr)filters.Groups.Count,
(UIntPtr)sizeof(SDL.SDL_DialogFileFilter));
SDL.SDL_SetNumberProperty(props, SDL.SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, filters.Groups.Count);
SDL.SDL_SetPointerProperty(props, SDL.SDL_PROP_FILE_DIALOG_FILTERS_POINTER, (nint)filtersAlloc);
// All these mallocs aren't gonna win any performance awards, but oh well.
for (var i = 0; i < filters.Groups.Count; i++)
{
var (name, pattern) = ConvertFilterGroup(filters.Groups[i]);
filtersAlloc[i].name = StringToNative(name);
filtersAlloc[i].pattern = StringToNative(pattern);
}
}
var task = ShowFileDialogWithProperties(type, props);
SDL.SDL_DestroyProperties(props);
if (filtersAlloc != null)
{
for (var i = 0; i < filters!.Groups.Count; i++)
{
var filter = filtersAlloc[i];
NativeMemory.Free(filter.name);
NativeMemory.Free(filter.pattern);
}
}
return task;
}
private static unsafe byte* StringToNative(string str)
{
var byteCount = Encoding.UTF8.GetByteCount(str);
var mem = (byte*) NativeMemory.Alloc((nuint)(byteCount + 1));
Encoding.UTF8.GetBytes(str, new Span<byte>(mem, byteCount));
mem[byteCount] = 0; // null-terminate
return mem;
}
private (string name, string pattern) ConvertFilterGroup(FileDialogFilters.Group group)
{
var name = string.Join(", ", group.Extensions.Select(e => $"*.{e}"));
var pattern = string.Join(";", group.Extensions);
return (name, pattern);
}
private unsafe Task<string?> ShowFileDialogWithProperties(int type, uint properties)
{
var tcs = new TaskCompletionSource<string?>();
var gcHandle = GCHandle.Alloc(new FileDialogState
{
Parent = this,
Tcs = tcs
});
SDL.SDL_ShowFileDialogWithProperties(
type,
&FileDialogCallback,
(void*)GCHandle.ToIntPtr(gcHandle),
properties);
return tcs.Task;
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe void FileDialogCallback(void* userdata, byte** filelist, int filter)
{
var stateHandle = GCHandle.FromIntPtr((IntPtr)userdata);
var state = (FileDialogState)stateHandle.Target!;
stateHandle.Free();
if (filelist == null)
{
// Error
state.Parent._sawmill.Error("File dialog failed: {error}", SDL.SDL_GetError());
state.Tcs.SetResult(null);
return;
}
// Handles null (cancelled/none selected) transparently.
var str = Marshal.PtrToStringUTF8((nint) filelist[0]);
state.Tcs.SetResult(str);
}
private sealed class FileDialogState
{
public required Sdl3WindowingImpl Parent;
public required TaskCompletionSource<string?> Tcs;
}
}
}

Some files were not shown because too many files have changed in this diff Show More