Compare commits

..

192 Commits

Author SHA1 Message Date
PJB3005
40b10f0dcc Version: 271.1.0 2026-01-20 19:45:58 +01:00
PJB3005
5885549c78 Release notes 2026-01-20 19:45:51 +01:00
PJB3005
89e16d5ba9 Fix Reset() not getting called if client channel disconnects unprompted.
Fixes https://github.com/space-wizards/RobustToolbox/issues/6390
Fixes https://github.com/space-wizards/RobustToolbox/issues/6388
2026-01-20 19:03:32 +01:00
PJB3005
2afef1480e Add debug code to slow down transfer connections 2026-01-20 19:01:56 +01:00
PJB3005
76189579c7 Remove dead code from transfer 2026-01-20 19:01:39 +01:00
PJB3005
114c2bee62 Fix transfer being entirely nonfunctional
Oops
2026-01-20 19:01:32 +01:00
PJB3005
ce96331ec4 Add completions to launchauth command 2026-01-20 01:14:56 +01:00
Ataman
14b17aff6d Add events on animation starts (#6382)
* added AnimationStartedEvent

* added AnimationStarted event to Control

* reordered Control.AnimationStarted above Control.AnimationCompleted

* fixed comment

* switched to internal constructor and proxy method
2026-01-19 23:29:22 +01:00
PJB3005
65ed19fa4e Version: 271.0.0 2026-01-19 21:07:22 +01:00
PJB3005
30cd9eb527 Update release notes 2026-01-19 21:06:58 +01:00
PJB3005
3f556814a5 Cull old release notes
This file was hundreds of kilobytes and genuinely made my Rider lag to edit
2026-01-19 21:06:25 +01:00
ThereDrD
17662baaf7 Fixed looped audio playback position calculation (#6325)
* fix: looped audio position calculation

* refactor: tiny clean up

* move comment

* fix: review changes

* remove `

* rerun tests

* Remove unecessary formatting changes

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2026-01-19 21:05:44 +01:00
Pieter-Jan Briers
dc1464b462 High-bandwidth transfer system (#6373)
* WebSocket-based data transfer system

* Move resource downloads/uploads to the new transfer system

Should drastically increase the permitted practical size

* Transfer impl for Lidgren

* Async impl for receive stream

* Use unbounded channel for Lidgren

* Add metrics

* More comments

* Add serverside stream limit to avoid being a DoS vector

* Fix tests

* Oops forgot to actually implement sequence channels in NetMessage

* Doc comment for NetMessage.SequenceChannel

* Release notes
2026-01-19 20:44:44 +01:00
DrSmugleaf
48654ac424 Fix component tree system crapping out taking half the game with it (#6366)
* Fix component tree system crapping out taking half the game with it

* Fix build
2026-01-19 20:43:50 +01:00
ArtisticRoomba
d9ea1079f7 Fix Robust.Benchmarks failing to compile (#6365) 2026-01-19 20:42:03 +01:00
DrSmugleaf
cb384b8242 Make VV work with structs in components (#6377)
* Make VV work with structs in components

* Fix missing imports
2026-01-19 20:41:24 +01:00
Pieter-Jan Briers
21581df93d Revert "make SharedAudioSystem.Stop not return early when the current tick has already been predicted" (#6375)
Revert "make SharedAudioSystem.Stop not return early when the current tick ha…"

This reverts commit c41d63be27.
2026-01-19 20:40:31 +01:00
Princess Cheeseballs
df98bca4bc Fix Erronius entity deletion on grid deletion from SetTiles() (#6367)
Don't delete the grid *before* we raise the event???

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-19 20:39:49 +01:00
PJB3005
02b64b7386 Show task bar progress bar for loading progress
Using the new API in SDL 3.4.0
2026-01-18 23:30:15 +01:00
PJB3005
36e5f10511 Update SDL3 to 3.4.0 2026-01-18 23:30:15 +01:00
Richard Van Tassel
93d14d55c7 Let viewport clear when eye is missing (#6379)
* Adds option to viewport to allow render target clearing if eye is missing

* on->when and add to IClydeViewport

* add to ClydeHeadless
2026-01-18 21:17:31 +01:00
pathetic meowmeow
c20343601d Make tabs wrap to available size (#6387) 2026-01-18 21:16:44 +01:00
deltanedas
52d3376c9e make "failed to set destination" debug assert not useless (#6383)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2026-01-18 09:27:50 -05:00
PJB3005
a417a8fd99 Mark DebugTools.AssertNotNull as [NotNull]
This means C# nullable analysis actually recognizes it.
2026-01-18 02:42:23 +01:00
Aiden
627856e207 Update error message for direct project references. (#6380)
Update error message for direct project references
2026-01-16 14:55:51 +01:00
Dinner
aa5cca4c7f Fix Layer constructor in SpriteComponent not properly copying unshaded sprite layers (#6368)
* remove Layer() null check for Unshaded and ShaderPrototype variables

* remove comments
2026-01-11 04:03:05 +01:00
eoineoineoin
5b06066fcb Make some Control properties animatable (#6376) 2026-01-10 23:35:08 +01:00
deltanedas
736e46cd82 fix MarkupNode.ToString mangling tags (#6374)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2026-01-09 20:17:46 +01:00
PJB3005
539d0563b8 "Add vorbis" she said. "It'll be easy"
Yeah so this adds libogg, libvorbis, and libopus to the Rust library on the client. This is intended for use by the client soon-ish to replace .NET ogg vorbis implementations and add opus support.

This turns out to be a huge pain thanks to https://github.com/rust-lang/rfcs/issues/2771 . To solve this I ended up compiling the projects as staticlib and creating a build.py script to invoke the linker. This sucks a *lot* and I have yet to write the linker invocations for Linux/Windows, but it's probably the best option we have.
2026-01-04 04:16:20 +01:00
PJB3005
d9740e3a4f Discover the magic of +whole-archive to avoid relying on hacks to load the objective-C++ code in webview native 2026-01-04 04:16:20 +01:00
slarticodefast
89b6bcd8e2 fix console warning spam (#6363)
fix console spam
2026-01-02 20:30:39 -05:00
TGRCDev
7b245260e3 SpriteView updates its size whenever the entity is set (#6362)
* SpriteView updates its size whenever the entity is set

* SpriteView recalculates its measure when the entity changes
2026-01-02 16:41:20 +01:00
PJB3005
68f8d00931 Oops, typo. Actual version is 270.1.0 2026-01-01 12:37:20 +01:00
PJB3005
57ad191d02 Version: 170.1.0 2026-01-01 12:35:52 +01:00
PJB3005
8cecdcc8de Oops one last one 2026-01-01 12:35:42 +01:00
PJB3005
de7cdec86e Update release notes 2026-01-01 12:34:53 +01:00
PJB3005
8ffa85c266 Move WebView appbundle stuff to imports folder 2026-01-01 12:31:08 +01:00
DrSmugleaf
e5be11458e Fix EntProtoId<T>.TryGet throwing an error on invalid prototype ids (#6349) 2026-01-01 00:53:14 +01:00
āda
9464ccb500 Get hard collision API method (#6354)
commit

Co-authored-by: iaada <iaada@users.noreply.github.com>
2025-12-31 23:20:14 +01:00
PJB3005
af7f4ec8e7 Make RichTextLabel.Text allow all tags again
Fixes #6360

I debated doing this with a special case in Robust.Xaml instead, but decided against it. This is mostly used for static contents anyways.
2025-12-31 23:15:06 +01:00
slarticodefast
de2dafe507 increase Loc.GetString log level to warning (#6361)
* increase loglevel to warning

* add release notes

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-12-31 19:49:05 +01:00
PJB3005
dd41a7ce44 Use hard links for macOS app bundles
Avoid needing to get executable path from MainModule, which broke the game when run with the dotnet command instead of the bin's apphost. Fixes tests.
2025-12-31 18:46:46 +01:00
PJB3005
c25f6c5e98 Merge branch '25-11-01-cef-update' 2025-12-29 18:19:03 +01:00
Axionyx
2799de33c5 Make run_server.bat cd to the current directory (#6356)
Add a cd to the current directory 

Will literally only affect people doing a "Run as administrator" as far as I'm aware, but still good to have I suppose
2025-12-28 20:50:15 +01:00
PJB3005
1fea48fbf4 Don't disable GPU compositing in CEF
Workaround no longer needed with message pump fixed. I think.
2025-12-28 02:16:23 +01:00
PJB3005
636e287fc5 Add new msbuild files to slnx 2025-12-27 22:29:15 +01:00
PJB3005
d43c3f2caf Merge remote-tracking branch 'origin/master' into 25-11-01-cef-update 2025-12-27 01:50:23 +01:00
PJB3005
a1dddf6af1 More macOS fixes, introduce RUST code 2025-12-27 01:44:57 +01:00
PJB3005
5f1327808d Mark IRobustRandom.GetRandom() as obsolete
This API should've never existed.
2025-12-24 16:03:06 +01:00
Princess Cheeseballs
d78e3ce157 Extend AddMessage capabilites in RobustToolbox. (#6350)
push or w/e

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-24 15:42:40 +01:00
PJB3005
6d31d5ba24 Version: 270.0.0 2025-12-23 23:21:37 +01:00
PJB3005
773357a80d Update release notes 2025-12-23 23:21:16 +01:00
PJB3005
63ef667167 Add [NotContentImplementable] to server interfaces 2025-12-23 18:44:01 +01:00
PJB3005
e8f2972f69 Remove unused IPlayerInput interface 2025-12-23 18:43:52 +01:00
PJB3005
ad056e0b45 Add [NotContentImplementable] to client interfaces 2025-12-23 18:34:27 +01:00
PJB3005
6f2c45aab7 Remove unused IRand interface 2025-12-23 18:33:44 +01:00
PJB3005
9a3aad4630 Delete unused IRenderableComponent 2025-12-23 18:25:36 +01:00
Myra
5e55effc73 Downgrade vorbispizza to 1.3.0 electric boogaloo (#6351)
* Downgrade vorbispizza to 1.3.0 electric boogaloo

#5607 again

Even though a fix on vorbispizza was made by pjb **this version was never actually released**

Unless we can get in contact with the dev of vorbispizza this is the easiest way to solve the issue with audio missing again

* Add comment
2025-12-23 18:14:41 +01:00
PJB3005
e308b89fe6 Log string map size 2025-12-22 17:56:16 +01:00
B_Kirill
68609a94d8 Fix inverted clipboard error check in SDL3 (#6348) 2025-12-22 17:35:09 +01:00
PJB3005
76727cec5f Give timescale a default value to fix tests 2025-12-22 01:49:45 +01:00
PJB3005
bb81d88653 Add [NotContentImplementable] to shared interfaces 2025-12-20 16:49:35 +01:00
PJB3005
9b02a4e718 Add game.time_scale cvar
Primary use case (other than silly) is to be a better way to speed up/slow down replays.
2025-12-20 16:32:06 +01:00
PJB3005
2e5856b54d Add missing changelog for 077ad1929e 2025-12-20 16:29:35 +01:00
PJB3005
9802963933 Add [NotContentImplementable] attribute 2025-12-20 14:11:36 +01:00
Fruitsalad
43337a3743 Fix layouting bug in SS14 ghost role menu (space-wizards/space-station-14#41434) (#6323) 2025-12-19 23:49:52 +01:00
PJB3005
c7ba63ed8e Version: 269.0.1 2025-12-19 18:06:06 +01:00
Pieter-Jan Briers
5ec8589f40 Fixed transitive project dependencies in content triggering "no direct project reference" detection. (#6343)
* Fixed transitive project dependencies in content triggering "no direct project reference" detection.

* Add chicken property
2025-12-19 17:55:49 +01:00
ArtisticRoomba
077ad1929e Expand SharedUserInterfaceSystem API (#6342)
* Expand SharedUserInterfaceSystem API

* addr reviews
2025-12-18 22:05:33 +01:00
PJB3005
a74f755692 Version: 269.0.0 2025-12-17 20:23:20 +01:00
PJB3005
e1b70982f0 Mute NU1510 warnings from .NET 10 2025-12-17 20:22:58 +01:00
PJB3005
23de5c4044 Update release notes AGAIN 2025-12-17 20:11:03 +01:00
PJB3005
0498036f1c Remove kdialog/nfd file dialog implementation
Purely rely on the SDL3 one now.
2025-12-17 20:03:15 +01:00
Tayrtahn
dd86bf980d Add MeansDataDefinition support to DataDefinitionAnalyzer (#5699)
* Add MeansDataDefinition support to DataDefinitionAnalyzer

* Poke tests

* Fix violations in engine

* Fix more lacking partials

* Fix tests

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-12-17 19:47:30 +01:00
Tayrtahn
d7abbad717 Add validation for DirtyField strings (#5713)
* Add ValidateMemberAttribute, analyzer and test

* Use attribute on DirtyFields methods

* Defer member lookup

* Additional test case

* Add support for collection types

* Poke tests

* Revert "Add support for collection types"

This reverts commit 2b8f5534bd.

* break, not continue

* Cheaper attribute check with AttributeHelper

* Clean up unused helper method

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-12-17 19:32:34 +01:00
PJB3005
7826e9e365 Add GitHub workflows to solution file 2025-12-17 18:18:47 +01:00
PJB3005
5476cdeff9 Run all tests on GitHub Actions 2025-12-17 18:18:47 +01:00
Tayrtahn
c1737a540f Analyzer & Fixer for redundant Prototype type strings (#5718)
* Add Prototype analyzer

* Add Prototype fixer

* Early return after finding prototype attribute

* Add PrototypeEndsWithPrototypeRule diagnostic

* Oops. Uncomment parallelizable.

* Rework to ignore redundancy for non-literal string values

* Allow redundancy when removal would expose class name not ending in "Prototype"

* Promote PrototypeEndsWithPrototypeRule from warning to error, since it causes a runtime error.

* No need to get the symbol to get the class identifier

* Minor cleanup

* A little more cleanup

* More specific location for redundant name

* Refactor redundant name fixer so argument order is no longer important

* Add failing test

* Use symbol analysis to fix alias handling

* Oops! We have to go back to the previous syntax-based approach.

Now it's a hybrid.

Also fixed tests to not copy the prototype definitions.

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-12-17 18:15:32 +01:00
PJB3005
ddfa12808c Fix some roslyn tests 2025-12-17 16:55:33 +01:00
PJB3005
e3fb7e620d Add WebView import file 2025-12-17 16:32:14 +01:00
PJB3005
c94e1742cb Fix warning in WebView 2025-12-17 16:29:39 +01:00
PJB3005
27fd737e6a Fix WebView build 2025-12-17 16:29:33 +01:00
PJB3005
568880d09d Update release notes 2025-12-17 00:17:24 +01:00
PJB3005
7f2ec17651 Okay, the Robust API thing didn't pan out. New plan.
It apparently broke clean builds, as the dependencies aren't in the project asset list or something anymore. I tried to fix this, but it seems impossible to do without relying on .NET SDK internals, as there's no point in the NuGet graph walk process that seems cleanly extensible.

Instead let's just do the much dumber thing: a bunch of .props files for content to import. Hooray!

This also means that I have to go through and *explicitly* disable transitive dependencies everywhere in RT. This thankfully isn't too hard.
2025-12-16 22:56:31 +01:00
PJB3005
39e30531e2 Bump to .NET 10
Idk maybe it'll use some more efficient overloads or something
2025-12-16 16:08:55 +01:00
PJB3005
e26bda4c8f I'm so good at this. 2025-12-16 16:06:03 +01:00
PJB3005
9137ab2c45 Cull slow .NET source generators
We don't need em
2025-12-16 16:04:45 +01:00
PJB3005
5af03247a7 Add missing Robust.Benchmarks project entry to solution 2025-12-16 16:04:45 +01:00
PJB3005
352ae60f42 Fix compile in Robust.benchmarks 2025-12-16 16:04:45 +01:00
PJB3005
d31db257ea Fix _RTMakeProjectReferences ordering issue 2025-12-16 16:04:45 +01:00
deltanedas
d548bce347 make EntityQuery.Resolve error not useless (#6320)
* make EntityQuery.Resolve error not useless

* it actually wasnt that bad

* goida

* make EntityQuery constructor internal

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
2025-12-16 14:23:47 +01:00
PJB3005
3246ad2d92 RobustApi system
Bans content from directly referencing Robust projects, instead it must go through new MSBuild items <UseRobustApi>. This way we can move types between RT projects without fear of causing breaking changes.
2025-12-16 05:29:59 +01:00
PJB3005
b4615b9c7a TestingParallelManager is apparently used by content 2025-12-16 05:25:41 +01:00
PJB3005
788e9386fd Split up test project
Robust.UnitTesting was both ALL tests for RT, and also API surface for content tests.

Tests are now split into separate projects as appropriate, and the API side has also been split off.
2025-12-16 01:36:53 +01:00
PJB3005
095c5f58d9 Obliterate GLFW with an orbital strike 2025-12-15 22:45:39 +01:00
PJB3005
b2bf5f9781 Solution file generation system
Fixes #6318
2025-12-15 22:40:42 +01:00
PJB3005
b936a77207 Clean up solution file 2025-12-15 22:40:42 +01:00
PJB3005
7da11b01c9 slnx now has size-2 indents 2025-12-15 22:40:42 +01:00
PJB3005
4f21c8c9b2 Move default marker filename to SLNX
Will be alright because next engine version will demand SLNX templating
2025-12-15 22:40:42 +01:00
slarticodefast
4747e5a05a Add and update a lot of documentation (#6337)
* Serialization docs

Co-authored-by: Moony <moonheart08@users.noreply.github.com>

* ECS docs

Co-authored-by: Moony <moonheart08@users.noreply.github.com>

* scattered docs

Co-authored-by: Moony <moonheart08@users.noreply.github.com>

* Fixes

---------

Co-authored-by: Moony <moonheart08@users.noreply.github.com>
Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-12-15 20:26:17 +01:00
haiwwkes
cfefd760e2 fix webview resize (#6340) 2025-12-15 19:37:28 +01:00
Pieter-Jan Briers
d161c3b3b8 Add FormattedString type (#6339)
This is basically a lightweight marker type saying "this string contains markup". Intended to avoid injection accidents if people don't realize they should escape stuff.
2025-12-15 19:36:16 +01:00
PJB3005
53e1222b6b Fix FormattedMessage not escaping text when converted to markup
Meant that ToMarkup() wasn't actually round-trip safe.
2025-12-15 04:58:23 +01:00
PJB3005
5f073dff35 Downgrade DiscordRichPresence to fix logged NRE.
Fixed in https://github.com/Lachee/discord-rpc-csharp/pull/282 but needs a new release made.
2025-12-14 22:28:56 +01:00
PJB3005
8de8f5dc7f Add AVX10 as logged intrinsics 2025-12-14 21:56:08 +01:00
PJB3005
e9dc40be24 Report available memory in startup info 2025-12-14 21:55:58 +01:00
PJB3005
95d5f7eec1 Add flag to block command bindings while UI focused
This means you can bind stuff to your numpad without it firing while you're *actually* typing stuff.
2025-12-14 21:48:20 +01:00
haiwwkes
497ca865dc Skip redundant texture uploads in WebView when content unchanged (#6338)
Add dirty flag to WebView to skip unnecessary texture uploads.
2025-12-14 20:01:30 +01:00
PJB3005
b4a0dad67a Fix tests on .NET 10
Fixes #6329
2025-12-14 17:46:41 +01:00
kosticia
f1c30128d6 Fix typos in EntityDeserializaer (#6336)
good
2025-12-10 19:20:00 +01:00
Princess Cheeseballs
ad31ec64d3 Add some default Sanitization to RichTextEntry (#6333)
* PR time

* am dum dont make PRs at 3am

* file scoped. I pushed this to the wrong branch initially because I'm stupid

* Revert "file scoped. I pushed this to the wrong branch initially because I'm stupid"

This reverts commit 9a911caa77.

* Review

* Update API used by content to actually use the default values

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-12-10 19:09:51 +01:00
PJB3005
fe35a24e88 Basic prototype string interning
From my extremely rough and unscientific tests, this saves like 15 MB of client memory on the main menu. Probably also just improves load speed on startup too.

It's per file to keep the implementation simple.
2025-12-09 12:31:50 +01:00
CaasGit
e875d89e14 fix(LoaderApi): Update submodule of LoaderApi. (#6332)
LoaderApi sync to latest commit.
2025-12-09 10:25:13 +01:00
PJB3005
e41165f9dd Force RT's global.json on SS14 in content test
So that it uses the right .NET SDK version when we update like this.
2025-12-08 00:56:52 +01:00
PJB3005
0414cde339 Remove recursion from TilesEnumerator
We love stack overflowing, Rider refactor go brr

Fixes #6330
2025-12-08 00:54:13 +01:00
PJB3005
8580ab93a6 Dependency update 2025-12-08 00:49:37 +01:00
PJB3005
1a0dab4526 OkLAB test in uitest
I can just add whatever random shit I want in there, nobody will stop me
2025-12-05 17:34:20 +01:00
PJB3005
dff5b5fe95 Prototype AspectRatioPanel UI control
Wrote this as a quicky, needed it for something. Don't feel like stabilizing it yet so it's internal.
2025-12-05 17:34:20 +01:00
CaasGit
d579c68082 fix(tools): Fix packaging libs and zips on linux. (#6327)
Both OSX and Linux builds were not excluding two libs from their zip
file due to a missing comma. You can see that they both exist within the
live client manifests.

Also fix the MS dlls having basically no date set on the file which is
throwing an error when trying to zip them.
2025-12-05 16:48:35 +01:00
Jewel
2c3cbaf3b9 Fix word wrapping on non-runed inline metrics like links (#6267)
Fix word wrapping on non-runed metrics like links (previously they would never wrap if they extended past max width)
2025-12-04 20:07:15 +01:00
PJB3005
4c961c627c I did not realize we have a global.json 2025-12-04 19:37:47 +01:00
PJB3005
b6b992ba49 Migrate to SLNX
Not relevant for content. Yet.
2025-12-04 19:30:35 +01:00
PJB3005
20d97ff855 Add ExtensionMarkerAttribute to sandbox
Necessary for C# 14 extension members
2025-12-04 19:08:20 +01:00
PJB3005
9d45357ca9 Enable C# 14, fix a nullability error 2025-12-04 17:49:46 +01:00
PJB3005
b0636c351c Update GitHub Actions to .NET 10 2025-12-04 17:38:01 +01:00
PJB3005
dab7ba13f5 .NET 10
Fixes #6302
2025-12-04 16:46:51 +01:00
PJB3005
fde2a83f03 Version: 268.1.0 2025-12-04 16:35:46 +01:00
PJB3005
35fb4cc832 Update release notes 2025-12-04 16:34:49 +01:00
PJB3005
6bbeaeeba6 Fix NetBitArraySerializer compatibility.
Apparently NetSerializer treats IDynamicTypeSerializer and IStaticTypeSerializer differently for sealed types??
2025-12-02 00:51:04 +01:00
wixoa
f2d74df4d7 Fix WebView loading (#6321) 2025-12-01 15:02:43 +01:00
B_Kirill
3175a6cb2b Cleanup warnings: CS0162, CS0618, CS0649 (#6309)
* Cleanup

* Revert TagHandler for now

* Probably deleting it would be better
2025-12-01 15:02:25 +01:00
Nemanja
465a1fb5bd Allow content to override ProcessStream and GetOcclusion in AudioSystem (#6268)
* Add optional override methods for ProcessStream and GetOcclusion

* Mark override events as static

* Change audio actions to non-static
2025-11-30 17:32:26 +01:00
LaCumbiaDelCoronavirus
c41d63be27 make SharedAudioSystem.Stop not return early when the current tick has already been predicted (#6137)
1 line diff
2025-11-30 17:02:35 +01:00
Myra
28f7d6497e Add Account Creation time from auth to the userdata (#6278)
Add CreatedTime from auth to the userdata

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-11-30 17:01:08 +01:00
eoineoineoin
83a1098476 Fix bug where ButtonGroup would erroneously unpress buttons (#6271) 2025-11-30 16:57:12 +01:00
ArtisticRoomba
107b7c5077 IParallelBulkRobustJob (#6307)
* init commit

* thank you tests
2025-11-30 16:30:45 +01:00
Peptide90
879ec72847 Add noSpawn property to audio entity (#6312)
* Add noSpawn property to audio entity

Stops audio entity showing in spawn menu

* Replace noSpawn with category HideSpawnMenu
2025-11-30 16:25:22 +01:00
Princess Cheeseballs
1bcf04ef76 Revert "Double-buffer contact events (#6295)" (#6314)
This reverts commit cd59027089.

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2025-11-30 16:24:55 +01:00
Szunti
899eef397c Make Caps Lock bindable (#6308) 2025-11-24 11:10:24 +01:00
Centronias
6aa4fb9594 Make SpriteSystem.LayerExists say layer index 0 is valid (#6305) 2025-11-21 22:04:20 +01:00
ToastEnjoyer
a9ba9192ac Removed sloth from physics codeowner (#6303) 2025-11-18 17:27:43 +01:00
PJB3005
b8ff394f73 Fix spamming startup error messages for projects without splash logo 2025-11-16 15:01:46 +01:00
PJB3005
b267cd6fb4 Actually add NetBitArraySerializer to RobustSerializer
oops

#6301
2025-11-15 18:46:01 +01:00
PJB3005
83ad6042a7 Fix BitArray serialization for .NET 10
Fixes #6301
2025-11-15 18:41:43 +01:00
metalgearsloth
46b65260c4 Consolidate and update physics benchmarks (#6298)
* Add

* a

* Fix test

* note

* Consolidate physics benchmarks

* Also add smash

* a

* Fix this
2025-11-15 17:54:05 +11:00
metalgearsloth
663b83821a Revert "Obsolete static IoC methods (#6232)" (#6296)
This reverts commit 14439784dd.
2025-11-15 11:26:52 +11:00
Leon Friedrich
64baee0a22 Add support for including map/game saves in replays. (#6189)
* Improve map serialization error logging

* Prevent remove children of erroring entities

* better logging

* Improve error tolerance

* Even more exception tolerance

* missing !

* Add WriteYaml and WriteObject to IReplayFileWriter

* Add MapLoaderSystem.TrySaveAllEntities()

* On second thought, WriteObject will just be abused

* I forgot to commit

* Add default implementation to avoid breaking changes

* release notes

* fix merge issues

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2025-11-13 22:14:56 +11:00
metalgearsloth
dbde8023ed Version: 268.0.0 2025-11-13 21:48:48 +11:00
DrSmugleaf
83279ff285 Fix YAML linter not printing the erroring file when failing to load it (#6256) 2025-11-13 21:35:42 +11:00
Leon Friedrich
14439784dd Obsolete static IoC methods (#6232) 2025-11-13 21:35:14 +11:00
Leon Friedrich
80cad0cd8f Add public control sawmill to UI Manager (#6231)
* Add public control sawmill

* Remove static IoCManager in MarkupTagManager

* very important comment

* release notes

* fix merge

* fix
2025-11-13 21:33:50 +11:00
Leon Friedrich
b15d960c69 Try improve RaiseEvent performance (#5925)
* IEventBus -> EventBus

* Fix CompIdx.RefArray

* IEntityManager -> EntityManager

* Remove double delegate

* Add GetNetCompEventHandlers()

* release notes & cleanup

* Make PVS use GetNetCompEventHandlers

* I love event bus tests

* also fix pvs pause test

* dont trimm

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2025-11-13 21:32:40 +11:00
metalgearsloth
cd59027089 Double-buffer contact events (#6295)
* Add

* a

* Fix test

* note

* Fix reflection
2025-11-13 21:28:13 +11:00
metalgearsloth
015bf3318b Generic GetMassData (#6291)
* Generic GetMassData

Nothing fancy. Also made the class version use the struct.

* Slim method

* Fix test
2025-11-10 18:30:24 +11:00
Leon Friedrich
3f19d25018 Box Simd (#6193)
* Box Simd

* Add 256 bit version of GetAABB

* Add AABB bechmarks

* No real diff between 128 & 256, so removing 256

| Method     | Mean      | Error     | StdDev    | Ratio |
|----------- |----------:|----------:|----------:|------:|
| GetAABB    | 5.8107 ns | 0.0154 ns | 0.0137 ns |  1.00 |
| GetAABB128 | 0.4927 ns | 0.0003 ns | 0.0002 ns |  0.08 |
| GetAABB256 | 0.4332 ns | 0.0006 ns | 0.0006 ns |  0.07 |

* Add Box2Rotated.Transform Benchmark

* Results

20% faster and much smaller code. Also I don't think it inlined RotateVec

* Add Matrix3x2Helper.TransformBox() benchmark

new:

| Method    | Mean     | Error     | StdDev    | Code Size |
|---------- |---------:|----------:|----------:|----------:|
| Transform | 2.463 ns | 0.0766 ns | 0.0679 ns |     216 B |

old:
| Method    | Mean     | Error     | StdDev    | Median   | Code Size |
|---------- |---------:|----------:|----------:|---------:|----------:|
| Transform | 9.469 ns | 0.2140 ns | 0.5408 ns | 9.206 ns |     621 B |

* Fix polygon constructor

* SlimPolygonBenchmark

* use new SimdHelper for other methods

* Fix bugs

* Use new methods

* Simd SlimPolygon.ComputeAABB

* Move simd transform to physics

* Cleanup

* Remove uneccesary Unsafe.SkipInit

* These tests all work on master

* Add Transform.MulSimd test

* Add SlimPolygon constructor tests

* Add ComputeAABB test

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2025-11-10 18:30:08 +11:00
metalgearsloth
fe19ff9bd5 Fix physics events not being re-run in prediction (#6261)
* Fix physics events not being re-run in prediction

Someone should probably network contacts someday.

* Documentation
2025-11-09 19:37:12 +11:00
metalgearsloth
c95b4320cf Version: 267.4.0 2025-11-09 18:44:49 +11:00
PJB3005
602d7833a1 Initial macOS WebView support
This is a gigantic kerfuffle because Chromium expects a very specific directory & app bundle layout. Have to change a bunch of resource loading code to account for content development being launched from an app bundle, and also had to make automatic MSBuild tooling & a python script to generate such an app bundle
2025-11-09 00:06:16 +01:00
metalgearsloth
4755cb5747 Better broadphase performance (#6272)
* Better broadphase parallelism

Moves more stuff into the parallel loop and avoids allocating the list per fixtureproxy.

* Fixes

* Better docs

* doc
2025-11-08 02:11:31 +11:00
PJB3005
7bc0ffb711 Make XAML hot reload JIT on UI load
This means we don't have to JIT a bunch of UIs that you might not open, reducing memory usage and startup overhead.

One (1) UI is always JITed in another thread before prototype UIs are loaded, so as to warm up the JIT machinery. Said type is DropDownDebugConsole which always gets used anyways so there's no harm in it.

In total, these changes save more than a second of startup time for me.
2025-10-30 01:41:17 +01:00
beck-thompson
e49515956a Add a basic loading screen! (#6003)
* Added basic loading screen

* Make it look better!

* I forgor xD

* Fix test fails

* Add comment

* Removed unused import

* Only write to file if the number of sections changed

* Servers can now have their own settings

* Minor optionzation and rare colors

* Remove some of the cvars

* debug only loading messages

* Added a few more steps

* Only one section at a time

* nullable section name

* Lock out functions if finished

* Get rid of saving the ccvar

* Cleanup

* Forgot!

* A few tweaks

* Disable vsync

* remove colors

* remove outdated vsync functions

* Silly me xD

* What I get for trying to be clever... ;(

* Better seconds display

* Simplify drawing logic + it looks better

* Type does not need to be partial

* Make interface to expose to content

* Use correct define to gate showing debug info

Should be TOOLS instead of DEBUG

* Use appropriate exception type in BeginLoadingSection

* Fix exception when closing window during loading screen

Would try to stop the main loop before it exists.

* Rename CVars, put debug info behind CVar instead of conditional compilation.

* Add to RELEASE-NOTES.md

* Add UI scaling support

* Make ILoadingScreenManager fully internal

Didn't realize content can't touch it as it'd break the total amount of sections

* Don't re-enable vsync manually, GameController does it at the end of init

* Add command to show top load time usage.

* Improve verbosity of debug time tracking

More steps and some steps named better

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-10-30 00:38:59 +01:00
Pieter-Jan Briers
f3a3f564e1 System font API (#5393)
* System font API

This is a new API that allows operating system fonts to be loaded by the engine and used by content.

Fonts are provided in a flat list exposing all the relevant metadata. They are loaded from disk with a Load call.

Initial implementation is only for Windows DirectWrite.

* Load system fonts as memory mapped files if possible.

This allows sharing the font file memory with other processes which is always good.

* Use ArrayPool to reduce char array allocations

* Disable verbose logging

* Implement system font support on Linux via Fontconfig

* Implement macOS support

* Add "FREEDESKTOP" define constant

This is basically LINUX || FREEBSD. Though FreeBSD currently gets detected as LINUX too. Oh well.

* Compile out Fontconfig and CoreText system font backends when not on those platforms

* Don't add Fontconfig package dep on Mac/Windows

* Allow disabling system font support via CVar

Cuz why not.
2025-10-28 22:07:55 +01:00
Leon Friedrich
8b7fbfa646 Add two new custom yaml serializers (#6253)
* Add two new custom yaml serializers

* make ComponentNameSerializer ignore ignored components

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2025-10-27 23:05:46 +11:00
Leon Friedrich
bb4c4ed302 Fix PredictedQueueDeleteEntity mispredicts (#6260)
* Fix PredictedQueueDeleteEntity

* typo

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2025-10-27 23:04:12 +11:00
PJB3005
c9009342b6 Move CVar registration to before config load on server
Fixes error on startup with the rollback system.
2025-10-26 23:14:11 +01:00
PJB3005
3a337e4842 SECURE CVars are no longer a thing
Good riddance
2025-10-26 23:13:42 +01:00
PJB3005
e788325cdb Expose more StringBuilder overloads to sandbox
Just some stuff that got added in the years. Spans, interpolated string handlers.
2025-10-26 23:09:59 +01:00
PJB3005
9a0e3b6b02 dmetamem now sorts results, no longer outputs to log to avoid interleaving 2025-10-26 23:09:37 +01:00
PJB3005
37eabbabc2 Add MetadataUpdateHandlerAttribute to sandbox 2025-10-26 22:53:06 +01:00
PJB3005
ab775af7cd Added FontTagHijackHolder to replace fonts resolved by FontTag.
Existing font prototype system is extremely half-baked and unusable. This allows bypassing it entirely for upcoming content changes.
2025-10-26 20:40:39 +01:00
PJB3005
8ac5fc58d2 Invalidate OutputPanel on style change
Fixes an incorrect height staying cached on font change.
2025-10-26 20:37:42 +01:00
PJB3005
37c7aa544e Properly use arrange in rich text control layout
This code is still broken, but this at least fixes the fact they don't get arranged with a proper size.

Supersedes #6269
2025-10-26 17:42:37 +01:00
PJB3005
7542b1ca16 Don't do work if assigning stylesheet a control already has 2025-10-26 17:40:19 +01:00
PJB3005
8235bd8478 OptionButton can now be filtered 2025-10-26 17:38:41 +01:00
PJB3005
1657a49c1c Fix modifying Label.FontOverride not causing a layout update. 2025-10-25 17:56:58 +02:00
Myra
669b515ce6 Ensure that sdl3 is the fallback if unknown windowingAPI is specified (#6266)
* Ensure what sdl3 is a fallback if unknown windowingAPI is specified

Webedit ops

* I am blind
2025-10-23 23:43:57 +02:00
metalgearsloth
8478e62a3e Add pure to some transform methods (#6262)
Useful IDE stuff
2025-10-22 18:47:02 +02:00
PJB3005
034728258c Add config rollback system
This is intended for content-side settings menus, so we can show users a "does this look correct" prompt after changing sensitive settings like graphics or UI, without risking an untimely config save *storing* broken CVar config.
2025-10-22 14:09:40 +02:00
PJB3005
b0fec0fd76 CVars defined in [CVarDefs] can now be private or internal. 2025-10-22 14:06:33 +02:00
Leon Friedrich
665294bee8 Rethrow more exceptions when EXCEPTION_TOLERANCE is false (#6238)
* Rethrow more exceptions when EXCEPTION_TOLERANCE is false

* A

* update test

* Revert "update test"

This reverts commit 37f4da67fc.

* actually we probably want to know if Deleting an exception throwing entity throws another exception
2025-10-20 20:51:24 +02:00
PJB3005
4b04081749 Fix Menu and NumpadDecimal key codes on SDL3
Fixes #6255
2025-10-16 14:39:08 +02:00
Amy
d3a9199b8e my hatred for yaml is building (#6226) 2025-10-15 23:28:47 +02:00
PJB3005
6fb9ff7554 Improve viewport leak logging
Shows name

Also fixes erroneous leak logging oops
2025-10-15 01:23:44 +02:00
ElectroJr
feb9e1db69 Version: 267.3.0 2025-10-14 22:35:41 +13:00
Leon Friedrich
613705613b Fix bug in OccluderSystem.InRangeUnoccluded (#6247) 2025-10-12 12:09:42 +13:00
Pok
80a053c0a9 command-ftl (#6248) 2025-10-10 19:10:40 +02:00
Leon Friedrich
657455dae0 Add abstract tile debug overlay & command (#6213)
* Add generic debug overlay & command

* fix

* Fix overlays

* a

* comments

* comments

* comment 2
2025-10-09 17:49:17 +13:00
Leon Friedrich
8ae35e12ee Update ComponentTreeSystem (#6211)
* Allow component trees to be disabled

* forgot

* I'm pretty sure this wasn't working as intended

* also outdated

* reduce branches in QueueTreeUpdate

* remove update hashset

* try fix

* Use Entity<T> and add ray overloads

* Move InRangeUnoccluded to engine

* reduce code duplication

* move _initialized check

* release notes
2025-10-09 17:30:00 +13:00
Leon Friedrich
4e2c0e431b Fix MapLoaderSystem.SerializeEntitiesRecursive (#6246)
* Fix MapLoaderSystem.SerializeEntitiesRecursive

* a

* oops
2025-10-08 17:50:50 +13:00
MilenVolf
9c41f19eaf Recursively update joint relays on removing entities from containers (#6244)
* Recursively update joint relays on removing entities from containers

* release notes
2025-10-08 17:50:07 +13:00
Nemanja
f75ce13f00 Always align newly created entities with the grid (#5915)
* align spawns with grids

* :godmode:

* Fix comment

* fix

* release notes
2025-10-06 17:37:48 +13:00
Rouden
ac45a0a64b MapId, MapCoordinates, EntityCoordinates Type serializers (#6165)
* New Type Serializers

* Delete NetCoordinatesSerializer.cs

* Make EntityCoordinates and MapCoordinates use DataRecord

* Turn them into actual record structs

I'm somewhat surprised the DataRecord attribute doesn't check this

* Allocate MapIds before deserializing components

* Deserialize preallocated ids

* fix map merge assert

* remove old

* Use TryGetMap

* release notes
2025-10-06 15:27:08 +13:00
1091 changed files with 19454 additions and 20612 deletions

View File

@@ -19,7 +19,7 @@ resharper_place_field_attribute_on_same_line = if_owner_is_single_line
resharper_wrap_chained_binary_patterns = chop_if_long
resharper_wrap_chained_method_calls = chop_if_long
[*.{csproj,xml,yml,dll.config,targets,props}]
[*.{csproj,xml,yml,dll.config,targets,props,slnx}]
indent_size = 2
[nuget.config]

2
.github/CODEOWNERS vendored
View File

@@ -8,5 +8,3 @@
*Command.cs @moonheart08
*Commands.cs @moonheart08
# Physics
**/Robust.Shared/Physics/** @metalgearsloth

View File

@@ -22,7 +22,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore

View File

@@ -14,7 +14,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Install dependencies
run: dotnet restore

View File

@@ -22,12 +22,10 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.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
- name: Run tests
run: dotnet test --no-build -- NUnit.ConsoleOut=0

View File

@@ -23,7 +23,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Package client
run: Tools/package_client_build.py

View File

@@ -20,7 +20,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Disable submodule autoupdate
run: touch BuildChecker/DISABLE_SUBMODULE_AUTOUPDATE
@@ -30,6 +30,8 @@ jobs:
git fetch origin ${{ github.sha }}
git checkout FETCH_HEAD
git submodule update --init --recursive
- name: Replace global.json
run: cp RobustToolbox/global.json .
- name: Install dependencies
run: dotnet restore
- name: Build

27
Directory.Build.props Normal file
View File

@@ -0,0 +1,27 @@
<Project>
<ItemDefinitionGroup>
<PackageReference>
<!--
Make all packages used by RT default to having everything private except what's needed to run the game (runtime, native)
This is to avoid having them leak to content, as they are primarily an implementation detail.
Where necessary, this can be overriden by doing PrivateAssets="none" on the PackageReference entry.
-->
<PrivateAssets>compile;contentFiles;build;buildMultitargeting;buildTransitive;analyzers</PrivateAssets>
</PackageReference>
</ItemDefinitionGroup>
<PropertyGroup>
<!--
Disable transitive project references.
This is just to avoid venues of leaky implementation details into content.
Being strict is always good.
-->
<DisableTransitiveProjectReferences>True</DisableTransitiveProjectReferences>
<!--
Shut up broken package pruning warnings.
We need these unpruneable packages because of transitive dependency pinning.
-->
<NoWarn>$(NoWarn);NU1510</NoWarn>
</PropertyGroup>
</Project>

View File

@@ -10,73 +10,75 @@
<ManagePackageVersionsCentrally />
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageVersion Include="JetBrains.Profiler.Api" Version="1.4.8" />
<PackageVersion Include="JetBrains.Annotations" Version="2025.2.4" />
<PackageVersion Include="JetBrains.Profiler.Api" Version="1.4.10" />
<PackageVersion Include="Linguini.Bundle" Version="0.8.1" />
<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.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="5.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="5.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="5.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="5.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="5.0.0" />
<PackageVersion Include="Microsoft.CodeCoverage" Version="18.0.1" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="10.0.0" />
<PackageVersion Include="Microsoft.DotNet.RemoteExecutor" Version="8.0.0-beta.24059.4" />
<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.Extensions.Logging" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Primitives" Version="10.0.0" />
<PackageVersion Include="Microsoft.ILVerification" Version="10.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.NET.ILLink.Tasks" Version="10.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.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="NUnit" Version="4.4.0" />
<PackageVersion Include="NUnit.Analyzers" Version="4.11.2" />
<PackageVersion Include="NUnit3TestAdapter" Version="5.2.0" />
<PackageVersion Include="Nett" Version="0.15.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2" />
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.9.4" />
<PackageVersion Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
<PackageVersion Include="Pidgin" Version="3.3.0" />
<PackageVersion Include="Pidgin" Version="3.5.1" />
<PackageVersion Include="Robust.Natives" Version="0.2.3" />
<PackageVersion Include="Robust.Natives.Zstd" Version="0.1.1-zstd1.5.7" />
<PackageVersion Include="Robust.Natives.Cef" Version="131.3.5" />
<PackageVersion Include="Robust.Shared.AuthLib" Version="0.1.2" />
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.2" />
<PackageVersion Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.10" />
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog" Version="4.3.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.11" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.2.0" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
<PackageVersion Include="SpaceWizards.Sdl" Version="1.0.0" />
<PackageVersion Include="SpaceWizards.Sdl" Version="1.1.1" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.1.0" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.3.0" />
<PackageVersion Include="SpaceWizards.Fontconfig.Interop" Version="1.0.0" />
<PackageVersion Include="libsodium" Version="1.0.20.1" />
<PackageVersion Include="System.Management" Version="9.0.8" />
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />
<PackageVersion Include="TerraFX.Interop.Xlib" Version="6.4.0" />
<PackageVersion Include="System.Management" Version="10.0.0" />
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.5" />
<PackageVersion Include="TerraFX.Interop.Xlib" Version="6.4.0.2" />
<!-- Intentionally kept back, there is a bug that breaks audio for systems without AVX instructions. And the fix is not yet published. -->
<PackageVersion Include="VorbisPizza" Version="1.3.0" />
<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="prometheus-net.DotNetRuntime" Version="4.4.1" />
<PackageVersion Include="PolySharp" Version="1.15.0" />
<PackageVersion Include="System.CommandLine" Version="2.0.1" />
<!-- 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" />
<PackageVersion Include="System.Formats.Asn1" Version="10.0.0" />
<PackageVersion Include="System.Reflection.Metadata" Version="10.0.0" />
<PackageVersion Include="System.Text.Json" Version="10.0.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="5.0.0" />
</ItemGroup>
</Project>

6
Imports/Benchmarks.props Normal file
View File

@@ -0,0 +1,6 @@
<Project>
<!-- Include this .props file from content to get access to the APIs in these projects. -->
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Benchmarks\Robust.Benchmarks.csproj" />
</ItemGroup>
</Project>

6
Imports/Client.props Normal file
View File

@@ -0,0 +1,6 @@
<Project>
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client\Robust.Client.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Avalonia.Base\Avalonia.Base.csproj" />
</ItemGroup>
</Project>

6
Imports/Lidgren.props Normal file
View File

@@ -0,0 +1,6 @@
<Project>
<!-- Include this .props file from content to get access to the APIs in these projects. -->
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Lidgren.Network\Lidgren.Network.csproj" />
</ItemGroup>
</Project>

6
Imports/Packaging.props Normal file
View File

@@ -0,0 +1,6 @@
<Project>
<!-- Include this .props file from content to get access to the APIs in these projects. -->
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Packaging\Robust.Packaging.csproj" />
</ItemGroup>
</Project>

6
Imports/Server.props Normal file
View File

@@ -0,0 +1,6 @@
<Project>
<!-- Include this .props file from content to get access to the APIs in these projects. -->
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Server\Robust.Server.csproj" />
</ItemGroup>
</Project>

8
Imports/Shared.props Normal file
View File

@@ -0,0 +1,8 @@
<Project>
<!-- Include this .props file from content to get access to the APIs in these projects. -->
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Shared\Robust.Shared.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\NetSerializer\NetSerializer\NetSerializer.csproj" />
</ItemGroup>
</Project>

9
Imports/Testing.props Normal file
View File

@@ -0,0 +1,9 @@
<Project>
<!-- Include this .props file from content to get access to the APIs in these projects. -->
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Shared.Maths.Testing\Robust.Shared.Maths.Testing.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Shared.Testing\Robust.Shared.Testing.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Server.Testing\Robust.Server.Testing.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.UnitTesting\Robust.UnitTesting.csproj" />
</ItemGroup>
</Project>

14
Imports/WebView.props Normal file
View File

@@ -0,0 +1,14 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- If you are using Robust.Client.WebView, import this to depend on it. -->
<Import Condition="'$(_RTMacOSAppBundle_targets_imported)' != 'True'"
Project="$(MSBuildThisFileDirectory)\..\MSBuild\MacOSAppBundle.targets" />
<PropertyGroup>
<_RTMacOSAppBundle_for_webview>--webview</_RTMacOSAppBundle_for_webview>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.WebView\Robust.Client.WebView.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,24 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--
Depend on this in your client project (e.g. Content.Client) to generate a development app bundle for macOS.
This is required for WebView.
-->
<PropertyGroup>
<RTMakeAppBundle Condition="'$(TargetOS)' == 'MacOS' And '$(RTMakeAppBundle)' == '' And '$(FullRelease)' != 'True'">True</RTMakeAppBundle>
<RTAppBundleName Condition="'$(RTAppBundleName)' == ''">RobustToolbox Project</RTAppBundleName>
<RTAppBundleIdentifier Condition="'$(RTAppBundleIdentifier)' == ''">org.robusttoolbox.project</RTAppBundleIdentifier>
<!-- RTAppBundleIcon controls icon -->
</PropertyGroup>
<PropertyGroup>
<_RTMacOSAppBundle_targets_imported>True</_RTMacOSAppBundle_targets_imported>
</PropertyGroup>
<Target Name="RTMakeAppBundleAfterBuild" Condition="'$(RTMakeAppBundle)' == 'True'" AfterTargets="AfterBuild">
<PropertyGroup>
<_RTMacOSAppBundle_icon Condition="'$(RTAppBundleIcon)' != ''">--icon &quot;$(RTAppBundleIcon)&quot;</_RTMacOSAppBundle_icon>
</PropertyGroup>
<Exec Command="$(MSBuildThisFileDirectory)/../Tools/macos_make_appbundle.py $(_RTMacOSAppBundle_for_webview) --name &quot;$(RTAppBundleName)&quot; --directory &quot;$(OutputPath)&quot; --apphost &quot;$(AssemblyName)&quot; --identifier &quot;$(RTAppBundleIdentifier)&quot; $(_RTMacOSAppBundle_icon)" />
</Target>
</Project>

View File

@@ -13,7 +13,7 @@
</When>
<Otherwise>
<PropertyGroup>
<DefineConstants>$(DefineConstants);LINUX;UNIX</DefineConstants>
<DefineConstants>$(DefineConstants);LINUX;UNIX;FREEDESKTOP</DefineConstants>
</PropertyGroup>
</Otherwise>
</Choose>

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>net9.0</TargetFramework>
<LangVersion>13</LangVersion>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>14</LangVersion>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
</PropertyGroup>

View File

@@ -31,5 +31,6 @@
<Python>python3</Python>
<Python Condition="'$(ActualOS)' == 'Windows'">py -3</Python>
<UseSystemSqlite Condition="'$(TargetOS)' == 'FreeBSD'">True</UseSystemSqlite>
<IsFreedesktop Condition="'$(TargetOS)' == 'FreeBSD' Or '$(TargetOS)' == 'Linux'">True</IsFreedesktop>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,17 @@
<Project>
<!--
Disallow content from using direct references to Robust project files.
Content should use the .props files in Imports/ instead.
-->
<PropertyGroup>
<AllowDirectRobustReferences Condition="'$(AllowDirectRobustReferences)' != ''">false</AllowDirectRobustReferences>
</PropertyGroup>
<Target Name="_RTCheckForDirectReferences" BeforeTargets="BeforeResolveReferences"
Condition="'$(AllowDirectRobustReferences)' != 'true'">
<Error File="%(ProjectReference.DefiningProjectFullPath)"
Text="Direct reference to %(Filename) is not allowed. Use RobustToolbox/Imports/*.props instead (e.g., Shared.props, Client.props, Server.props)"
Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(Identity)', 'RobustToolbox')) and !$([System.Text.RegularExpressions.Regex]::IsMatch('%(DefiningProjectFullPath)', '([Mm]icrosoft|RobustToolbox)'))" />
</Target>
</Project>

View File

@@ -3,8 +3,9 @@
<!-- Import this at the end of any project files in Robust and Content. -->
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<RTCullDotnetAnalyzers Condition="'$(RTCullDotnetAnalyzers)' == ''">true</RTCullDotnetAnalyzers>
</PropertyGroup>
<Import Project="Robust.Custom.targets" Condition="Exists('Robust.Custom.targets')"/>
@@ -28,5 +29,19 @@
<Import Project="Robust.Analyzers.targets" Condition="'$(SkipRobustAnalyzer)' != 'true'" />
<!-- serialization generator -->
<Import Project="Robust.Serialization.Generator.targets" Condition="'$(SkipRobustAnalyzer)' != 'true' And '$(SkipRobustSerializationGenerator)' != 'true'" />
<Import Project="Robust.Serialization.Generator.targets" Condition="'$(SkipRobustAnalyzer)' != 'true'" />
<!-- Robust API system -->
<Import Project="Robust.ProjectReferences.targets" />
<!--
We don't use these features and they add a not-insignificant amount of time to build perf.
Am I micro-optimizing? Maybe.
-->
<Target Name="_RTRemoveSlowAnalyzers" BeforeTargets="AfterResolveReferences" Returns="@(Analyzer)">
<ItemGroup Condition="'$(RTCullDotnetAnalyzers)' == 'true'">
<Analyzer Remove="@(Analyzer)" Condition="'%(Analyzer.AssemblyName)' == 'Microsoft.Interop.ComInterfaceGenerator'" />
<Analyzer Remove="@(Analyzer)" Condition="'%(Analyzer.AssemblyName)' == 'Microsoft.Interop.JavaScript.JSImportGenerator'" />
</ItemGroup>
</Target>
</Project>

File diff suppressed because it is too large Load Diff

View File

@@ -1005,6 +1005,22 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- name: GLFW
license: |
Copyright © 2002-2006 Marcus Geelnard
Copyright © 2006-2019 Camilla Löwy
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:
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.
Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
This notice may not be removed or altered from any source distribution.
- name: FluidSynth
license: |
GNU LESSER GENERAL PUBLIC LICENSE

View File

@@ -2,7 +2,8 @@
id: Audio
name: Audio
description: Audio entity used by engine
save: false
save: false # TODO PERSISTENCE what about looping or long sounds?
categories: [ HideSpawnMenu ]
components:
- type: Transform
gridTraversal: false

View File

@@ -1,27 +0,0 @@
import Robust::SpriteBatch::{VertexInput, VertexOutput, mainTexture, mainSampler, View};
import Robust::Math::srgb_to_linear;
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var transformed = vec3(input.position, 1.0) * View.projViewMatrix;
transformed += 1.0;
transformed /= View.screenPixelSize * 2.0;
transformed = floor(transformed + 0.5);
transformed *= View.screenPixelSize * 2.0;
transformed -= 1.0;
var out: VertexOutput;
out.position = vec4(transformed, 0.0, 1.0);
out.texCoord = input.texCoord;
out.color = srgb_to_linear(input.color);
return out;
}
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4f {
var color = textureSample(mainTexture, mainSampler, input.texCoord);
color = color * input.color;
return color;
}

View File

@@ -1,6 +0,0 @@
fn srgb_to_linear(srgb: vec4f) -> vec4f {
let higher = pow((srgb.rgb + 0.055) / 1.055, vec3(2.4));
let lower = srgb.rgb / 12.92;
let s = max(vec3(0.0), sign(srgb.rgb - 0.04045));
return vec4(mix(lower, higher, s), srgb.a);
}

View File

@@ -1,33 +0,0 @@
// Group 0: global constants.
struct UniformConstants {
time: f32
}
@group(0) @binding(0) var<uniform> Constants: UniformConstants;
// Group 1: parameters that change infrequently in a draw pass.
struct UniformView {
projViewMatrix: mat2x3f,
screenPixelSize: vec2f
}
@group(1) @binding(0) var<uniform> View: UniformView;
// Group 2: per-draw parameters.
@group(2) @binding(0)
var mainTexture: texture_2d<f32>;
@group(2) @binding(1)
var mainSampler: sampler;
struct VertexInput {
@location(0) position: vec2f,
@location(1) texCoord: vec2f,
@location(2) color: vec4f
}
struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) texCoord: vec2f,
@location(1) color: vec4f,
}

View File

@@ -1 +0,0 @@

View File

@@ -1,16 +1,16 @@
# Loc strings for various entity state & client-side PVS related commands
cmd-reset-ent-help = Usage: resetent <Entity UID>
cmd-reset-ent-desc = Reset an entity to the most recently received server state. This will also reset entities that have been detached to null-space.
cmd-reset-ent-help = Usage: {$command} <Entity UID>
cmd-reset-ent-desc = Reset an entity to the most recently received server state. This will also reset entities that have been detached to null-space.
cmd-reset-all-ents-help = Usage: resetallents
cmd-reset-all-ents-desc = Resets all entities to the most recently received server state. This only impacts entities that have not been detached to null-space.
cmd-reset-all-ents-help = Usage: {$command}
cmd-reset-all-ents-desc = Resets all entities to the most recently received server state. This only impacts entities that have not been detached to null-space.
cmd-detach-ent-help = Usage: detachent <Entity UID>
cmd-detach-ent-help = Usage: {$command} <Entity UID>
cmd-detach-ent-desc = Detach an entity to null-space, as if it had left PVS range.
cmd-local-delete-help = Usage: localdelete <Entity UID>
cmd-local-delete-help = Usage: {$command} <Entity UID>
cmd-local-delete-desc = Deletes an entity. Unlike the normal delete command, this is CLIENT-SIDE. Unless the entity is a client-side entity, this will likely cause errors.
cmd-full-state-reset-help = Usage: fullstatereset
cmd-full-state-reset-help = Usage: {$command}
cmd-full-state-reset-desc = Discards any entity state information and requests a full-state from the server.

View File

@@ -23,8 +23,8 @@ cmd-error-dir-not-found = Could not find directory: {$dir}.
cmd-failure-no-attached-entity = There is no entity attached to this shell.
## 'help' command
cmd-help-desc = Display general help or help text for a specific command
cmd-help-help = Usage: help [command name]
cmd-help-desc = Display general help or help text for a specific command.
cmd-help-help = Usage: {$command} [command name]
When no command name is provided, displays general-purpose help text. If a command name is provided, displays help text for that command.
cmd-help-no-args = To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'. To search for commands, use 'list <filter>'.
@@ -35,7 +35,7 @@ cmd-help-arg-cmdname = [command name]
## 'cvar' command
cmd-cvar-desc = Gets or sets a CVar.
cmd-cvar-help = Usage: cvar <name | ?> [value]
cmd-cvar-help = Usage: {$command} <name | ?> [value]
If a value is passed, the value is parsed and stored as the new value of the CVar.
If not, the current value of the CVar is displayed.
Use 'cvar ?' to get a list of all registered CVars.
@@ -49,14 +49,14 @@ cmd-cvar-value-hidden = <value hidden>
## 'cvar_subs' command
cmd-cvar_subs-desc = Lists the OnValueChanged subscriptions for a CVar.
cmd-cvar_subs-help = Usage: cvar_subs <name>
cmd-cvar_subs-help = Usage: {$command} <name>
cmd-cvar_subs-invalid-args = Must provide exactly one argument.
cmd-cvar_subs-arg-name = <name>
## 'list' command
cmd-list-desc = Lists available commands, with optional search filter
cmd-list-help = Usage: list [filter]
cmd-list-desc = Lists available commands, with optional search filter.
cmd-list-help = Usage: {$command} [filter]
Lists all available commands. If an argument is provided, it will be used to filter commands by name.
cmd-list-heading = SIDE NAME DESC{"\u000A"}-------------------------{"\u000A"}
@@ -64,13 +64,13 @@ cmd-list-heading = SIDE NAME DESC{"\u000A"}-------------------------{
cmd-list-arg-filter = [filter]
## '>' command, aka remote exec
cmd-remoteexec-desc = Executes server-side commands
cmd-remoteexec-desc = Executes server-side commands.
cmd-remoteexec-help = Usage: > <command> [arg] [arg] [arg...]
Executes a command on the server. This is necessary if a command with the same name exists on the client, as simply running the command would run the client command first.
## 'gc' command
cmd-gc-desc = Run the GC (Garbage Collector)
cmd-gc-help = Usage: gc [generation]
cmd-gc-desc = Run the GC (Garbage Collector).
cmd-gc-help = Usage: {$command} [generation]
Uses GC.Collect() to execute the Garbage Collector.
If an argument is provided, it is parsed as a GC generation number and GC.Collect(int) is used.
Use the 'gfc' command to do an LOH-compacting full GC.
@@ -79,13 +79,13 @@ cmd-gc-arg-generation = [generation]
## 'gcf' command
cmd-gcf-desc = Run the GC, fully, compacting LOH and everything.
cmd-gcf-help = Usage: gcf
cmd-gcf-help = Usage: {$command}
Does a full GC.Collect(2, GCCollectionMode.Forced, true, true) while also compacting LOH.
This will probably lock up for hundreds of milliseconds, be warned.
## 'gc_mode' command
cmd-gc_mode-desc = Change/Read the GC Latency mode
cmd-gc_mode-help = Usage: gc_mode [type]
cmd-gc_mode-desc = Change/Read the GC Latency mode.
cmd-gc_mode-help = Usage: {$command} [type]
If no argument is provided, returns the current GC latency mode.
If an argument is passed, it is parsed as GCLatencyMode and set as the GC latency mode.
@@ -98,8 +98,8 @@ cmd-gc_mode-result = resulting gc latency mode: { $mode }
cmd-gc_mode-arg-type = [type]
## 'mem' command
cmd-mem-desc = Prints managed memory info
cmd-mem-help = Usage: mem
cmd-mem-desc = Prints managed memory info.
cmd-mem-help = Usage: {$command}
cmd-mem-report = Heap Size: { TOSTRING($heapSize, "N0") }
Total Allocated: { TOSTRING($totalAllocated, "N0") }
@@ -108,26 +108,26 @@ cmd-mem-report = Heap Size: { TOSTRING($heapSize, "N0") }
cmd-physics-overlay = {$overlay} is not a recognised overlay
## 'lsasm' command
cmd-lsasm-desc = Lists loaded assemblies by load context
cmd-lsasm-desc = Lists loaded assemblies by load context.
cmd-lsasm-help = Usage: lsasm
## 'exec' command
cmd-exec-desc = Executes a script file from the game's writeable user data
cmd-exec-help = Usage: exec <fileName>
cmd-exec-desc = Executes a script file from the game's writeable user data.
cmd-exec-help = Usage: {$command} <fileName>
Each line in the file is executed as a single command, unless it starts with a #
cmd-exec-arg-filename = <fileName>
## 'dump_net_comps' command
cmd-dump_net_comps-desc = Prints the table of networked components.
cmd-dump_net_comps-help = Usage: dump_net-comps
cmd-dump_net_comps-help = Usage: {$command}
cmd-dump_net_comps-error-writeable = Registration still writeable, network ids have not been generated.
cmd-dump_net_comps-header = Networked Component Registrations:
## 'dump_event_tables' command
cmd-dump_event_tables-desc = Prints directed event tables for an entity.
cmd-dump_event_tables-help = Usage: dump_event_tables <entityUid>
cmd-dump_event_tables-help = Usage: {$command} <entityUid>
cmd-dump_event_tables-missing-arg-entity = Missing entity argument
cmd-dump_event_tables-error-entity = Invalid entity
@@ -135,7 +135,7 @@ cmd-dump_event_tables-arg-entity = <entityUid>
## 'monitor' command
cmd-monitor-desc = Toggles a debug monitor in the F3 menu.
cmd-monitor-help = Usage: monitor <name>
cmd-monitor-help = Usage: {$command} <name>
Possible monitors are: { $monitors }
You can also use the special values "-all" and "+all" to hide or show all monitors, respectively.
@@ -148,13 +148,13 @@ cmd-monitor-plus-all-hint = Shows all monitors
## 'setambientlight' command
cmd-set-ambient-light-desc = Allows you to set the ambient light for the specified map, in SRGB.
cmd-set-ambient-light-help = setambientlight [mapid] [r g b a]
cmd-set-ambient-light-help = Usage: {$command} [mapid] [r g b a]
cmd-set-ambient-light-parse = Unable to parse args as a byte values for a color.
## Mapping commands
cmd-savemap-desc = Serializes a map to disk. Will not save a post-init map unless forced.
cmd-savemap-help = savemap <MapID> <Path> [force]
cmd-savemap-help = Usage: {$command} <MapID> <Path> [force]
cmd-savemap-not-exist = Target map does not exist.
cmd-savemap-init-warning = Attempted to save a post-init map without forcing the save.
cmd-savemap-attempt = Attempting to save map {$mapId} to {$path}.
@@ -165,7 +165,7 @@ cmd-hint-savemap-path = <Path>
cmd-hint-savemap-force = [bool]
cmd-loadmap-desc = Loads a map from disk into the game.
cmd-loadmap-help = loadmap <MapID> <Path> [x] [y] [rotation] [consistentUids]
cmd-loadmap-help = Usage: {$command} <MapID> <Path> [x] [y] [rotation] [consistentUids]
cmd-loadmap-nullspace = You cannot load into map 0.
cmd-loadmap-exists = Map {$mapId} already exists.
cmd-loadmap-success = Map {$mapId} has been loaded from {$path}.
@@ -180,73 +180,74 @@ cmd-hint-savebp-id = <Grid EntityID>
## 'flushcookies' command
# Note: the flushcookies command is from Robust.Client.WebView, it's not in the main engine code.
cmd-flushcookies-desc = Flush CEF cookie storage to disk
cmd-flushcookies-help = This ensure cookies are properly saved to disk in the event of unclean shutdowns.
cmd-flushcookies-desc = Flush CEF cookie storage to disk.
cmd-flushcookies-help = Usage: {$command}
This ensure cookies are properly saved to disk in the event of unclean shutdowns.
Note that the actual operation is asynchronous.
cmd-ldrsc-desc = Pre-caches a resource.
cmd-ldrsc-help = Usage: ldrsc <path> <type>
cmd-ldrsc-help = Usage: {$command} <path> <type>
cmd-rldrsc-desc = Reloads a resource.
cmd-rldrsc-help = Usage: rldrsc <path> <type>
cmd-rldrsc-help = Usage: {$command} <path> <type>
cmd-gridtc-desc = Gets the tile count of a grid.
cmd-gridtc-help = Usage: gridtc <gridId>
cmd-gridtc-help = Usage: {$command} <gridId>
# Client-side commands
cmd-guidump-desc = Dump GUI tree to /guidump.txt in user data.
cmd-guidump-help = Usage: guidump
cmd-guidump-help = Usage: {$command}
cmd-uitest-desc = Open a dummy UI testing window
cmd-uitest-help = Usage: uitest
cmd-uitest-desc = Open a dummy UI testing window.
cmd-uitest-help = Usage: {$command}
## 'uitest2' command
cmd-uitest2-desc = Opens a UI control testing OS window
cmd-uitest2-help = Usage: uitest2 <tab>
cmd-uitest2-desc = Opens a UI control testing OS window.
cmd-uitest2-help = Usage: {$command} <tab>
cmd-uitest2-arg-tab = <tab>
cmd-uitest2-error-args = Expected at most one argument
cmd-uitest2-error-tab = Invalid tab: '{$value}'
cmd-uitest2-title = UITest2
cmd-setclipboard-desc = Sets the system clipboard
cmd-setclipboard-help = Usage: setclipboard <text>
cmd-setclipboard-desc = Sets the system clipboard.
cmd-setclipboard-help = Usage: {$command} <text>
cmd-getclipboard-desc = Gets the system clipboard
cmd-getclipboard-help = Usage: Getclipboard
cmd-getclipboard-desc = Gets the system clipboard.
cmd-getclipboard-help = Usage: {$command}
cmd-togglelight-desc = Toggles light rendering.
cmd-togglelight-help = Usage: togglelight
cmd-togglelight-help = Usage: {$command}
cmd-togglefov-desc = Toggles fov for client.
cmd-togglefov-help = Usage: togglefov
cmd-togglefov-help = Usage: {$command}
cmd-togglehardfov-desc = Toggles hard fov for client. (for debugging space-station-14#2353)
cmd-togglehardfov-help = Usage: togglehardfov
cmd-togglehardfov-help = Usage: {$command}
cmd-toggleshadows-desc = Toggles shadow rendering.
cmd-toggleshadows-help = Usage: toggleshadows
cmd-toggleshadows-help = Usage: {$command}
cmd-togglelightbuf-desc = Toggles lighting rendering. This includes shadows but not FOV.
cmd-togglelightbuf-help = Usage: togglelightbuf
cmd-togglelightbuf-help = Usage: {$command}
cmd-chunkinfo-desc = Gets info about a chunk under your mouse cursor.
cmd-chunkinfo-help = Usage: chunkinfo
cmd-chunkinfo-help = Usage: {$command}
cmd-rldshader-desc = Reloads all shaders.
cmd-rldshader-help = Usage: rldshader
cmd-rldshader-help = Usage: {$command}
cmd-cldbglyr-desc = Toggle fov and light debug layers.
cmd-cldbglyr-help= Usage: cldbglyr <layer>: Toggle <layer>
cmd-cldbglyr-help= Usage: {$command} <layer>: Toggle <layer>
cldbglyr: Turn all Layers off
cmd-key-info-desc = Keys key info for a key.
cmd-key-info-help = Usage: keyinfo <Key>
cmd-key-info-help = Usage: {$command} <Key>
## 'bind' command
cmd-bind-desc = Binds an input key combination to an input command.
cmd-bind-help = Usage: bind { cmd-bind-arg-key } { cmd-bind-arg-mode } { cmd-bind-arg-command }
cmd-bind-help = Usage: {$command} { cmd-bind-arg-key } { cmd-bind-arg-mode } { cmd-bind-arg-command }
Note that this DOES NOT automatically save bindings.
Use the 'svbind' command to save binding configuration.
@@ -255,316 +256,322 @@ cmd-bind-arg-mode = <BindMode>
cmd-bind-arg-command = <InputCommand>
cmd-net-draw-interp-desc = Toggles the debug drawing of the network interpolation.
cmd-net-draw-interp-help = Usage: net_draw_interp
cmd-net-draw-interp-help = Usage: {$command}
cmd-net-watch-ent-desc = Dumps all network updates for an EntityId to the console.
cmd-net-watch-ent-help = Usage: net_watchent <0|EntityUid>
cmd-net-watch-ent-help = Usage: {$command} <0|EntityUid>
cmd-net-refresh-desc = Requests a full server state.
cmd-net-refresh-help = Usage: net_refresh
cmd-net-refresh-help = Usage: {$command}
cmd-net-entity-report-desc = Toggles the net entity report panel.
cmd-net-entity-report-help = Usage: net_entityreport
cmd-net-entity-report-help = Usage: {$command}
cmd-fill-desc = Fill up the console for debugging.
cmd-fill-help = Fills the console with some nonsense for debugging.
cmd-fill-help = Usage: {$command}
Fills the console with some nonsense for debugging.
cmd-cls-desc = Clears the console.
cmd-cls-help = Clears the debug console of all messages.
cmd-cls-help = Usage: {$command}
Clears the debug console of all messages.
cmd-sendgarbage-desc = Sends garbage to the server.
cmd-sendgarbage-help = The server will reply with 'no u'
cmd-sendgarbage-help = Usage: {$command}
The server will reply with 'no u'
cmd-loadgrid-desc = Loads a grid from a file into an existing map.
cmd-loadgrid-help = loadgrid <MapID> <Path> [x y] [rotation] [storeUids]
cmd-loadgrid-help = Usage: {$command} <MapID> <Path> [x y] [rotation] [storeUids]
cmd-loc-desc = Prints the absolute location of the player's entity to console.
cmd-loc-help = loc
cmd-loc-help = Usage: {$command}
cmd-tpgrid-desc = Teleports a grid to a new location.
cmd-tpgrid-help = tpgrid <gridId> <X> <Y> [<MapId>]
cmd-tpgrid-help = Usage: {$command} <gridId> <X> <Y> [<MapId>]
cmd-rmgrid-desc = Removes a grid from a map. You cannot remove the default grid.
cmd-rmgrid-help = rmgrid <gridId>
cmd-rmgrid-help = Usage: {$command} <gridId>
cmd-mapinit-desc = Runs map init on a map.
cmd-mapinit-help = mapinit <mapID>
cmd-mapinit-help = Usage: {$command} <mapID>
cmd-lsmap-desc = Lists maps.
cmd-lsmap-help = lsmap
cmd-lsmap-help = Usage: {$command}
cmd-lsgrid-desc = Lists grids.
cmd-lsgrid-help = lsgrid
cmd-lsgrid-help = Usage: {$command}
cmd-addmap-desc = Adds a new empty map to the round. If the mapID already exists, this command does nothing.
cmd-addmap-help = addmap <mapID> [pre-init]
cmd-addmap-help = Usage: {$command} <mapID> [pre-init]
cmd-rmmap-desc = Removes a map from the world. You cannot remove nullspace.
cmd-rmmap-help = rmmap <mapId>
cmd-rmmap-help = Usage: {$command} <mapId>
cmd-savegrid-desc = Serializes a grid to disk.
cmd-savegrid-help = savegrid <gridID> <Path>
cmd-savegrid-help = Usage: {$command} <gridID> <Path>
cmd-testbed-desc = Loads a physics testbed on the specified map.
cmd-testbed-help = testbed <mapid> <test>
cmd-testbed-help = Usage: {$command} <mapid> <test>
## 'flushcookies' command
# Note: the flushcookies command is from Robust.Client.WebView, it's not in the main engine code.
## 'addcomp' command
cmd-addcomp-desc = Adds a component to an entity.
cmd-addcomp-help = addcomp <uid> <componentName>
cmd-addcomp-help = Usage: {$command} <uid> <componentName>
cmd-addcompc-desc = Adds a component to an entity on the client.
cmd-addcompc-help = addcompc <uid> <componentName>
cmd-addcompc-help = Usage: {$command} <uid> <componentName>
## 'rmcomp' command
cmd-rmcomp-desc = Removes a component from an entity.
cmd-rmcomp-help = rmcomp <uid> <componentName>
cmd-rmcomp-help = Usage: {$command} <uid> <componentName>
cmd-rmcompc-desc = Removes a component from an entity on the client.
cmd-rmcompc-help = rmcomp <uid> <componentName>
cmd-rmcompc-help = Usage: {$command} <uid> <componentName>
## 'addview' command
cmd-addview-desc = Allows you to subscribe to an entity's view for debugging purposes.
cmd-addview-help = addview <entityUid>
cmd-addview-help = Usage: {$command} <entityUid>
cmd-addviewc-desc = Allows you to subscribe to an entity's view for debugging purposes.
cmd-addviewc-help = addview <entityUid>
cmd-addviewc-help = Usage: {$command} <entityUid>
## 'removeview' command
cmd-removeview-desc = Allows you to unsubscribe to an entity's view for debugging purposes.
cmd-removeview-help = removeview <entityUid>
cmd-removeview-help = Usage: {$command} <entityUid>
## 'loglevel' command
cmd-loglevel-desc = Changes the log level for a provided sawmill.
cmd-loglevel-help = Usage: loglevel <sawmill> <level>
cmd-loglevel-help = Usage: {$command} <sawmill> <level>
sawmill: A label prefixing log messages. This is the one you're setting the level for.
level: The log level. Must match one of the values of the LogLevel enum.
cmd-testlog-desc = Writes a test log to a sawmill.
cmd-testlog-help = Usage: testlog <sawmill> <level> <message>
cmd-testlog-help = Usage: {$command} <sawmill> <level> <message>
sawmill: A label prefixing the logged message.
level: The log level. Must match one of the values of the LogLevel enum.
message: The message to be logged. Wrap this in double quotes if you want to use spaces.
## 'vv' command
cmd-vv-desc = Opens View Variables.
cmd-vv-help = Usage: vv <entity ID|IoC interface name|SIoC interface name>
cmd-vv-help = Usage: {$command} <entity ID|IoC interface name|SIoC interface name>
## 'showvelocities' command
cmd-showvelocities-desc = Displays your angular and linear velocities.
cmd-showvelocities-help = Usage: showvelocities
cmd-showvelocities-help = Usage: {$command}
## 'setinputcontext' command
cmd-setinputcontext-desc = Sets the active input context.
cmd-setinputcontext-help = Usage: setinputcontext <context>
cmd-setinputcontext-help = Usage: {$command} <context>
## 'forall' command
cmd-forall-desc = Runs a command over all entities with a given component.
cmd-forall-help = Usage: forall <bql query> do <command...>
cmd-forall-help = Usage: {$command} <bql query> do <command...>
## 'delete' command
cmd-delete-desc = Deletes the entity with the specified ID.
cmd-delete-help = delete <entity UID>
cmd-delete-help = Usage: {$command} <entity UID>
# System commands
cmd-showtime-desc = Shows the server time.
cmd-showtime-help = showtime
cmd-showtime-help = Usage: {$command}
cmd-restart-desc = Gracefully restarts the server (not just the round).
cmd-restart-help = restart
cmd-restart-help = Usage: {$command}
cmd-shutdown-desc = Gracefully shuts down the server.
cmd-shutdown-help = shutdown
cmd-shutdown-help = Usage: {$command}
cmd-saveconfig-desc = Saves the server configuration to the config file.
cmd-saveconfig-help = saveconfig
cmd-saveconfig-help = Usage: {$command}
cmd-netaudit-desc = Prints into about NetMsg security.
cmd-netaudit-help = netaudit
cmd-netaudit-help = Usage: {$command}
# Player commands
cmd-tp-desc = Teleports a player to any location in the round.
cmd-tp-help = tp <x> <y> [<mapID>]
cmd-tp-help = Usage: {$command} <x> <y> [<mapID>]
cmd-tpto-desc = Teleports the current player or the specified players/entities to the location of the first player/entity.
cmd-tpto-help = tpto <username|uid> [username|NetEntity]...
cmd-tpto-help = Usage: {$command} <username|uid> [username|NetEntity]...
cmd-tpto-destination-hint = destination (NetEntity or username)
cmd-tpto-victim-hint = entity to teleport (NetEntity or username)
cmd-tpto-parse-error = Cant resolve entity or player: {$str}
cmd-listplayers-desc = Lists all players currently connected.
cmd-listplayers-help = listplayers
cmd-listplayers-help = Usage: {$command}
cmd-kick-desc = Kicks a connected player out of the server, disconnecting them.
cmd-kick-help = kick <PlayerIndex> [<Reason>]
cmd-kick-help = Usage: {$command} <PlayerIndex> [<Reason>]
# Spin command
cmd-spin-desc = Causes an entity to spin. Default entity is the attached player's parent.
cmd-spin-help = spin velocity [drag] [entityUid]
cmd-spin-help = Usage: {$command} velocity [drag] [entityUid]
# Localization command
cmd-rldloc-desc = Reloads localization (client & server).
cmd-rldloc-help = Usage: rldloc
cmd-rldloc-help = Usage: {$command}
# Debug entity controls
cmd-spawn-desc = Spawns an entity with specific type.
cmd-spawn-help = spawn <prototype> OR spawn <prototype> <relative entity ID> OR spawn <prototype> <x> <y>
cmd-spawn-help = Usage: {$command} <prototype> | {$command} <prototype> <relative entity ID> | {$command} <prototype> <x> <y>
cmd-cspawn-desc = Spawns a client-side entity with specific type at your feet.
cmd-cspawn-help = cspawn <entity type>
cmd-cspawn-help = Usage: {$command} <entity type>
cmd-dumpentities-desc = Dump entity list.
cmd-dumpentities-help = Dumps entity list of UIDs and prototype.
cmd-dumpentities-help = Usage: {$command}
Dumps entity list of UIDs and prototype.
cmd-getcomponentregistration-desc = Gets component registration information.
cmd-getcomponentregistration-help = Usage: getcomponentregistration <componentName>
cmd-getcomponentregistration-help = Usage: {$command} <componentName>
cmd-showrays-desc = Toggles debug drawing of physics rays. An integer for <raylifetime> must be provided.
cmd-showrays-help = Usage: showrays <raylifetime>
cmd-showrays-help = Usage: {$command} <raylifetime>
cmd-disconnect-desc = Immediately disconnect from the server and go back to the main menu.
cmd-disconnect-help = Usage: disconnect
cmd-disconnect-help = Usage: {$command}
cmd-entfo-desc = Displays verbose diagnostics for an entity.
cmd-entfo-help = Usage: entfo <entityuid>
cmd-entfo-help = Usage: {$command} <entityuid>
The entity UID can be prefixed with 'c' to convert it to a client entity UID.
cmd-fuck-desc = Throws an exception
cmd-fuck-help = Usage: fuck
cmd-fuck-desc = Throws an exception.
cmd-fuck-help = Usage: {$command}
cmd-showpos-desc = Show the position of all entities on the screen.
cmd-showpos-help = Usage: showpos
cmd-showpos-help = Usage: {$command}
cmd-showrot-desc = Show the rotation of all entities on the screen.
cmd-showrot-help = Usage: showrot
cmd-showrot-help = Usage: {$command}
cmd-showvel-desc = Show the local velocity of all entites on the screen.
cmd-showvel-help = Usage: showvel
cmd-showvel-help = Usage: {$command}
cmd-showangvel-desc = Show the angular velocity of all entities on the screen.
cmd-showangvel-help = Usage: showangvel
cmd-showangvel-help = Usage: {$command}
cmd-sggcell-desc = Lists entities on a snap grid cell.
cmd-sggcell-help = Usage: sggcell <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.
cmd-sggcell-help = Usage: {$command} <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.
cmd-overrideplayername-desc = Changes the name used when attempting to connect to the server.
cmd-overrideplayername-help = Usage: overrideplayername <name>
cmd-overrideplayername-help = Usage: {$command} <name>
cmd-showanchored-desc = Shows anchored entities on a particular tile
cmd-showanchored-help = Usage: showanchored
cmd-showanchored-desc = Shows anchored entities on a particular tile.
cmd-showanchored-help = Usage: {$command}
cmd-dmetamem-desc = Dumps a type's members in a format suitable for the sandbox configuration file.
cmd-dmetamem-help = Usage: dmetamem <type>
cmd-dmetamem-help = Usage: {$command} <type>
cmd-launchauth-desc = Load authentication tokens from launcher data to aid in testing of live servers.
cmd-launchauth-help = Usage: launchauth <account name>
cmd-launchauth-help = Usage: {$command} <account name>
cmd-lightbb-desc = Toggles whether to show light bounding boxes.
cmd-lightbb-help = Usage: lightbb
cmd-lightbb-help = Usage: {$command}
cmd-monitorinfo-desc = Monitors info
cmd-monitorinfo-help = Usage: monitorinfo <id>
cmd-monitorinfo-desc = Monitors info.
cmd-monitorinfo-help = Usage: {$command} <id>
cmd-setmonitor-desc = Set monitor
cmd-setmonitor-help = Usage: setmonitor <id>
cmd-setmonitor-desc = Set monitor.
cmd-setmonitor-help = Usage: {$command} <id>
cmd-physics-desc = Shows a debug physics overlay. The arg supplied specifies the overlay.
cmd-physics-help = Usage: physics <aabbs / com / contactnormals / contactpoints / distance / joints / shapeinfo / shapes>
cmd-physics-help = Usage: {$command} <aabbs / com / contactnormals / contactpoints / distance / joints / shapeinfo / shapes>
cmd-hardquit-desc = Kills the game client instantly.
cmd-hardquit-help = Kills the game client instantly, leaving no traces. No telling the server goodbye.
cmd-hardquit-help = Usage: {$command}
Kills the game client instantly, leaving no traces. No telling the server goodbye.
cmd-quit-desc = Shuts down the game client gracefully.
cmd-quit-help = Properly shuts down the game client, notifying the connected server and such.
cmd-quit-help = Usage: {$command}
Properly shuts down the game client, notifying the connected server and such.
cmd-csi-desc = Opens a C# interactive console.
cmd-csi-help = Usage: csi
cmd-csi-help = Usage: {$command}
cmd-scsi-desc = Opens a C# interactive console on the server.
cmd-scsi-help = Usage: scsi
cmd-scsi-help = Usage: {$command}
cmd-watch-desc = Opens a variable watch window.
cmd-watch-help = Usage: watch
cmd-watch-help = Usage: {$command}
cmd-showspritebb-desc = Toggle whether sprite bounds are shown
cmd-showspritebb-help = Usage: showspritebb
cmd-showspritebb-desc = Toggle whether sprite bounds are shown.
cmd-showspritebb-help = Usage: {$command}
cmd-togglelookup-desc = Shows / hides entitylookup bounds via an overlay.
cmd-togglelookup-help = Usage: togglelookup
cmd-togglelookup-help = Usage: {$command}
cmd-net_entityreport-desc = Toggles the net entity report panel.
cmd-net_entityreport-help = Usage: net_entityreport
cmd-net_entityreport-help = Usage: {$command}
cmd-net_refresh-desc = Requests a full server state.
cmd-net_refresh-help = Usage: net_refresh
cmd-net_refresh-help = Usage: {$command}
cmd-net_graph-desc = Toggles the net statistics panel.
cmd-net_graph-help = Usage: net_graph
cmd-net_graph-help = Usage: {$command}
cmd-net_watchent-desc = Dumps all network updates for an EntityId to the console.
cmd-net_watchent-help = Usage: net_watchent <0|EntityUid>
cmd-net_watchent-help = Usage: {$command} <0|EntityUid>
cmd-net_draw_interp-desc = Toggles the debug drawing of the network interpolation.
cmd-net_draw_interp-help = Usage: net_draw_interp <0|EntityUid>
cmd-net_draw_interp-help = Usage: {$command} <0|EntityUid>
cmd-vram-desc = Displays video memory usage statics by the game.
cmd-vram-help = Usage: vram
cmd-vram-help = Usage: {$command}
cmd-showislands-desc = Shows the current physics bodies involved in each physics island.
cmd-showislands-help = Usage: showislands
cmd-showislands-help = Usage: {$command}
cmd-showgridnodes-desc = Shows the nodes for grid split purposes.
cmd-showgridnodes-help = Usage: showgridnodes
cmd-showgridnodes-help = Usage: {$command}
cmd-profsnap-desc = Make a profiling snapshot.
cmd-profsnap-help = Usage: profsnap
cmd-profsnap-help = Usage: {$command}
cmd-devwindow-desc = Dev Window
cmd-devwindow-help = Usage: devwindow
cmd-devwindow-desc = Dev Window.
cmd-devwindow-help = Usage: {$command}
cmd-scene-desc = Immediately changes the UI scene/state.
cmd-scene-help = Usage: scene <className>
cmd-scene-help = Usage: {$command} <className>
cmd-szr_stats-desc = Report serializer statistics.
cmd-szr_stats-help = Usage: szr_stats
cmd-szr_stats-help = Usage: {$command}
cmd-hwid-desc = Returns the current HWID (HardWare ID).
cmd-hwid-help = Usage: hwid
cmd-hwid-help = Usage: {$command}
cmd-vvread-desc = Retrieve a path's value using VV (View Variables).
cmd-vvread-help = Usage: vvread <path>
cmd-vvread-help = Usage: {$command} <path>
cmd-vvwrite-desc = Modify a path's value using VV (View Variables).
cmd-vvwrite-help = Usage: vvwrite <path>
cmd-vvwrite-help = Usage: {$command} <path>
cmd-vvinvoke-desc = Invoke/Call a path with arguments using VV.
cmd-vvinvoke-help = Usage: vvinvoke <path> [arguments...]
cmd-vvinvoke-help = Usage: {$command} <path> [arguments...]
cmd-dump_dependency_injectors-desc = Dump IoCManager's dependency injector cache.
cmd-dump_dependency_injectors-help = Usage: dump_dependency_injectors
cmd-dump_dependency_injectors-help = Usage: {$command}
cmd-dump_dependency_injectors-total-count = Total count: { $total }
cmd-dump_netserializer_type_map-desc = Dump NetSerializer's type map and serializer hash.
cmd-dump_netserializer_type_map-help = Usage: dump_netserializer_type_map
cmd-dump_netserializer_type_map-help = Usage: {$command}
cmd-hub_advertise_now-desc = Immediately advertise to the master hub server
cmd-hub_advertise_now-help = Usage: hub_advertise_now
cmd-hub_advertise_now-desc = Immediately advertise to the master hub server.
cmd-hub_advertise_now-help = Usage: {$command}
cmd-echo-desc = Echo arguments back to the console
cmd-echo-help = Usage: echo "<message>"
cmd-echo-desc = Echo arguments back to the console.
cmd-echo-help = Usage: {$command} "<message>"
## 'vfs_ls' command
cmd-vfs_ls-desc = List directory contents in the VFS.
cmd-vfs_ls-help = Usage: vfs_list <path>
cmd-vfs_ls-help = Usage: {$command} <path>
Example:
vfs_list /Assemblies
cmd-vfs_ls-err-args = Need exactly 1 argument.
cmd-vfs_ls-hint-path = <path>
cmd-reloadtiletextures-desc = Reloads the tile texture atlas to allow hot reloading tile sprites
cmd-reloadtiletextures-help = Usage: reloadtiletextures
cmd-reloadtiletextures-desc = Reloads the tile texture atlas to allow hot reloading tile sprites.
cmd-reloadtiletextures-help = Usage: {$command}
cmd-audio_length-desc = Shows the length of an audio file
cmd-audio_length-help = Usage: audio_length { cmd-audio_length-arg-file-name }
cmd-audio_length-help = Usage: {$command} { cmd-audio_length-arg-file-name }
cmd-audio_length-arg-file-name = <file name>
## PVS
@@ -573,8 +580,8 @@ cmd-pvs-override-info-empty = Entity {$nuid} has no PVS overrides.
cmd-pvs-override-info-global = Entity {$nuid} has a global override.
cmd-pvs-override-info-clients = Entity {$nuid} has a session override for {$clients}.
cmd-localization_set_culture-desc = Set DefaultCulture for the client LocalizationManager
cmd-localization_set_culture-help = Usage: localization_set_culture <cultureName>
cmd-localization_set_culture-desc = Set DefaultCulture for the client LocalizationManager.
cmd-localization_set_culture-help = Usage: {$command} <cultureName>
cmd-localization_set_culture-culture-name = <cultureName>
cmd-localization_set_culture-changed = Localization changed to { $code } ({ $nativeName } / { $englishName })

View File

@@ -8,3 +8,5 @@ color-selector-sliders-alpha = A
color-selector-sliders-rgb = RGB
color-selector-sliders-hsv = HSV
option-button-filter = Filter

View File

@@ -68,6 +68,7 @@ input-key-MouseButton6 = Mouse 6
input-key-MouseButton7 = Mouse 7
input-key-MouseButton8 = Mouse 8
input-key-MouseButton9 = Mouse 9
input-key-CapsLock = Caps Lock
input-key-LSystem-win = Left Win
input-key-RSystem-win = Right Win

View File

@@ -9,6 +9,9 @@ cmd-replay-pause-help = replay_pause
cmd-replay-toggle-desc = Resume or pause replay playback.
cmd-replay-toggle-help = replay_toggle
cmd-replay-toggle-screenshot-mode-desc = Toggles screenshot mode for replays, hiding the replay control widget.
cmd-replay-toggle-screenshot-mode-help = replay_toggle_screenshot_mode
cmd-replay-stop-desc = Stop and unload a replay.
cmd-replay-stop-help = replay_stop

View File

@@ -428,3 +428,7 @@ command-description-cmd-info =
On its own, this means it'll print the command's help message.
command-description-comp-rm =
Removes the given component from the entity.
command-description-overlay-toggle = Toggle an overlay on or off
command-description-overlay-add = Add an overlay (if it does not already exist)
command-description-overlay-remove = Remove an overlay

View File

@@ -43,6 +43,7 @@ input-key-MouseButton6 = Mouse 6
input-key-MouseButton7 = Mouse 7
input-key-MouseButton8 = Mouse 8
input-key-MouseButton9 = Mouse 9
input-key-CapsLock = Caps Lock
input-key-LSystem-win = Left Win
input-key-RSystem-win = Right Win

View File

@@ -0,0 +1,177 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PrototypeAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
[TestOf(typeof(PrototypeAnalyzer))]
public sealed class PrototypeAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new RTAnalyzerTest<PrototypeAnalyzer>()
{
TestState =
{
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.Prototypes.Attributes.cs",
"Robust.Shared.Prototypes.IPrototype.cs",
"Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute.cs"
);
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task RedundantTypeTest()
{
const string code = """
using Robust.Shared.Prototypes;
[Prototype]
public sealed partial class GoodAutoPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
}
[Prototype("someOtherName")]
public sealed partial class GoodUnmatchedPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
}
[Prototype("badMatched")]
public sealed partial class BadMatchedPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
}
[Prototype(ProtoName)]
public sealed partial class GoodNonLiteralMatchedPrototype : IPrototype
{
public const string ProtoName = "goodNonLiteralMatched";
[IdDataField]
public string ID { get; private set; } = default!;
}
[Prototype(ProtoName)]
public sealed partial class GoodNonLiteralUnmatchedPrototype : IPrototype
{
public const string ProtoName = "someOtherNameEntirely";
[IdDataField]
public string ID { get; private set; } = default!;
}
[Prototype("goodDoesNotEndWithPrototypeWord")]
public sealed partial class GoodDoesNotEndWithPrototypeWord : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
}
""";
await Verifier(code,
// /0/Test0.cs(9,2): warning RA0033: Prototype BadMatchedPrototype has explicitly set type "badMatched" that matches autogenerated value
VerifyCS.Diagnostic(PrototypeAnalyzer.PrototypeRedundantTypeRule).WithSpan(17, 12, 17, 24).WithArguments("BadMatchedPrototype", "badMatched")
);
}
[Test]
public async Task AliasTest()
{
const string code = """
using Robust.Shared.Prototypes;
using PPrototypeAttribute = Robust.Shared.Prototypes.PrototypeAttribute;
[PPrototype("badMatched")]
public sealed partial class BadMatchedPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
}
""";
await Verifier(code,
// /0/Test0.cs(4,2): warning RA0042: Prototype BadMatchedPrototype has explicitly set type "badMatched" that matches autogenerated value
VerifyCS.Diagnostic(PrototypeAnalyzer.PrototypeRedundantTypeRule).WithSpan(4, 13, 4, 25).WithArguments("BadMatchedPrototype", "badMatched")
);
}
[Test]
public async Task MoreAttributesTest()
{
const string code = """
using System;
using Robust.Shared.Prototypes;
using PPrototypeAttribute = Robust.Shared.Prototypes.PrototypeAttribute;
[FooBarAttribute]
[PPrototype("badMatched")]
public sealed partial class BadMatchedPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
}
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class FooBarAttribute : Attribute;
""";
await Verifier(code,
// /0/Test0.cs(4,2): warning RA0042: Prototype BadMatchedPrototype has explicitly set type "badMatched" that matches autogenerated value
VerifyCS.Diagnostic(PrototypeAnalyzer.PrototypeRedundantTypeRule).WithSpan(6, 13, 6, 25).WithArguments("BadMatchedPrototype", "badMatched")
);
}
[Test]
public async Task NameEndsWithPrototypeTest()
{
const string code = """
using Robust.Shared.Prototypes;
[Prototype]
public sealed partial class GoodAutoPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
}
[Prototype("ThisIsFine")]
public sealed partial class GoodManual : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
}
[Prototype]
public sealed partial class BadAuto : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
}
""";
await Verifier(code,
// /0/Test0.cs(18,29): error RA0043: Prototype BadAuto does not end with the word Prototype
VerifyCS.Diagnostic(PrototypeAnalyzer.PrototypeEndsWithPrototypeRule).WithSpan(18, 29, 18, 36).WithArguments("BadAuto")
);
}
}

View File

@@ -0,0 +1,95 @@
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.PrototypeAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
public sealed class PrototypeFixerTest
{
private static Task Verifier(string code, string fixedCode, params DiagnosticResult[] expected)
{
var test = new CSharpCodeFixTest<PrototypeAnalyzer, PrototypeFixer, DefaultVerifier>()
{
TestState =
{
Sources = { code },
},
FixedState =
{
Sources = { fixedCode },
}
};
test.TestState.Sources.Add(("PrototypeAttribute.cs", PrototypeAttributeDef));
test.FixedState.Sources.Add(("PrototypeAttribute.cs", PrototypeAttributeDef));
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
private const string PrototypeAttributeDef = """
using System;
namespace Robust.Shared.Prototypes
{
public class PrototypeAttribute : Attribute
{
public string? Type { get; internal set; }
public readonly int LoadPriority = 1;
public PrototypeAttribute(string? type = null, int loadPriority = 1)
{
Type = type;
LoadPriority = loadPriority;
}
public PrototypeAttribute(int loadPriority)
{
Type = null;
LoadPriority = loadPriority;
}
}
public interface IPrototype;
}
""";
[Test]
public async Task Test()
{
const string code = """
using Robust.Shared.Prototypes;
[Prototype]
public sealed partial class GoodAutoPrototype : IPrototype;
[Prototype("someOtherName")]
public sealed partial class GoodUnmatchedPrototype : IPrototype;
[Prototype("badMatched")]
public sealed partial class BadMatchedPrototype : IPrototype;
""";
const string fixedCode = """
using Robust.Shared.Prototypes;
[Prototype]
public sealed partial class GoodAutoPrototype : IPrototype;
[Prototype("someOtherName")]
public sealed partial class GoodUnmatchedPrototype : IPrototype;
[Prototype]
public sealed partial class BadMatchedPrototype : IPrototype;
""";
await Verifier(code, fixedCode,
// /0/Test0.cs(9,2): warning RA0033: Prototype BadMatchedPrototype has explicitly set type "badMatched" that matches autogenerated value
VerifyCS.Diagnostic(PrototypeAnalyzer.PrototypeRedundantTypeRule).WithSpan(9, 12, 9, 24).WithArguments("BadMatchedPrototype", "badMatched")
);
}
}

View File

@@ -15,8 +15,9 @@
<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\Analyzers\ForbidLiteralAttribute.cs" LogicalName="Robust.Shared.Analyzers.ForbidLiteralAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared.Utility\Analyzers\ObsoleteInheritanceAttribute.cs" LogicalName="Robust.Shared.Analyzers.ObsoleteInheritanceAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared.Utility\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\ObsoleteInheritanceAttribute.cs" LogicalName="Robust.Shared.Analyzers.ObsoleteInheritanceAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\ValidateMemberAttribute.cs" LogicalName="Robust.Shared.Analyzers.ValidateMemberAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\GameObjects\EventBusAttributes.cs" LogicalName="Robust.Shared.GameObjects.EventBusAttributes.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Serialization\NetSerializableAttribute.cs" LogicalName="Robust.Shared.Serialization.NetSerializableAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Prototypes\Attributes.cs" LogicalName="Robust.Shared.Prototypes.Attributes.cs" LinkBase="Implementations" />

View File

@@ -0,0 +1,96 @@
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.ValidateMemberAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
public sealed class ValidateMemberAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<ValidateMemberAnalyzer, DefaultVerifier>()
{
TestState =
{
Sources = { code },
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.Analyzers.ValidateMemberAttribute.cs"
);
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task Test()
{
const string code = """
using System;
using Robust.Shared.Analyzers;
public sealed class TestComponent
{
public int IntField;
public bool BoolField;
}
public sealed class OtherComponent
{
public float FloatField;
public double DoubleField;
}
public sealed class TestManager
{
public static void DirtyField<T>(T comp, [ValidateMember]string fieldName) { }
public static void DirtyTwoFields<T>(T comp, [ValidateMember]string first, [ValidateMember]string second) { }
}
public sealed class TestCaller
{
public void Test()
{
var testComp = new TestComponent();
var otherComp = new OtherComponent();
TestManager.DirtyField(testComp, nameof(TestComponent.IntField));
TestManager.DirtyField(testComp, nameof(OtherComponent.FloatField));
TestManager.DirtyField(otherComp, nameof(TestComponent.IntField));
TestManager.DirtyField(otherComp, nameof(OtherComponent.FloatField));
TestManager.DirtyTwoFields(testComp, nameof(TestComponent.IntField), nameof(TestComponent.BoolField));
TestManager.DirtyTwoFields(testComp, nameof(TestComponent.IntField), nameof(OtherComponent.FloatField));
TestManager.DirtyTwoFields(testComp, nameof(OtherComponent.FloatField), nameof(OtherComponent.DoubleField));
}
}
""";
await Verifier(code,
// /0/Test0.cs(31,42): error RA0033: FloatField is not a member of TestComponent
VerifyCS.Diagnostic().WithSpan(31, 42, 31, 75).WithArguments("FloatField", "TestComponent"),
// /0/Test0.cs(33,43): error RA0033: IntField is not a member of OtherComponent
VerifyCS.Diagnostic().WithSpan(33, 43, 33, 73).WithArguments("IntField", "OtherComponent"),
// /0/Test0.cs(39,78): error RA0033: FloatField is not a member of TestComponent
VerifyCS.Diagnostic().WithSpan(39, 78, 39, 111).WithArguments("FloatField", "TestComponent"),
// /0/Test0.cs(41,46): error RA0033: FloatField is not a member of TestComponent
VerifyCS.Diagnostic().WithSpan(41, 46, 41, 79).WithArguments("FloatField", "TestComponent"),
// /0/Test0.cs(41,81): error RA0033: DoubleField is not a member of TestComponent
VerifyCS.Diagnostic().WithSpan(41, 81, 41, 115).WithArguments("DoubleField", "TestComponent")
);
}
}

View File

@@ -16,6 +16,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
{
private const string DataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataDefinitionAttribute";
private const string ImplicitDataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.ImplicitDataDefinitionForInheritorsAttribute";
private const string MeansDataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.MeansDataDefinitionAttribute";
private const string DataFieldBaseNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataFieldBaseAttribute";
private const string ViewVariablesNamespace = "Robust.Shared.ViewVariables.ViewVariablesAttribute";
private const string NotYamlSerializableName = "Robust.Shared.Serialization.Manager.Attributes.NotYamlSerializableAttribute";
@@ -270,6 +271,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
return false;
return HasAttribute(type, DataDefinitionNamespace) ||
MeansDataDefinition(type) ||
IsImplicitDataDefinition(type);
}
@@ -425,6 +427,19 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
return (VVAccess)accessByte == VVAccess.ReadWrite;
}
private static bool MeansDataDefinition(ITypeSymbol type)
{
foreach (var attribute in type.GetAttributes())
{
if (attribute.AttributeClass is null)
continue;
if (HasAttribute(attribute.AttributeClass, MeansDataDefinitionNamespace))
return true;
}
return false;
}
private static bool IsNotYamlSerializable(ISymbol field, ITypeSymbol type)
{
return HasAttribute(type, NotYamlSerializableName);

View File

@@ -0,0 +1,16 @@
using Microsoft.CodeAnalysis;
namespace Robust.Analyzers;
public static class ITypeSymbolExtensions
{
public static IEnumerable<ITypeSymbol> GetBaseTypesAndThis(this ITypeSymbol type)
{
var current = type;
while (current != null)
{
yield return current;
current = current.BaseType;
}
}
}

View File

@@ -0,0 +1,139 @@
#nullable enable
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
using Robust.Shared.Prototypes;
namespace Robust.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class PrototypeAnalyzer : DiagnosticAnalyzer
{
public static readonly DiagnosticDescriptor PrototypeRedundantTypeRule = new(
Diagnostics.IdPrototypeRedundantType,
"Redundant Prototype Type specification",
"Prototype {0} has explicitly set type \"{1}\" that matches autogenerated value",
"Usage",
DiagnosticSeverity.Warning,
true,
"Remove the redundant type specification."
);
public static readonly DiagnosticDescriptor PrototypeEndsWithPrototypeRule = new(
Diagnostics.IdPrototypeEndsWithPrototype,
"Prototype name must end with the word Prototype",
"Prototype {0} does not end with the word Prototype",
"Usage",
DiagnosticSeverity.Error,
true,
"Add the word Prototype to the end of the class name or manually specify a name in the Prototype attribute."
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
[PrototypeRedundantTypeRule, PrototypeEndsWithPrototypeRule];
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze |
GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(static ctx =>
{
var prototypeAttribute =
ctx.Compilation.GetTypeByMetadataName("Robust.Shared.Prototypes.PrototypeAttribute");
// No attribute, no analyzer.
if (prototypeAttribute is null)
return;
ctx.RegisterSyntaxNodeAction(
symCtx => AnalyzePrototype(symCtx, prototypeAttribute),
SyntaxKind.ClassDeclaration);
});
}
private static void AnalyzePrototype(SyntaxNodeAnalysisContext context, INamedTypeSymbol prototypeAttributeSymbol)
{
if (context.Node is not ClassDeclarationSyntax classDeclarationSyntax)
return;
var classSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax);
if (classSymbol is null)
return;
var className = classSymbol.Name;
if (!AttributeHelper.HasAttribute(classSymbol, prototypeAttributeSymbol, out var attributeData))
return;
var prototypeAttribute = GetAttributeSyntax(attributeData, classDeclarationSyntax);
if (prototypeAttribute == null)
return;
// Check for autogenerated type
if (prototypeAttribute.ArgumentList?.Arguments[0] is not { } argumentSyntax)
{
if (!className.EndsWith(PrototypeUtility.PrototypeNameEnding))
{
context.ReportDiagnostic(Diagnostic.Create(PrototypeEndsWithPrototypeRule,
classDeclarationSyntax.Identifier.GetLocation(),
className));
}
return;
}
// We only care about redundancy if the argument is a string literal.
// Passing in a value that resolves to a redundant string is fine.
if (argumentSyntax.Expression is not LiteralExpressionSyntax literalSyntax)
return;
var literalValue = context.SemanticModel.GetConstantValue(literalSyntax);
if (literalValue.Value is not string specifiedName)
return;
var autoName = PrototypeUtility.CalculatePrototypeName(className);
// Check for name redundancy
if (autoName == specifiedName)
{
// If the class name does not end with "Prototype", allow the redundancy
if (!className.EndsWith(PrototypeUtility.PrototypeNameEnding))
return;
var location = argumentSyntax.GetLocation();
context.ReportDiagnostic(Diagnostic.Create(PrototypeRedundantTypeRule,
location,
className,
specifiedName));
}
}
private static AttributeSyntax? GetAttributeSyntax(
AttributeData attributeData,
ClassDeclarationSyntax classSyntax)
{
if (attributeData.ApplicationSyntaxReference is not { } syntaxReference)
return null;
foreach (var attributeList in classSyntax.AttributeLists)
{
foreach (var attribute in attributeList.Attributes)
{
if (syntaxReference.SyntaxTree != attribute.SyntaxTree)
continue;
if (!syntaxReference.Span.OverlapsWith(attribute.Span))
continue;
return attribute;
}
}
return null;
}
}

View File

@@ -0,0 +1,77 @@
#nullable enable
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Robust.Roslyn.Shared.Diagnostics;
namespace Robust.Analyzers;
[ExportCodeFixProvider(LanguageNames.CSharp)]
public sealed class PrototypeFixer : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => [IdPrototypeRedundantType];
public override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
foreach (var diagnostic in context.Diagnostics)
{
switch (diagnostic.Id)
{
case IdPrototypeRedundantType:
return RegisterRemoveType(context, diagnostic);
}
}
return Task.CompletedTask;
}
private static async Task RegisterRemoveType(CodeFixContext context, Diagnostic diagnostic)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
var span = diagnostic.Location.SourceSpan;
var token = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<AttributeArgumentSyntax>().First();
if (token == null)
return;
context.RegisterCodeFix(CodeAction.Create(
"Remove explicitly set type",
c => RemoveType(context.Document, token, c),
"Remove explicitly set type"
), diagnostic);
}
private static async Task<Document> RemoveType(Document document, AttributeArgumentSyntax syntax, CancellationToken cancellation)
{
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
if (syntax.Parent is not AttributeArgumentListSyntax argListSyntax)
return document;
if (argListSyntax.Arguments.Count == 1)
{
// If this is the only argument, remove the whole argument list so we don't leave empty parentheses
if (argListSyntax.Parent is not AttributeSyntax attributeSyntax)
return document;
var newAttributeSyntax = attributeSyntax.RemoveNode(argListSyntax, SyntaxRemoveOptions.KeepNoTrivia);
root = root!.ReplaceNode(attributeSyntax, newAttributeSyntax!);
}
else
{
// Otherwise just remove the argument
var newArgListSyntax = argListSyntax.WithArguments(argListSyntax.Arguments.Remove(syntax));
root = root!.ReplaceNode(argListSyntax, newArgListSyntax);
}
return document.WithSyntaxRoot(root);
}
}

View File

@@ -33,6 +33,11 @@
<Compile Include="..\Robust.Shared\Serialization\NetSerializableAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<ItemGroup>
<!-- Needed for PrototypeAnalyzer. -->
<Compile Include="..\Robust.Shared\Prototypes\PrototypeUtility.cs" LinkBase="Implementations" />
</ItemGroup>
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
<PropertyGroup>

View File

@@ -0,0 +1,95 @@
#nullable enable
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ValidateMemberAnalyzer : DiagnosticAnalyzer
{
private const string ValidateMemberType = "Robust.Shared.Analyzers.ValidateMemberAttribute";
private static readonly DiagnosticDescriptor ValidateMemberDescriptor = new(
Diagnostics.IdValidateMember,
"Invalid member name",
"{0} is not a member of {1}",
"Usage",
DiagnosticSeverity.Error,
true,
"Be sure the type and member name are correct.");
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [ValidateMemberDescriptor];
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterSyntaxNodeAction(AnalyzeExpression, SyntaxKind.InvocationExpression);
}
private void AnalyzeExpression(SyntaxNodeAnalysisContext context)
{
if (context.Node is not InvocationExpressionSyntax node)
return;
if (context.SemanticModel.GetSymbolInfo(node.Expression).Symbol is not IMethodSymbol methodSymbol)
return;
// We need at least one type argument for context
if (methodSymbol.TypeArguments.Length < 1)
return;
// We'll be checking members of the first type argument
if (methodSymbol.TypeArguments[0] is not INamedTypeSymbol targetType)
return;
// We defer building this set until we need it later, so we don't have to build it for every single method invocation!
ImmutableHashSet<ISymbol>? members = null;
// Check each parameter of the method
foreach (var parameterContext in node.ArgumentList.Arguments)
{
// Get the symbol for this parameter
if (context.SemanticModel.GetOperation(parameterContext) is not IArgumentOperation op || op.Parameter is null)
continue;
var parameterSymbol = op.Parameter.OriginalDefinition;
// Make sure the parameter has the ValidateMember attribute
if (!AttributeHelper.HasAttribute(parameterSymbol, ValidateMemberType, out _))
continue;
// Find the value passed for this parameter.
// We use GetConstantValue to resolve compile-time values - i.e. the result of nameof()
if (context.SemanticModel.GetConstantValue(parameterContext.Expression).Value is not string fieldName)
continue;
// Get a set containing all the members of the target type and its ancestors
members ??= targetType.GetBaseTypesAndThis().SelectMany(n => n.GetMembers()).ToImmutableHashSet(SymbolEqualityComparer.Default);
// Check each member of the target type to see if it matches our passed in value
var found = false;
foreach (var member in members)
{
if (member.Name == fieldName)
{
found = true;
break;
}
}
// If we didn't find it, report the violation
if (!found)
context.ReportDiagnostic(Diagnostic.Create(
ValidateMemberDescriptor,
parameterContext.GetLocation(),
fieldName,
targetType.Name
));
}
}
}

View File

@@ -59,5 +59,6 @@ public sealed class DefaultSQLConfig : IConfig
public CultureInfo CultureInfo => DefaultConfig.Instance.CultureInfo!;
public ConfigOptions Options => DefaultConfig.Instance.Options;
public TimeSpan BuildTimeout => DefaultConfig.Instance.BuildTimeout;
public WakeLockType WakeLock => DefaultConfig.Instance.WakeLock;
public IReadOnlyList<Conclusion> ConfigAnalysisConclusion => DefaultConfig.Instance.ConfigAnalysisConclusion;
}

View File

@@ -0,0 +1,19 @@
using System.Numerics;
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.Maths;
namespace Robust.Benchmarks.NumericsHelpers;
[Virtual, DisassemblyDiagnoser]
public class Box2Benchmark
{
public Box2 Box = new();
public Matrix3x2 Matrix = new();
[Benchmark]
public Box2 Transform()
{
return Matrix.TransformBox(Box);
}
}

View File

@@ -0,0 +1,18 @@
using System.Numerics;
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.Maths;
namespace Robust.Benchmarks.NumericsHelpers;
[Virtual, DisassemblyDiagnoser]
public class Box2RotatedBenchmark
{
public Box2Rotated Box = new();
[Benchmark]
public Matrix3x2 GetTransform()
{
return Box.Transform;
}
}

View File

@@ -0,0 +1,25 @@
using System.Runtime.Intrinsics;
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.Maths;
namespace Robust.Benchmarks.NumericsHelpers;
[Virtual, DisassemblyDiagnoser]
public class GetAABBBenchmark
{
public Vector128<float> X;
public Vector128<float> Y;
[Benchmark(Baseline = true)]
public Vector128<float> GetAABB_NoAvx()
{
return SimdHelpers.GetAABBSlow(X, Y);
}
[Benchmark]
public Vector128<float> GetAABB_Avx()
{
return SimdHelpers.GetAABBAvx(X, Y);
}
}

View File

@@ -0,0 +1,21 @@
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Shapes;
namespace Robust.Benchmarks.NumericsHelpers;
// SlimPolyon is internal, so this won't compile without changes.
/*
[Virtual, DisassemblyDiagnoser]
public class SlimPolygonBenchmark
{
public Box2Rotated RotBox = new();
[Benchmark]
public SlimPolygon RotatedBox()
{
return new SlimPolygon(in RotBox);
}
}
*/

View File

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

@@ -1,92 +0,0 @@
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,312 @@
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, MediumRunJob]
public class PhysicsBenchmark
{
// TODO: Rain
// Large pyramid
// Joint Grid
// Spinner
// Washer
const float frameTime = 0.016f;
#region Many Pyramids
private ISimulation _manyPyramidSim = default!;
[GlobalSetup(Target = nameof(ManyPyramids))]
public void PyramidSetup()
{
_manyPyramidSim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = _manyPyramidSim.Resolve<IEntityManager>();
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
SetupManyPyramids(entManager, mapId);
}
[Benchmark]
public void ManyPyramids()
{
var entManager = _manyPyramidSim.Resolve<IEntityManager>();
for (var i = 0; i < (1f / frameTime) * 10; i++)
{
entManager.TickUpdate(frameTime, false);
}
}
private void SetupManyPyramids(IEntityManager entManager, MapId mapId)
{
int baseCount = 10;
float extent = 0.5f;
int rowCount = 20; // 5
int columnCount = 5;
// Setup ground
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
physics.SetGravity(new Vector2(0f, -9.8f));
// Setup boxes
float a = 0.5f;
PolygonShape shape = new();
shape.SetAsBox(a, a);
float groundDeltaY = 2.0f * extent * ( baseCount + 1.0f );
float groundWidth = 2.0f * extent * columnCount * ( baseCount + 1.0f );
float groundY = 0.0f;
for ( int i = 0; i < rowCount; ++i )
{
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(-0.5f * 2.0f * groundWidth, groundY), new Vector2(0.5f * 2.0f * groundWidth, groundY));
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
physics.WakeBody(groundUid, body: ground);
groundY += groundDeltaY;
}
float baseWidth = 2.0f * extent * baseCount;
float baseY = 0.0f;
for ( int i = 0; i < rowCount; ++i )
{
for ( int j = 0; j < columnCount; ++j )
{
float centerX = -0.5f * groundWidth + j * ( baseWidth + 2.0f * extent ) + extent;
CreateSmallPyramid(entManager, mapId, baseCount, extent, centerX, baseY);
}
baseY += groundDeltaY;
}
}
private void CreateSmallPyramid(IEntityManager entManager, MapId mapId, int baseCount, float extent, float centerX, float baseY)
{
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
var shape = new PolygonShape();
shape.SetAsBox(extent, extent);
for ( int i = 0; i < baseCount; ++i )
{
float y = ( 2.0f * i + 1.0f ) * extent + baseY;
for ( int j = i; j < baseCount; ++j )
{
float x = ( i + 1.0f ) * extent + 2.0f * ( j - i ) * extent + centerX - 0.5f;
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(new Vector2(x, 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);
physics.WakeBody(boxUid);
}
}
}
#endregion
#region Smash
private ISimulation _smashSim = default!;
[GlobalSetup(Target = nameof(Smash))]
public void SmashSetup()
{
_smashSim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = _smashSim.Resolve<IEntityManager>();
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
var joints = entManager.System<SharedJointSystem>();
var xformSystem = entManager.System<SharedTransformSystem>();
physics.SetGravity(new Vector2(0f, -9.8f));
{
var smashBox = new PolygonShape();
smashBox.SetAsBox(4f, 4f);
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);
xformSystem.SetLocalPosition(bodyUid, new Vector2(-20f, 0f));
physics.SetLinearVelocity(bodyUid, new Vector2(40f, 0f));
fixtures.TryCreateFixture(bodyUid, smashBox, "fix1", density: 8f, hard: true);
}
float d = 0.4f;
var box = new PolygonShape();
box.SetAsBox(0.5f * d, 0.5f * d);
int columns = 120; // 20
int rows = 80; // 10
for ( int i = 0; i < columns; ++i )
{
for ( int j = 0; j < rows; ++j )
{
var bodyUid = entManager.SpawnEntity(null, new MapCoordinates(i * d + 30f, ( j - rows / 2.0f ) * d, 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);
xformSystem.SetLocalPosition(bodyUid, new Vector2(-20f, 0f));
physics.SetLinearVelocity(bodyUid, new Vector2(40f, 0f));
fixtures.TryCreateFixture(bodyUid, box, "fix1", hard: true);
physics.WakeBody(bodyUid);
}
}
}
[Benchmark]
public void Smash()
{
var entManager = _smashSim.Resolve<IEntityManager>();
for (var i = 0; i < (1f / frameTime) * 10; i++)
{
entManager.TickUpdate(frameTime, false);
}
}
#endregion
#region Tumbler
private ISimulation _tumblerSim = default!;
[GlobalSetup(Target = nameof(Tumbler))]
public void TumblerSetup()
{
_tumblerSim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = _tumblerSim.Resolve<IEntityManager>();
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
SetupTumbler(entManager, mapId);
}
[Benchmark]
public void Tumbler()
{
var entManager = _tumblerSim.Resolve<IEntityManager>();
for (var i = 0; i < 1 / frameTime * 10; i++)
{
entManager.TickUpdate(frameTime, false);
}
}
private void SetupTumbler(IEntityManager entManager, MapId mapId)
{
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
var joints = entManager.System<SharedJointSystem>();
physics.SetGravity(new Vector2(0f, -9.8f));
{
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, 50f));
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, 50f));
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, 50f));
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, 50f));
physics.WakeBody(groundUid, body: ground);
physics.WakeBody(bodyUid, body: body);
var revolute = joints.CreateRevoluteJoint(groundUid, bodyUid);
var motorSpeed = 25f;
revolute.LocalAnchorA = new Vector2(0f, 10f);
revolute.LocalAnchorB = new Vector2(0f, 0f);
revolute.ReferenceAngle = 0f;
revolute.MotorSpeed = MathF.PI / 180f * motorSpeed;
revolute.MaxMotorTorque = 100000000f;
revolute.EnableMotor = true;
}
// Make boxes
{
var gridCount = 20; // 45
var y = -0.2f * gridCount + 10f;
var a = 0.125f;
PolygonShape shape = new();
shape.SetAsBox(a, a);
for (var i = 0; i < gridCount; i++)
{
var x = -0.2f * gridCount;
for (var j = 0; j < gridCount; j++)
{
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(new Vector2(x, y), mapId));
var body = entManager.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: body);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f), body: body);
x += 0.4f;
physics.WakeBody(boxUid, body: body);
physics.SetSleepingAllowed(boxUid, body, false);
}
y += 0.4f;
}
}
}
#endregion
}

View File

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

@@ -1,108 +0,0 @@
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>();
var mapUid = entManager.System<SharedMapSystem>().CreateMap(out var mapId);
SetupTumbler(entManager, mapId);
for (var i = 0; i < 300; 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);
}
if (entManager.TryGetComponent(mapUid, out BroadphaseComponent? mapBroadphase))
entManager.System<SharedBroadphaseSystem>().RebuildBottomUp(mapBroadphase);
}
[Benchmark]
public void Tumbler()
{
var entManager = _sim.Resolve<IEntityManager>();
for (var i = 0; i < 1000; 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

@@ -6,14 +6,24 @@
<OutputPath>../bin/Benchmarks</OutputPath>
<OutputType>Exe</OutputType>
<NoWarn>RA0003</NoWarn>
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Robust.Server.Testing\Robust.Server.Testing.csproj" />
<ProjectReference Include="..\Robust.Server\Robust.Server.csproj" />
<ProjectReference Include="..\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
<ProjectReference Include="..\Robust.UnitTesting\Robust.UnitTesting.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" />
<!-- BenchmarkDotNet autogenerates files that attempt to reference BenchmarkDotNet through Robust.Benchmarks.
By default the RT project privates these files, so we have to explicitly state that these files should be made available
to the BenchmarkDotNet project so it can build the runner that executes the benchmark. -->
<PackageReference Include="BenchmarkDotNet" PrivateAssets="none" />
<PackageReference Include="JetBrains.Annotations" />
<PackageReference Include="YamlDotNet" />
<PackageReference Include="prometheus-net" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>

View File

@@ -1,4 +0,0 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Robust.Client")]
[module: SkipLocalsInit]

View File

@@ -1,22 +0,0 @@
global using Robust.Client.Interop.RobustNative.Webgpu;
global using static Robust.Client.Interop.RobustNative.Webgpu.Wgpu;
global using static Robust.Shared.Utility.FfiHelper;
global using unsafe WGPUTexture = Robust.Client.Interop.RobustNative.Webgpu.WGPUTextureImpl*;
global using unsafe WGPUDevice = Robust.Client.Interop.RobustNative.Webgpu.WGPUDeviceImpl*;
global using unsafe WGPUQueue = Robust.Client.Interop.RobustNative.Webgpu.WGPUQueueImpl*;
global using unsafe WGPUAdapter = Robust.Client.Interop.RobustNative.Webgpu.WGPUAdapterImpl*;
global using unsafe WGPUInstance = Robust.Client.Interop.RobustNative.Webgpu.WGPUInstanceImpl*;
global using unsafe WGPUTextureView = Robust.Client.Interop.RobustNative.Webgpu.WGPUTextureViewImpl*;
global using unsafe WGPUBindGroup = Robust.Client.Interop.RobustNative.Webgpu.WGPUBindGroupImpl*;
global using unsafe WGPUBindGroupLayout = Robust.Client.Interop.RobustNative.Webgpu.WGPUBindGroupLayoutImpl*;
global using unsafe WGPUBuffer = Robust.Client.Interop.RobustNative.Webgpu.WGPUBufferImpl*;
global using unsafe WGPUSampler = Robust.Client.Interop.RobustNative.Webgpu.WGPUSamplerImpl*;
global using unsafe WGPUCommandBuffer = Robust.Client.Interop.RobustNative.Webgpu.WGPUCommandBufferImpl*;
global using unsafe WGPUCommandEncoder = Robust.Client.Interop.RobustNative.Webgpu.WGPUCommandEncoderImpl*;
global using unsafe WGPURenderPassEncoder = Robust.Client.Interop.RobustNative.Webgpu.WGPURenderPassEncoderImpl*;
global using unsafe WGPURenderPipeline = Robust.Client.Interop.RobustNative.Webgpu.WGPURenderPipelineImpl*;
global using unsafe WGPUPipelineLayout = Robust.Client.Interop.RobustNative.Webgpu.WGPUPipelineLayoutImpl*;
global using unsafe WGPUShaderModule = Robust.Client.Interop.RobustNative.Webgpu.WGPUShaderModuleImpl*;
global using unsafe WGPUSurface = Robust.Client.Interop.RobustNative.Webgpu.WGPUSurfaceImpl*;

View File

@@ -1,131 +0,0 @@
using Robust.Client.Graphics.Rhi.WebGpu;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics.Rhi;
public abstract partial class RhiBase
{
//
// Clyde <-> RHI API.
//
internal abstract void Init(in RhiInitParams initParams, out RhiWebGpu.WindowData windowData);
internal abstract void Shutdown();
/// <summary>
/// A window was created by Clyde. It should be initialized by the RHI to make it ready for rendering.
/// </summary>
/// <remarks>
/// Does not get called for the main window.
/// </remarks>
internal abstract RhiWebGpu.WindowData WindowCreated(in RhiWindowSurfaceParams surfaceParams, Vector2i size, bool vsync);
/// <summary>
/// A window is about to be destroyed by Clyde. Clean up resources for it.
/// </summary>
internal abstract void WindowDestroy(RhiWebGpu.WindowData reg);
/// <summary>
/// Recreate the native swap chain, in case it has become suboptimal (e.g. due to window resizing).
/// </summary>
internal abstract void WindowRecreateSwapchain(RhiWebGpu.WindowData reg, Vector2i size, bool vsyncEnabled);
internal abstract RhiTexture GetSurfaceTextureForWindow(RhiWebGpu.WindowData reg);
internal abstract void WindowPresent(RhiWebGpu.WindowData reg);
//
// RHI-internal API to de-OOP the public RHI API.
//
internal abstract RhiRenderPassEncoder CommandEncoderBeginRenderPass(
RhiCommandEncoder encoder,
in RhiRenderPassDescriptor descriptor
);
internal abstract RhiCommandBuffer CommandEncoderFinish(
in RhiCommandEncoder encoder,
in RhiCommandBufferDescriptor descriptor);
internal abstract void RenderPassEncoderSetPipeline(
in RhiRenderPassEncoder encoder,
RhiRenderPipeline pipeline
);
internal abstract void RenderPassEncoderDraw(
in RhiRenderPassEncoder encoder,
uint vertexCount,
uint instanceCount,
uint firstVertex,
uint firstInstance
);
internal abstract void RenderPassEncoderEnd(RhiRenderPassEncoder encoder);
internal abstract void QueueSubmit(RhiQueue queue, RhiCommandBuffer[] commandBuffers);
internal abstract void QueueWriteTexture(
RhiQueue queue,
in RhiImageCopyTexture destination,
ReadOnlySpan<byte> data,
in RhiImageDataLayout dataLayout,
RhiExtent3D size
);
public abstract void QueueWriteBuffer(RhiBuffer buffer, ulong bufferOffset, ReadOnlySpan<byte> data);
internal abstract RhiTextureView TextureCreateView(RhiTexture texture, in RhiTextureViewDescriptor descriptor);
internal abstract void TextureViewDrop(RhiTextureView textureView);
internal abstract void BindGroupDrop(RhiBindGroup rhiBindGroup);
internal abstract void RenderPassEncoderSetBindGroup(
RhiRenderPassEncoder encoder,
uint index,
RhiBindGroup? bindGroup
);
internal abstract void RenderPassEncoderSetVertexBuffer(RhiRenderPassEncoder encoder,
uint slot,
RhiBuffer? buffer,
ulong offset,
ulong? size);
internal abstract void RenderPassEncoderSetScissorRect(RhiRenderPassEncoder encoder,
uint x,
uint y,
uint w,
uint h);
internal abstract void CommandBufferDrop(RhiCommandBuffer commandBuffer);
internal abstract RhiBufferMapState BufferGetMapState(RhiBuffer buffer);
internal abstract ValueTask BufferMapAsync(RhiBuffer buffer, RhiMapModeFlags mode, nuint offset, nuint size);
internal abstract RhiMappedBufferRange BufferGetMappedRange(RhiBuffer buffer, nuint offset, nuint size);
internal abstract void BufferUnmap(RhiBuffer buffer);
internal abstract void BufferDrop(RhiBuffer buffer);
internal struct RhiInitParams
{
public required string Backends;
public required RhiPowerPreference PowerPreference;
public required RhiWindowSurfaceParams MainWindowSurfaceParams;
}
internal unsafe struct RhiWindowSurfaceParams
{
#if WINDOWS
public void* HInstance;
public void* HWnd;
#elif MACOS
public void* MetalLayer;
#elif LINUX
public bool Wayland; // False = X11
public void* X11Display;
public void* X11Window;
public void* WaylandDisplay;
public void* WaylandSurface;
#endif
}
}
internal record struct RhiHandle(long Value);

View File

@@ -1,40 +0,0 @@
using System.Numerics;
using System.Runtime.InteropServices;
namespace Robust.Client.Graphics.Rhi;
/// <summary>
/// Equivalent to a WGSL <c>mat2x3f</c>.
/// </summary>
/// <remarks>
/// This matrix is columnar and 2 columns, 3 rows. This is equivalent to .NET's <see cref="Matrix3x2"/>!
/// </remarks>
[StructLayout(LayoutKind.Explicit)]
public struct ShaderMat2x3F
{
[FieldOffset(0)]
public float M11;
[FieldOffset(4)]
public float M21;
[FieldOffset(8)]
public float M31;
[FieldOffset(16)]
public float M12;
[FieldOffset(20)]
public float M22;
[FieldOffset(24)]
public float M32;
public static ShaderMat2x3F FromMatrix(in Matrix3x2 matrix)
{
var ret = default(ShaderMat2x3F);
ret.M11 = matrix.M11;
ret.M12 = matrix.M12;
ret.M21 = matrix.M21;
ret.M22 = matrix.M22;
ret.M31 = matrix.M31;
ret.M32 = matrix.M32;
return ret;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\MSBuild\Robust.Engine.props"/>
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<SkipRobustSerializationGenerator>true</SkipRobustSerializationGenerator>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Robust.Client.Interop.RobustNative\Robust.Client.Interop.RobustNative.csproj" />
<ProjectReference Include="..\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
<ProjectReference Include="..\Robust.Shared.Utility\Robust.Shared.Utility.csproj" />
</ItemGroup>
<Import Project="..\MSBuild\Robust.Properties.targets"/>
</Project>

View File

@@ -1,113 +0,0 @@
namespace Robust.Client.Graphics.Rhi.WebGpu;
internal sealed unsafe partial class RhiWebGpu
{
private readonly Dictionary<RhiHandle, BindGroupLayoutReg> _bindGroupLayoutRegistry = new();
private readonly Dictionary<RhiHandle, BindGroupReg> _bindGroupRegistry = new();
internal override void BindGroupDrop(RhiBindGroup rhiBindGroup)
{
wgpuBindGroupRelease(_bindGroupRegistry[rhiBindGroup.Handle].Native);
_bindGroupRegistry.Remove(rhiBindGroup.Handle);
}
public override RhiBindGroupLayout CreateBindGroupLayout(in RhiBindGroupLayoutDescriptor descriptor)
{
Span<byte> buffer = stackalloc byte[512];
var pDescriptor = BumpAllocate<WGPUBindGroupLayoutDescriptor>(ref buffer);
pDescriptor->label = BumpAllocateStringView(ref buffer, descriptor.Label);
var entries = descriptor.Entries;
pDescriptor->entryCount = (uint)entries.Length;
pDescriptor->entries = BumpAllocate<WGPUBindGroupLayoutEntry>(ref buffer, entries.Length);
for (var i = 0; i < entries.Length; i++)
{
ref var entry = ref entries[i];
var pEntry = &pDescriptor->entries[i];
pEntry->binding = entry.Binding;
pEntry->visibility = (ulong)entry.Visibility;
switch (entry.Layout)
{
case RhiSamplerBindingLayout sampler:
pEntry->sampler.type = (WGPUSamplerBindingType)sampler.Type;
break;
case RhiTextureBindingLayout texture:
pEntry->texture.multisampled = texture.Multisampled ? 1u : 0u;
pEntry->texture.sampleType = (WGPUTextureSampleType)texture.SampleType;
pEntry->texture.viewDimension =
(WGPUTextureViewDimension)ValidateTextureViewDimension(texture.ViewDimension);
break;
case RhiBufferBindingLayout layoutBuffer:
pEntry->buffer.type = (WGPUBufferBindingType) layoutBuffer.Type;
pEntry->buffer.hasDynamicOffset = layoutBuffer.HasDynamicOffset ? 1u : 0u;
pEntry->buffer.minBindingSize = layoutBuffer.MinBindingSize;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
var native = wgpuDeviceCreateBindGroupLayout(_wgpuDevice, pDescriptor);
// TODO: Thread safety
var handle = AllocRhiHandle();
_bindGroupLayoutRegistry.Add(handle, new BindGroupLayoutReg { Native = native });
return new RhiBindGroupLayout(this, handle);
}
public override RhiBindGroup CreateBindGroup(in RhiBindGroupDescriptor descriptor)
{
// TODO: SAFETY
Span<byte> buffer = stackalloc byte[1024];
var pDescriptor = BumpAllocate<WGPUBindGroupDescriptor>(ref buffer);
pDescriptor->label = BumpAllocateStringView(ref buffer, descriptor.Label);
pDescriptor->layout = _bindGroupLayoutRegistry[descriptor.Layout.Handle].Native;
var entries = descriptor.Entries;
pDescriptor->entryCount = (uint) entries.Length;
pDescriptor->entries = BumpAllocate<WGPUBindGroupEntry>(ref buffer, entries.Length);
for (var i = 0; i < entries.Length; i++)
{
ref var entry = ref descriptor.Entries[i];
var pEntry = &pDescriptor->entries[i];
pEntry->binding = entry.Binding;
switch (entry.Resource)
{
case RhiSampler rhiSampler:
pEntry->sampler = _samplerRegistry[rhiSampler.Handle].Native;
break;
case RhiTextureView rhiTextureView:
pEntry->textureView = _textureViewRegistry[rhiTextureView.Handle].Native;
break;
case RhiBufferBinding bufferBinding:
pEntry->buffer = _bufferRegistry[bufferBinding.Buffer.Handle].Native;
pEntry->offset = bufferBinding.Offset;
pEntry->size = bufferBinding.Size ?? WGPU_WHOLE_SIZE;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
var bindGroup = wgpuDeviceCreateBindGroup(_wgpuDevice, pDescriptor);
var handle = AllocRhiHandle();
_bindGroupRegistry.Add(handle, new BindGroupReg { Native = bindGroup });
return new RhiBindGroup(this, handle);
}
private sealed class BindGroupLayoutReg
{
public WGPUBindGroupLayout Native;
}
private sealed class BindGroupReg
{
public WGPUBindGroup Native;
}
}

View File

@@ -1,148 +0,0 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Robust.Client.Graphics.Rhi.WebGpu;
internal sealed partial class RhiWebGpu
{
private readonly Dictionary<RhiHandle, BufferReg> _bufferRegistry = new();
public override unsafe RhiBuffer CreateBuffer(in RhiBufferDescriptor descriptor)
{
Span<byte> buffer = stackalloc byte[512];
var pDescriptor = BumpAllocate<WGPUBufferDescriptor>(ref buffer);
pDescriptor->label = BumpAllocateStringView(ref buffer, descriptor.Label);
pDescriptor->mappedAtCreation = descriptor.MappedAtCreation ? 1u : 0u;
pDescriptor->size = descriptor.Size;
pDescriptor->usage = (ulong) descriptor.Usage;
var native = wgpuDeviceCreateBuffer(_wgpuDevice, pDescriptor);
var handle = AllocRhiHandle();
_bufferRegistry.Add(handle, new BufferReg { Native = native });
var rhiBuffer= new RhiBuffer(this, handle);
if (pDescriptor->mappedAtCreation == 1)
{
rhiBuffer.Mapping = new RhiBuffer.ActiveMapping(rhiBuffer) { Valid = true };
}
return rhiBuffer;
}
internal override unsafe RhiBufferMapState BufferGetMapState(RhiBuffer buffer)
{
var nativeBuffer = _bufferRegistry[buffer.Handle].Native;
return (RhiBufferMapState) wgpuBufferGetMapState(nativeBuffer);
}
internal override async ValueTask BufferMapAsync(RhiBuffer buffer, RhiMapModeFlags mode, nuint offset, nuint size)
{
// TODO: Probably need some more locks here idk.
// So people can't map the buffer at the same time as or something.
buffer.MapState = RhiBufferMapState.Pending;
WgpuMapBufferAsyncResult result;
using (var promise = new WgpuPromise<WgpuMapBufferAsyncResult>())
{
unsafe
{
var nativeBuffer = _bufferRegistry[buffer.Handle].Native;
wgpuBufferMapAsync(
nativeBuffer,
(ulong) mode,
offset,
size,
new WGPUBufferMapCallbackInfo
{
callback = &WgpuMapBufferAsyncCallback,
userdata1 = promise.UserData,
}
);
}
// TODO: are we handling the error correctly, here?
result = await promise.Task;
buffer.Mapping = new RhiBuffer.ActiveMapping(buffer) { Valid = true };
}
if (result.Status != WGPUMapAsyncStatus.WGPUMapAsyncStatus_Success)
throw new RhiException(result.Status.ToString());
buffer.MapState = RhiBufferMapState.Mapped;
}
internal override unsafe RhiMappedBufferRange BufferGetMappedRange(RhiBuffer buffer, nuint offset, nuint size)
{
if (size > int.MaxValue)
throw new ArgumentException("Mapped area too big!");
if (buffer.Mapping == null)
throw new InvalidOperationException("Buffer is not mapped");
lock (buffer.Mapping)
{
if (!buffer.Mapping.Valid)
{
// Not sure if this is possible, but can't hurt.
throw new InvalidOperationException();
}
var nativeBuffer = _bufferRegistry[buffer.Handle].Native;
var mapped = wgpuBufferGetMappedRange(nativeBuffer, offset, size);
return new RhiMappedBufferRange(buffer.Mapping, mapped, (int) size);
}
}
internal override unsafe void BufferUnmap(RhiBuffer buffer)
{
if (buffer.Mapping == null)
throw new InvalidOperationException("Buffer is not mapped!");
lock (buffer.Mapping)
{
if (!buffer.Mapping.Valid)
{
// Not sure if this is possible, but can't hurt.
throw new InvalidOperationException();
}
if (buffer.Mapping.ActiveSpans > 0)
throw new InvalidOperationException("Current thread has buffer accessible as span, cannot unmap!");
var nativeBuffer = _bufferRegistry[buffer.Handle].Native;
wgpuBufferUnmap(nativeBuffer);
buffer.Mapping.Valid = false;
buffer.Mapping = null;
buffer.MapState = RhiBufferMapState.Unmapped;
}
}
internal override unsafe void BufferDrop(RhiBuffer buffer)
{
wgpuBufferRelease(_bufferRegistry[buffer.Handle].Native);
_bufferRegistry.Remove(buffer.Handle);
}
private sealed unsafe class BufferReg
{
public WGPUBuffer Native;
}
private record struct WgpuMapBufferAsyncResult(WGPUMapAsyncStatus Status);
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static unsafe void WgpuMapBufferAsyncCallback(
WGPUMapAsyncStatus status,
WGPUStringView stringView,
void* userdata1,
void* userdata2)
{
WgpuPromise<WgpuMapBufferAsyncResult>.SetResult(userdata1, new WgpuMapBufferAsyncResult(status));
}
}

View File

@@ -1,21 +0,0 @@
namespace Robust.Client.Graphics.Rhi.WebGpu;
internal sealed unsafe partial class RhiWebGpu
{
private readonly Dictionary<RhiHandle, CommandBufferReg> _commandBufferRegistry = new();
/// <summary>
/// Command buffer was dropped natively, either via explicit call or implicit side effect (e.g. queue submit).
/// </summary>
private void CommandBufferDropped(RhiCommandBuffer commandBuffer)
{
_commandBufferRegistry.Remove(commandBuffer.Handle);
GC.SuppressFinalize(commandBuffer);
}
internal override void CommandBufferDrop(RhiCommandBuffer commandBuffer)
{
wgpuCommandBufferRelease(_commandBufferRegistry[commandBuffer.Handle].Native);
CommandBufferDropped(commandBuffer);
}
}

View File

@@ -1,221 +0,0 @@
namespace Robust.Client.Graphics.Rhi.WebGpu;
internal sealed unsafe partial class RhiWebGpu
{
private readonly Dictionary<RhiHandle, CommandEncoderReg> _commandEncoderRegistry = new();
private readonly Dictionary<RhiHandle, RenderPassEncoderReg> _renderPassEncoderRegistry = new();
public override RhiCommandEncoder CreateCommandEncoder(in RhiCommandEncoderDescriptor descriptor)
{
WGPUCommandEncoder nativeEncoder;
fixed (byte* pLabel = MakeLabel(descriptor.Label))
{
var nativeDescriptor = new WGPUCommandEncoderDescriptor
{
label = new WGPUStringView {data = (sbyte*)pLabel, length = WGPU_STRLEN}
};
nativeEncoder = wgpuDeviceCreateCommandEncoder(_wgpuDevice, &nativeDescriptor);
}
// TODO: thread safety
var handle = AllocRhiHandle();
_commandEncoderRegistry.Add(handle, new CommandEncoderReg { Native = nativeEncoder });
return new RhiCommandEncoder(this, handle);
}
internal override RhiRenderPassEncoder CommandEncoderBeginRenderPass(
RhiCommandEncoder encoder,
in RhiRenderPassDescriptor descriptor)
{
// TODO: Ensure not disposed
// TODO: Thread safety
Span<byte> buffer = stackalloc byte[512];
var pDescriptor = BumpAllocate<WGPURenderPassDescriptor>(ref buffer);
pDescriptor->label = BumpAllocateStringView(ref buffer, descriptor.Label);
var colorAttachments = descriptor.ColorAttachments;
pDescriptor->colorAttachmentCount = (uint)colorAttachments.Length;
pDescriptor->colorAttachments = BumpAllocate<WGPURenderPassColorAttachment>(ref buffer, colorAttachments.Length);
for (var i = 0; i < colorAttachments.Length; i++)
{
ref var attachment = ref colorAttachments[i];
var pAttachment = &pDescriptor->colorAttachments[i];
pAttachment->view = _textureViewRegistry[attachment.View.Handle].Native;
if (attachment.ResolveTarget is { } resolveTarget)
pAttachment->resolveTarget = _textureViewRegistry[resolveTarget.Handle].Native;
pAttachment->clearValue = WgpuColor(attachment.ClearValue);
pAttachment->loadOp = (WGPULoadOp)attachment.LoadOp;
pAttachment->storeOp = (WGPUStoreOp)attachment.StoreOp;
pAttachment->depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
}
if (descriptor.DepthStencilAttachment is { } depthStencilAttachment)
{
var pDepthStencilAttachment = BumpAllocate<WGPURenderPassDepthStencilAttachment>(ref buffer);
pDescriptor->depthStencilAttachment = pDepthStencilAttachment;
pDepthStencilAttachment->view = _textureViewRegistry[depthStencilAttachment.View.Handle].Native;
pDepthStencilAttachment->depthLoadOp = (WGPULoadOp)depthStencilAttachment.DepthLoadOp;
pDepthStencilAttachment->depthStoreOp = (WGPUStoreOp)depthStencilAttachment.DepthStoreOp;
pDepthStencilAttachment->depthClearValue = depthStencilAttachment.DepthClearValue;
pDepthStencilAttachment->depthReadOnly = depthStencilAttachment.DepthReadOnly ? 1u : 0u;
pDepthStencilAttachment->stencilLoadOp = (WGPULoadOp)depthStencilAttachment.StencilLoadOp;
pDepthStencilAttachment->stencilStoreOp = (WGPUStoreOp)depthStencilAttachment.StencilStoreOp;
pDepthStencilAttachment->stencilClearValue = depthStencilAttachment.StencilClearValue;
pDepthStencilAttachment->stencilReadOnly = depthStencilAttachment.StencilReadOnly ? 1u : 0u;
}
if (descriptor.OcclusionQuerySet != null)
throw new NotImplementedException();
var pDescriptorMaxDrawCount = BumpAllocate<WGPURenderPassMaxDrawCount>(ref buffer);
pDescriptor->nextInChain = (WGPUChainedStruct*)pDescriptorMaxDrawCount;
pDescriptorMaxDrawCount->chain.sType = WGPUSType.WGPUSType_RenderPassMaxDrawCount;
pDescriptorMaxDrawCount->maxDrawCount = descriptor.MaxDrawCount;
var nativeEncoder = wgpuCommandEncoderBeginRenderPass(
_commandEncoderRegistry[encoder.Handle].Native,
pDescriptor
);
// TODO: thread safety
var handle = AllocRhiHandle();
_renderPassEncoderRegistry.Add(handle, new RenderPassEncoderReg { Native = nativeEncoder });
return new RhiRenderPassEncoder(this, handle);
}
internal override void RenderPassEncoderSetPipeline(
in RhiRenderPassEncoder encoder,
RhiRenderPipeline pipeline)
{
// TODO: safety
wgpuRenderPassEncoderSetPipeline(
_renderPassEncoderRegistry[encoder.Handle].Native,
_renderPipelineRegistry[pipeline.Handle].Native
);
}
internal override void RenderPassEncoderDraw(
in RhiRenderPassEncoder encoder,
uint vertexCount,
uint instanceCount,
uint firstVertex,
uint firstInstance)
{
// TODO: safety
wgpuRenderPassEncoderDraw(
_renderPassEncoderRegistry[encoder.Handle].Native,
vertexCount,
instanceCount,
firstVertex,
firstInstance
);
}
internal override void RenderPassEncoderEnd(RhiRenderPassEncoder encoder)
{
// TODO: safety
var handle = encoder.Handle;
wgpuRenderPassEncoderEnd(_renderPassEncoderRegistry[handle].Native);
RenderPassEncoderDropped(handle);
}
internal override void RenderPassEncoderSetBindGroup(
RhiRenderPassEncoder encoder,
uint index,
RhiBindGroup? bindGroup)
{
wgpuRenderPassEncoderSetBindGroup(
_renderPassEncoderRegistry[encoder.Handle].Native,
index,
_bindGroupRegistry[bindGroup!.Handle].Native,
0, null
);
}
internal override void RenderPassEncoderSetVertexBuffer(
RhiRenderPassEncoder encoder,
uint slot,
RhiBuffer? buffer,
ulong offset,
ulong? size)
{
WGPUBuffer nativeBuffer = null;
if (buffer != null)
nativeBuffer = _bufferRegistry[buffer.Handle].Native;
wgpuRenderPassEncoderSetVertexBuffer(
_renderPassEncoderRegistry[encoder.Handle].Native,
slot,
nativeBuffer,
offset,
size ?? WGPU_WHOLE_SIZE
);
}
internal override void RenderPassEncoderSetScissorRect(
RhiRenderPassEncoder encoder,
uint x, uint y, uint w, uint h)
{
// TODO: safety
wgpuRenderPassEncoderSetScissorRect(
_renderPassEncoderRegistry[encoder.Handle].Native,
x,
y,
w,
h
);
}
internal override RhiCommandBuffer CommandEncoderFinish(
in RhiCommandEncoder encoder,
in RhiCommandBufferDescriptor descriptor)
{
// TODO: safety
var handle = encoder.Handle;
Span<byte> buffer = stackalloc byte[512];
var pDescriptor = BumpAllocate<WGPUCommandBufferDescriptor>(ref buffer);
pDescriptor->label = BumpAllocateStringView(ref buffer, descriptor.Label);
var nativeBuffer = wgpuCommandEncoderFinish(
_commandEncoderRegistry[handle].Native,
pDescriptor
);
CommandEncoderDropped(handle);
var bufferHandle = AllocRhiHandle();
_commandBufferRegistry.Add(bufferHandle, new CommandBufferReg { Native = nativeBuffer });
return new RhiCommandBuffer(this, bufferHandle);
}
private void CommandEncoderDropped(RhiHandle encoder)
{
_commandEncoderRegistry.Remove(encoder);
}
private void RenderPassEncoderDropped(RhiHandle encoder)
{
_renderPassEncoderRegistry.Remove(encoder);
}
private sealed class CommandEncoderReg
{
public WGPUCommandEncoder Native;
}
private sealed class RenderPassEncoderReg
{
public WGPURenderPassEncoder Native;
}
private sealed class CommandBufferReg
{
public WGPUCommandBuffer Native;
}
}

View File

@@ -1,228 +0,0 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Text;
namespace Robust.Client.Graphics.Rhi.WebGpu;
internal sealed unsafe partial class RhiWebGpu
{
private static string WgpuVersionToString(uint version)
{
var a = (version >> 24) & 0xFF;
var b = (version >> 16) & 0xFF;
var c = (version >> 08) & 0xFF;
var d = (version >> 00) & 0xFF;
return $"{a}.{b}.{c}.{d}";
}
private static WGPUOptionalBool WgpuOptionalBool(bool? value)
{
return value switch
{
null => WGPUOptionalBool.WGPUOptionalBool_Undefined,
false => WGPUOptionalBool.WGPUOptionalBool_False,
true => WGPUOptionalBool.WGPUOptionalBool_True,
};
}
private static WGPUColor WgpuColor(RhiColor color) => new()
{
r = color.R,
g = color.G,
b = color.B,
a = color.A
};
private static WGPUExtent3D WgpuExtent3D(RhiExtent3D extent)
{
return new WGPUExtent3D
{
width = extent.Width,
height = extent.Height,
depthOrArrayLayers = extent.Depth
};
}
private static WGPUOrigin3D WgpuOrigin3D(RhiOrigin3D origin)
{
return new WGPUOrigin3D
{
x = origin.X,
y = origin.Y,
z = origin.Z
};
}
private static string? GetString(WGPUStringView stringView)
{
if (stringView.data == null)
{
if (stringView.length == WGPU_STRLEN)
return null;
if (stringView.length == 0)
return "";
throw new RhiException("Null address to WGPUStringView");
}
if (stringView.length == WGPU_STRLEN)
return Marshal.PtrToStringUTF8((IntPtr)stringView.data);
if (stringView.length > int.MaxValue)
throw new RhiException("WGPUStringView too long!");
var span = new ReadOnlySpan<byte>(stringView.data, (int)stringView.length);
return Encoding.UTF8.GetString(span);
}
private static RhiTextureFormat ToRhiFormat(WGPUTextureFormat format)
{
return (RhiTextureFormat)format;
}
private static WGPUTextureFormat ValidateTextureFormat(RhiTextureFormat format)
{
if (format is 0 or >= RhiTextureFormat.Final)
throw new ArgumentException($"Invalid {nameof(RhiTextureFormat)}");
return (WGPUTextureFormat)format;
}
private static WGPUTextureDimension ValidateTextureDimension(RhiTextureDimension dimension)
{
if (dimension > RhiTextureDimension.Dim3D)
throw new ArgumentException($"Invalid {nameof(RhiTextureDimension)}");
return dimension switch
{
RhiTextureDimension.Dim1D => WGPUTextureDimension.WGPUTextureDimension_1D,
RhiTextureDimension.Dim2D => WGPUTextureDimension.WGPUTextureDimension_2D,
RhiTextureDimension.Dim3D => WGPUTextureDimension.WGPUTextureDimension_3D,
_ => throw new UnreachableException()
};
}
private static RhiTextureUsage ValidateTextureUsage(RhiTextureUsage usage)
{
if (usage >= RhiTextureUsage.Final)
throw new ArgumentException($"Invalid {nameof(RhiTextureUsage)}");
return usage;
}
private static RhiTextureViewDimension ValidateTextureViewDimension(RhiTextureViewDimension dimension)
{
if (dimension >= RhiTextureViewDimension.Final)
throw new ArgumentException($"Invalid {nameof(RhiTextureViewDimension)}");
return dimension;
}
private static RhiTextureAspect ValidateTextureAspect(RhiTextureAspect aspect)
{
if (aspect >= RhiTextureAspect.Final)
throw new ArgumentException($"Invalid {nameof(RhiTextureAspect)}");
return aspect;
}
private static RhiAddressMode ValidateAddressMode(RhiAddressMode addressMode)
{
if (addressMode >= RhiAddressMode.Final)
throw new ArgumentException($"Invalid {nameof(RhiAddressMode)}");
return addressMode;
}
private static RhiFilterMode ValidateFilterMode(RhiFilterMode filterMode)
{
if (filterMode >= RhiFilterMode.Final)
throw new ArgumentException($"Invalid {nameof(RhiFilterMode)}");
return filterMode;
}
private static RhiMipmapFilterMode ValidateMipmapFilterMode(RhiMipmapFilterMode mipmapFilterMode)
{
if (mipmapFilterMode >= RhiMipmapFilterMode.Final)
throw new ArgumentException($"Invalid {nameof(RhiMipmapFilterMode)}");
return mipmapFilterMode;
}
private static RhiCompareFunction ValidateCompareFunction(RhiCompareFunction compareFunction)
{
if (compareFunction >= RhiCompareFunction.Final)
throw new ArgumentException($"Invalid {nameof(RhiCompareFunction)}");
return compareFunction;
}
private static WGPUPowerPreference ValidatePowerPreference(RhiPowerPreference powerPreference)
{
if (powerPreference >= RhiPowerPreference.Final)
throw new ArgumentException($"Invalid {nameof(RhiPowerPreference)}");
return (WGPUPowerPreference) powerPreference;
}
private static string MarshalFromString(byte* str)
{
return Marshal.PtrToStringUTF8((nint)str)!;
}
[return: NotNullIfNotNull(nameof(label))]
private static byte[]? MakeLabel(string? label)
{
// TODO: Replace with stackalloc
if (label == null)
return null;
return Encoding.UTF8.GetBytes(label);
}
private static WGPUStringView BumpAllocateStringView(ref Span<byte> buf, string? str)
{
if (str == null)
return WGPUStringView.Null;
var byteCount = Encoding.UTF8.GetByteCount(str) ;
var ptr = BumpAllocate(ref buf, byteCount);
var dstSpan = new Span<byte>(ptr, byteCount);
Encoding.UTF8.GetBytes(str, dstSpan);
return new WGPUStringView
{
data = (sbyte*)ptr,
length = (nuint)byteCount
};
}
private sealed class WgpuPromise<TResult> : IDisposable
{
private readonly TaskCompletionSource<TResult> _tcs;
private GCHandle _gcHandle;
public Task<TResult> Task => _tcs.Task;
public void* UserData => (void*) GCHandle.ToIntPtr(_gcHandle);
public WgpuPromise()
{
_tcs = new TaskCompletionSource<TResult>();
_gcHandle = GCHandle.Alloc(this);
}
public static void SetResult(void* userdata, TResult result)
{
var self = (WgpuPromise<TResult>)GCHandle.FromIntPtr((nint) userdata).Target!;
self._tcs.SetResult(result);
}
public void Dispose()
{
_gcHandle.Free();
}
}
}

View File

@@ -1,85 +0,0 @@
namespace Robust.Client.Graphics.Rhi.WebGpu;
internal sealed unsafe partial class RhiWebGpu
{
public override RhiQueue Queue { get; }
// queue is ignored as parameter, since WebGPU only supports one queue for now.
internal override void QueueWriteTexture(
RhiQueue queue,
in RhiImageCopyTexture destination,
ReadOnlySpan<byte> data,
in RhiImageDataLayout dataLayout,
RhiExtent3D size)
{
// TODO: Thread safety
var nativeTexture = _textureRegistry[destination.Texture.Handle].Native;
var nativeDestination = new WGPUTexelCopyTextureInfo
{
aspect = (WGPUTextureAspect)ValidateTextureAspect(destination.Aspect),
texture = nativeTexture,
origin = WgpuOrigin3D(destination.Origin),
mipLevel = destination.MipLevel
};
var nativeDataLayout = new WGPUTexelCopyBufferLayout
{
// TODO: Validation
offset = dataLayout.Offset,
bytesPerRow = dataLayout.BytesPerRow,
rowsPerImage = dataLayout.RowsPerImage
};
var nativeSize = WgpuExtent3D(size);
fixed (byte* pData = data)
{
wgpuQueueWriteTexture(
_wgpuQueue,
&nativeDestination,
pData, (nuint) data.Length,
&nativeDataLayout,
&nativeSize
);
}
}
public override void QueueWriteBuffer(RhiBuffer buffer, ulong bufferOffset, ReadOnlySpan<byte> data)
{
var nativeBuffer = _bufferRegistry[buffer.Handle].Native;
fixed (byte* pData = data)
{
wgpuQueueWriteBuffer(
_wgpuQueue,
nativeBuffer,
bufferOffset,
pData,
(nuint) data.Length);
}
}
internal override void QueueSubmit(RhiQueue queue, RhiCommandBuffer[] commandBuffers)
{
// TODO: Safety
var pBuffers = stackalloc WGPUCommandBuffer[commandBuffers.Length];
for (var i = 0; i < commandBuffers.Length; i++)
{
pBuffers[i] = _commandBufferRegistry[commandBuffers[i].Handle].Native;
}
wgpuQueueSubmit(
_wgpuQueue,
(uint) commandBuffers.Length,
pBuffers
);
foreach (var commandBuffer in commandBuffers)
{
CommandBufferDropped(commandBuffer);
}
}
}

View File

@@ -1,8 +0,0 @@
namespace Robust.Client.Graphics.Rhi.WebGpu;
internal sealed partial class RhiWebGpu
{
private long _rhiHandleCounter;
private RhiHandle AllocRhiHandle() => new(Interlocked.Increment(ref _rhiHandleCounter));
}

View File

@@ -1,226 +0,0 @@
using System.Runtime.InteropServices;
namespace Robust.Client.Graphics.Rhi.WebGpu;
internal sealed unsafe partial class RhiWebGpu
{
private readonly Dictionary<RhiHandle, RenderPipelineReg> _renderPipelineRegistry = new();
private readonly Dictionary<RhiHandle, PipelineLayoutReg> _pipelineLayoutRegistry = new();
public override RhiPipelineLayout CreatePipelineLayout(in RhiPipelineLayoutDescriptor descriptor)
{
// TODO: SAFETY
Span<byte> buffer = stackalloc byte[128];
var pDescriptor = BumpAllocate<WGPUPipelineLayoutDescriptor>(ref buffer);
pDescriptor->label = BumpAllocateStringView(ref buffer, descriptor.Label);
var layouts = descriptor.BindGroupLayouts;
pDescriptor->bindGroupLayoutCount = (uint) layouts.Length;
pDescriptor->bindGroupLayouts = BumpAllocatePtr<WGPUBindGroupLayoutImpl>(ref buffer, layouts.Length);
for (var i = 0; i < layouts.Length; i++)
{
pDescriptor->bindGroupLayouts[i] = _bindGroupLayoutRegistry[layouts[i].Handle].Native;
}
var native = wgpuDeviceCreatePipelineLayout(_wgpuDevice, pDescriptor);
// TODO: Thread safety
var handle = AllocRhiHandle();
_pipelineLayoutRegistry.Add(handle, new PipelineLayoutReg { Native = native });
return new RhiPipelineLayout(this, handle);
}
public override RhiRenderPipeline CreateRenderPipeline(in RhiRenderPipelineDescriptor descriptor)
{
// TODO: THREAD SAFETY
// TODO: INPUT VALIDATION
var vertexShader = _shaderModuleRegistry[descriptor.Vertex.ProgrammableStage.ShaderModule.Handle].Native;
const int bufferSize = 8192;
var bufferPtr = NativeMemory.AlignedAlloc(bufferSize, 8);
WGPURenderPipeline nativePipeline;
try
{
var buffer = new Span<byte>(bufferPtr, bufferSize);
WGPURenderPipelineDescriptor pipelineDesc = default;
pipelineDesc.label = BumpAllocateStringView(ref buffer, descriptor.Label);
// Pipeline layout
switch (descriptor.Layout)
{
case RhiPipelineLayout pipelineLayout:
pipelineDesc.layout = _pipelineLayoutRegistry[pipelineLayout.Handle].Native;
break;
case RhiAutoLayoutMode:
throw new NotSupportedException("wgpu does not support auto layout yet");
// Default case: no layout given, do nothing
}
// Vertex state
pipelineDesc.vertex.module = vertexShader;
pipelineDesc.vertex.entryPoint = BumpAllocateStringView(
ref buffer,
descriptor.Vertex.ProgrammableStage.EntryPoint);
WgpuProgrammableConstants(
ref buffer,
descriptor.Vertex.ProgrammableStage.Constants,
out pipelineDesc.vertex.constantCount,
out pipelineDesc.vertex.constants);
var buffers = descriptor.Vertex.Buffers;
pipelineDesc.vertex.bufferCount = (uint)buffers.Length;
pipelineDesc.vertex.buffers = BumpAllocate<WGPUVertexBufferLayout>(ref buffer, buffers.Length);
for (var i = 0; i < buffers.Length; i++)
{
ref var bufferLayout = ref pipelineDesc.vertex.buffers[i];
bufferLayout.arrayStride = buffers[i].ArrayStride;
bufferLayout.stepMode = buffers[i].StepMode == RhiVertexStepMode.Instance
? WGPUVertexStepMode.WGPUVertexStepMode_Instance
: WGPUVertexStepMode.WGPUVertexStepMode_Vertex;
var attributes = buffers[i].Attributes;
bufferLayout.attributeCount = (uint)attributes.Length;
bufferLayout.attributes = BumpAllocate<WGPUVertexAttribute>(ref buffer, attributes.Length);
for (var j = 0; j < attributes.Length; j++)
{
ref var attribute = ref bufferLayout.attributes[j];
attribute.format = (WGPUVertexFormat)attributes[j].Format;
attribute.offset = attributes[j].Offset;
attribute.shaderLocation = attributes[j].ShaderLocation;
}
}
// Primitive state
pipelineDesc.primitive.topology = (WGPUPrimitiveTopology)descriptor.Primitive.Topology;
pipelineDesc.primitive.stripIndexFormat = (WGPUIndexFormat)descriptor.Primitive.StripIndexformat;
pipelineDesc.primitive.frontFace = (WGPUFrontFace)descriptor.Primitive.FrontFace;
pipelineDesc.primitive.cullMode = (WGPUCullMode)descriptor.Primitive.CullMode;
pipelineDesc.primitive.unclippedDepth = descriptor.Primitive.UnclippedDepth ? 1u : 0u;
// Depth stencil state
if (descriptor.DepthStencil is { } depthStencil)
{
var pDepthStencil = BumpAllocate<WGPUDepthStencilState>(ref buffer);
pipelineDesc.depthStencil = pDepthStencil;
pDepthStencil->format = (WGPUTextureFormat)depthStencil.Format;
pDepthStencil->depthWriteEnabled = WgpuOptionalBool(depthStencil.DepthWriteEnabled);
pDepthStencil->depthCompare = (WGPUCompareFunction)depthStencil.DepthCompare;
pDepthStencil->stencilFront = WgpuStencilFaceState(depthStencil.StencilFront ?? new RhiStencilFaceState());
pDepthStencil->stencilBack = WgpuStencilFaceState(depthStencil.StencilBack ?? new RhiStencilFaceState());
pDepthStencil->stencilReadMask = depthStencil.StencilReadMask;
pDepthStencil->stencilWriteMask = depthStencil.StencilWriteMask;
pDepthStencil->depthBias = depthStencil.DepthBias;
pDepthStencil->depthBiasSlopeScale = depthStencil.DepthBiasSlopeScale;
pDepthStencil->depthBiasClamp = depthStencil.DepthBiasClamp;
}
// Multisample state
pipelineDesc.multisample.count = descriptor.Multisample.Count;
pipelineDesc.multisample.mask = descriptor.Multisample.Mask;
pipelineDesc.multisample.alphaToCoverageEnabled = descriptor.Multisample.AlphaToCoverageEnabled ? 1u : 0u;
// Fragment state
if (descriptor.Fragment is { } fragment)
{
var fragmentShader = _shaderModuleRegistry[fragment.ProgrammableStage.ShaderModule.Handle].Native;
var pFragment = BumpAllocate<WGPUFragmentState>(ref buffer);
pipelineDesc.fragment = pFragment;
pFragment->module = fragmentShader;
pFragment->entryPoint = BumpAllocateStringView(ref buffer, fragment.ProgrammableStage.EntryPoint);
WgpuProgrammableConstants(
ref buffer,
fragment.ProgrammableStage.Constants,
out pFragment->constantCount,
out pFragment->constants);
var targets = fragment.Targets;
pFragment->targetCount = (uint)targets.Length;
pFragment->targets = BumpAllocate<WGPUColorTargetState>(ref buffer, targets.Length);
for (var i = 0; i < targets.Length; i++)
{
ref var target = ref pFragment->targets[i];
target.format = (WGPUTextureFormat)targets[i].Format;
if (targets[i].Blend is { } blend)
{
var pBlend = BumpAllocate<WGPUBlendState>(ref buffer);
target.blend = pBlend;
pBlend->alpha = WgpuBlendComponent(blend.Alpha);
pBlend->color = WgpuBlendComponent(blend.Color);
}
target.writeMask = (ulong)targets[i].WriteMask;
}
}
nativePipeline = wgpuDeviceCreateRenderPipeline(_wgpuDevice, &pipelineDesc);
}
finally
{
NativeMemory.AlignedFree(bufferPtr);
}
// TODO: Thread safety
var handle = AllocRhiHandle();
_renderPipelineRegistry.Add(handle, new RenderPipelineReg { Native = nativePipeline });
return new RhiRenderPipeline(this, handle);
}
private static WGPUStencilFaceState WgpuStencilFaceState(in RhiStencilFaceState state)
{
return new WGPUStencilFaceState
{
compare = (WGPUCompareFunction)state.Compare,
failOp = (WGPUStencilOperation)state.FailOp,
depthFailOp = (WGPUStencilOperation)state.DepthFailOp,
passOp = (WGPUStencilOperation)state.PassOp
};
}
private static void WgpuProgrammableConstants(
ref Span<byte> buffer,
RhiConstantEntry[] constants,
out nuint constantCount,
out WGPUConstantEntry* pConstants)
{
constantCount = (uint)constants.Length;
pConstants = BumpAllocate<WGPUConstantEntry>(ref buffer, constants.Length);
for (var i = 0; i < constants.Length; i++)
{
ref var constant = ref pConstants[i];
constant.key = BumpAllocateStringView(ref buffer, constants[i].Key);
constant.value = constants[i].Value;
}
}
private static WGPUBlendComponent WgpuBlendComponent(in RhiBlendComponent component)
{
return new WGPUBlendComponent
{
operation = (WGPUBlendOperation)component.Operation,
dstFactor = (WGPUBlendFactor)component.DstFactor,
srcFactor = (WGPUBlendFactor)component.SrcFactor,
};
}
private sealed class RenderPipelineReg
{
public WGPURenderPipeline Native;
}
private sealed class PipelineLayoutReg
{
public WGPUPipelineLayout Native;
}
}

View File

@@ -1,52 +0,0 @@
namespace Robust.Client.Graphics.Rhi.WebGpu;
internal sealed unsafe partial class RhiWebGpu
{
private readonly Dictionary<RhiHandle, SamplerReg> _samplerRegistry = new();
public override RhiSampler CreateSampler(in RhiSamplerDescriptor descriptor)
{
var addressModeU = ValidateAddressMode(descriptor.AddressModeU);
var addressModeV = ValidateAddressMode(descriptor.AddressModeV);
var addressModeW = ValidateAddressMode(descriptor.AddressModeW);
var magFilter = ValidateFilterMode(descriptor.MagFilter);
var minFilter = ValidateFilterMode(descriptor.MinFilter);
var mipmapFilter = ValidateMipmapFilterMode(descriptor.MipmapFilter);
var compare = ValidateCompareFunction(descriptor.Compare);
WGPUSampler sampler;
fixed (byte* label = MakeLabel(descriptor.Label))
{
var samplerDesc = new WGPUSamplerDescriptor
{
addressModeU = (WGPUAddressMode) addressModeU,
addressModeV = (WGPUAddressMode) addressModeV,
addressModeW = (WGPUAddressMode) addressModeW,
magFilter = (WGPUFilterMode) magFilter,
minFilter = (WGPUFilterMode) minFilter,
mipmapFilter = (WGPUMipmapFilterMode) mipmapFilter,
lodMinClamp = descriptor.LodMinClamp,
lodMaxClamp = descriptor.LodMaxClamp,
compare = (WGPUCompareFunction) compare,
maxAnisotropy = descriptor.MaxAnisotropy,
label = new WGPUStringView
{
data = (sbyte*)label,
length = WGPU_STRLEN
}
};
sampler = wgpuDeviceCreateSampler(_wgpuDevice, &samplerDesc);
}
// TODO: Thread safety
var handle = AllocRhiHandle();
_samplerRegistry.Add(handle, new SamplerReg { Native = sampler });
return new RhiSampler(this, handle);
}
private sealed class SamplerReg
{
public WGPUSampler Native;
}
}

View File

@@ -1,55 +0,0 @@
using System.Text;
namespace Robust.Client.Graphics.Rhi.WebGpu;
internal sealed unsafe partial class RhiWebGpu
{
private readonly Dictionary<RhiHandle, ShaderModuleReg> _shaderModuleRegistry = new();
public override RhiShaderModule CreateShaderModule(in RhiShaderModuleDescriptor descriptor)
{
var codeBytes = Encoding.UTF8.GetBytes(descriptor.Code);
return CreateShaderModule(new RhiShaderModuleDescriptorUtf8
{
Code = codeBytes,
Label = descriptor.Label
});
}
public override RhiShaderModule CreateShaderModule(in RhiShaderModuleDescriptorUtf8 descriptor)
{
WGPUShaderModule shaderModule;
fixed (byte* pCode = descriptor.Code)
fixed (byte* pLabel = MakeLabel(descriptor.Label))
{
var descWgsl = new WGPUShaderSourceWGSL();
descWgsl.code = new WGPUStringView
{
data = (sbyte*)pCode,
length = WGPU_STRLEN
};
descWgsl.chain.sType = WGPUSType.WGPUSType_ShaderSourceWGSL;
var desc = new WGPUShaderModuleDescriptor();
desc.label = new WGPUStringView
{
data = (sbyte*)pLabel,
length = WGPU_STRLEN
};
desc.nextInChain = (WGPUChainedStruct*) (&descWgsl);
shaderModule = wgpuDeviceCreateShaderModule(_wgpuDevice, &desc);
}
// TODO: Thread safety
var handle = AllocRhiHandle();
_shaderModuleRegistry.Add(handle, new ShaderModuleReg { Native = shaderModule });
return new RhiShaderModule(this, handle);
}
private sealed class ShaderModuleReg
{
public WGPUShaderModule Native;
}
}

View File

@@ -1,152 +0,0 @@
using System.Diagnostics;
namespace Robust.Client.Graphics.Rhi.WebGpu;
internal sealed unsafe partial class RhiWebGpu
{
private readonly Dictionary<RhiHandle, TextureReg> _textureRegistry = new();
private readonly Dictionary<RhiHandle, TextureViewReg> _textureViewRegistry = new();
public override RhiTexture CreateTexture(in RhiTextureDescriptor descriptor)
{
var format = descriptor.Format;
var usage = descriptor.Usage;
ValidateTextureFormat(format);
var dimension = ValidateTextureDimension(descriptor.Dimension);
ValidateTextureUsage(usage);
// TODO: Copy to stackalloc instead.
var viewFormats = descriptor.ViewFormats?.ToArray() ?? [];
foreach (var vf in viewFormats)
{
ValidateTextureFormat(vf);
}
Debug.Assert(
sizeof(RhiTextureFormat) == sizeof(WGPUTextureFormat),
"Pointer to view formats array is cast directly to pass to native, sizes must match");
WGPUTexture texturePtr;
var label = MakeLabel(descriptor.Label);
fixed (byte* pLabel = label)
fixed (RhiTextureFormat* pViewFormats = viewFormats)
{
var webGpuDesc = new WGPUTextureDescriptor
{
sampleCount = descriptor.SampleCount,
mipLevelCount = descriptor.MipLevelCount,
dimension = dimension,
format = (WGPUTextureFormat) format,
label = new WGPUStringView
{
data = (sbyte*)pLabel,
length = (UIntPtr)(label?.Length ?? 0),
},
size = WgpuExtent3D(descriptor.Size),
usage = (ulong) usage,
viewFormats = (WGPUTextureFormat*) pViewFormats,
viewFormatCount = checked((uint) viewFormats.Length),
};
texturePtr = wgpuDeviceCreateTexture(_wgpuDevice, &webGpuDesc);
}
if (texturePtr == null)
throw new RhiException("Texture creation failed");
return AllocRhiTexture(texturePtr);
// TODO: Thread safety
var handle = AllocRhiHandle();
_textureRegistry.Add(handle, new TextureReg { Native = texturePtr });
return new RhiTexture(this, handle);
}
internal override RhiTextureView TextureCreateView(RhiTexture texture, in RhiTextureViewDescriptor descriptor)
{
// TODO: Thread safety
var nativeTexture = _textureRegistry[texture.Handle].Native;
var format = ValidateTextureFormat(descriptor.Format);
var dimension = ValidateTextureViewDimension(descriptor.Dimension);
var aspect = ValidateTextureAspect(descriptor.Aspect);
var mipLevelCount = descriptor.MipLevelCount;
var arrayLayerCount = descriptor.ArrayLayerCount;
if (mipLevelCount == 0)
throw new ArgumentException($"Invalid {nameof(descriptor.MipLevelCount)}");
if (arrayLayerCount == 0)
throw new ArgumentException($"Invalid {nameof(descriptor.ArrayLayerCount)}");
WGPUTextureView textureView;
fixed (byte* label = MakeLabel(descriptor.Label))
{
var webGpuDesc = new WGPUTextureViewDescriptor
{
format = (WGPUTextureFormat) format,
dimension = (WGPUTextureViewDimension) dimension,
aspect = (WGPUTextureAspect) aspect,
label = new WGPUStringView
{
data = (sbyte*)label,
length = WGPU_STRLEN
},
baseMipLevel = descriptor.BaseMipLevel,
mipLevelCount = mipLevelCount,
baseArrayLayer = descriptor.BaseArrayLayer,
arrayLayerCount = descriptor.ArrayLayerCount
};
textureView = wgpuTextureCreateView(nativeTexture, &webGpuDesc);
}
return AllocRhiTextureView(textureView);
}
internal override void TextureViewDrop(RhiTextureView textureView)
{
wgpuTextureViewRelease(_textureViewRegistry[textureView.Handle].Native);
_textureViewRegistry.Remove(textureView.Handle);
}
internal override RhiTexture GetSurfaceTextureForWindow(WindowData reg)
{
// TODO: Thread safety
var surface = reg.Surface;
// This creates a new texture view handle.
WGPUSurfaceTexture textureRet;
wgpuSurfaceGetCurrentTexture(surface, &textureRet);
return AllocRhiTexture(textureRet.texture);
}
private RhiTexture AllocRhiTexture(WGPUTexture native)
{
// TODO: Thread safety
var handle = AllocRhiHandle();
_textureRegistry.Add(handle, new TextureReg { Native = native });
return new RhiTexture(this, handle);
}
private RhiTextureView AllocRhiTextureView(WGPUTextureView native)
{
// TODO: Thread safety
var handle = AllocRhiHandle();
_textureViewRegistry.Add(handle, new TextureViewReg { Native = native });
return new RhiTextureView(this, handle);
}
private sealed class TextureReg
{
public WGPUTexture Native;
}
private sealed class TextureViewReg
{
public WGPUTextureView Native;
}
}

View File

@@ -1,171 +0,0 @@
using Robust.Shared.Maths;
namespace Robust.Client.Graphics.Rhi.WebGpu;
internal sealed unsafe partial class RhiWebGpu
{
private RhiTextureFormat _mainTextureFormat;
private WGPUPresentMode[] _availPresentModes = [];
public override RhiTextureFormat MainTextureFormat => _mainTextureFormat;
public sealed class WindowData
{
public WGPUSurface Surface;
}
private WindowData CreateSurfaceForWindow(in RhiWindowSurfaceParams surfaceParams)
{
WGPUSurfaceDescriptor surfaceDesc = default;
#if WINDOWS
var surfaceDescHwnd = new WGPUSurfaceSourceWindowsHWND
{
chain =
{
sType = WGPUSType.WGPUSType_SurfaceSourceWindowsHWND
},
hinstance = surfaceParams.HInstance,
hwnd = surfaceParams.HWnd,
};
surfaceDesc.nextInChain = (WGPUChainedStruct*)(&surfaceDescHwnd);
#elif MACOS
var surfaceDescMetal = new WGPUSurfaceSourceMetalLayer
{
chain =
{
sType = WGPUSType.WGPUSType_SurfaceSourceMetalLayer
},
layer = surfaceParams.MetalLayer
};
surfaceDesc.nextInChain = (WGPUChainedStruct*)(&surfaceDescMetal);
#elif LINUX
WGPUSurfaceSourceWaylandSurface surfaceDescWayland;
WGPUSurfaceSourceXlibWindow surfaceDescX11;
if (surfaceParams.Wayland)
{
surfaceDescWayland = new WGPUSurfaceSourceWaylandSurface
{
chain =
{
sType = WGPUSType.WGPUSType_SurfaceSourceWaylandSurface
},
display = surfaceParams.WaylandDisplay,
surface = surfaceParams.WaylandSurface,
};
surfaceDesc.nextInChain = (WGPUChainedStruct*)(&surfaceDescWayland);
}
else
{
surfaceDescX11 = new WGPUSurfaceSourceXlibWindow()
{
chain =
{
sType = WGPUSType.WGPUSType_SurfaceSourceXlibWindow
},
display = surfaceParams.X11Display,
// TODO "Oh my god x11 support might be a nightmare this is outside of your ability to deal with -pjb"
// window = surfaceParams.X11Window,
};
surfaceDesc.nextInChain = (WGPUChainedStruct*)(&surfaceDescX11);
}
#endif
var surface = wgpuInstanceCreateSurface(_wgpuInstance, &surfaceDesc);
return new WindowData
{
Surface = surface
};
}
private void DecideMainTextureFormat(WindowData mainWindow)
{
WGPUSurfaceCapabilities surfaceCaps;
var res = wgpuSurfaceGetCapabilities(mainWindow.Surface, _wgpuAdapter, &surfaceCaps);
if (res != WGPUStatus.WGPUStatus_Success)
throw new RhiException("wgpuSurfaceGetCapabilities failed");
var modes = new Span<WGPUPresentMode>(surfaceCaps.presentModes, (int)surfaceCaps.presentModeCount);
_availPresentModes = modes.ToArray();
_sawmill.Debug($"Available present modes: {string.Join(", ", _availPresentModes)}");
var formats = new Span<WGPUTextureFormat>(surfaceCaps.formats, (int)surfaceCaps.formatCount);
var found = false;
foreach (var format in formats)
{
if (format == WGPUTextureFormat.WGPUTextureFormat_BGRA8UnormSrgb ||
format == WGPUTextureFormat.WGPUTextureFormat_RGBA8UnormSrgb)
{
found = true;
_mainTextureFormat = ToRhiFormat(format);
break;
}
}
_sawmill.Debug($"Available surface formats: {string.Join(", ", formats.ToArray())}");
if (!found)
throw new RhiException("Unable to find suitable surface format for main window!");
_sawmill.Debug($"Preferred surface format is {_mainTextureFormat}");
wgpuSurfaceCapabilitiesFreeMembers(surfaceCaps);
}
private void ConfigureSurface(WindowData window, Vector2i size, bool vsync)
{
var swapChainDesc = new WGPUSurfaceConfiguration
{
format = ValidateTextureFormat(_mainTextureFormat),
width = (uint)size.X,
height = (uint)size.Y,
usage = WGPUTextureUsage_RenderAttachment,
presentMode = WGPUPresentMode.WGPUPresentMode_Fifo,
device = _wgpuDevice
};
if (!vsync)
{
if (_availPresentModes.Contains(WGPUPresentMode.WGPUPresentMode_Immediate))
swapChainDesc.presentMode = WGPUPresentMode.WGPUPresentMode_Immediate;
else if (_availPresentModes.Contains(WGPUPresentMode.WGPUPresentMode_Mailbox))
swapChainDesc.presentMode = WGPUPresentMode.WGPUPresentMode_Mailbox;
}
wgpuSurfaceConfigure(window.Surface, &swapChainDesc);
_sawmill.Verbose("WebGPU Surface reconfigured!");
}
internal override WindowData WindowCreated(in RhiWindowSurfaceParams surfaceParams, Vector2i size, bool vsync)
{
var windowData = CreateSurfaceForWindow(in surfaceParams);
ConfigureSurface(windowData, size, vsync);
return windowData;
}
internal override void WindowDestroy(WindowData reg)
{
wgpuSurfaceUnconfigure(reg.Surface);
wgpuSurfaceRelease(reg.Surface);
}
internal override void WindowRecreateSwapchain(WindowData reg, Vector2i size, bool vsyncEnabled)
{
ConfigureSurface(reg, size, vsyncEnabled);
}
internal override void WindowPresent(WindowData reg)
{
// TODO: Safety
wgpuSurfacePresent(reg.Surface);
}
}

View File

@@ -1,335 +0,0 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
using RLogLevel = Robust.Shared.Log.LogLevel;
#pragma warning disable CS8500
namespace Robust.Client.Graphics.Rhi.WebGpu;
internal sealed unsafe partial class RhiWebGpu : RhiBase
{
private readonly ISawmill _sawmill;
private readonly ISawmill _apiLogSawmill;
private WGPUInstance _wgpuInstance;
private WGPUAdapter _wgpuAdapter;
private WGPUDevice _wgpuDevice;
private WGPUQueue _wgpuQueue;
private RhiLimits? _deviceLimits;
private RhiAdapterInfo? _adapterProperties;
private string _description = "not initialized";
public override RhiLimits DeviceLimits =>
_deviceLimits ?? throw new InvalidOperationException("Not initialized yet");
public override RhiAdapterInfo AdapterInfo =>
_adapterProperties ?? throw new InvalidOperationException("Not initialized yet");
public override string Description => _description;
public RhiWebGpu(IDependencyCollection dependencies)
{
var logMgr = dependencies.Resolve<ILogManager>();
_sawmill = logMgr.GetSawmill("clyde.rhi.webGpu");
_apiLogSawmill = logMgr.GetSawmill("clyde.rhi.webGpu.apiLog");
Queue = new RhiQueue(this, default);
}
internal override void Init(in RhiInitParams initParams, out WindowData windowData)
{
_sawmill.Info("Initializing WebGPU RHI!");
InitInstance(in initParams);
windowData = CreateSurfaceForWindow(in initParams.MainWindowSurfaceParams);
_sawmill.Debug("WebGPU main surface created!");
InitAdapterAndDevice(in initParams, windowData.Surface);
DecideMainTextureFormat(windowData);
}
private void InitInstance(in RhiInitParams initParams)
{
var wgpuVersion = WgpuVersionToString(wgpuGetVersion());
_sawmill.Debug($"wgpu-native loaded, version: {wgpuVersion}");
_description = $"WebGPU (wgpu-native {wgpuVersion})";
InitLogging();
Span<byte> buffer = stackalloc byte[128];
var pInstanceDescriptor = BumpAllocate<WGPUInstanceDescriptor>(ref buffer);
// Specify instance extras for wgpu-native.
var pInstanceExtras = BumpAllocate<WGPUInstanceExtras>(ref buffer);
pInstanceDescriptor->nextInChain = (WGPUChainedStruct*)pInstanceExtras;
pInstanceExtras->chain.sType = (WGPUSType)WGPUNativeSType.WGPUSType_InstanceExtras;
pInstanceExtras->backends = (uint)GetInstanceBackendCfg(initParams.Backends);
_wgpuInstance = wgpuCreateInstance(pInstanceDescriptor);
_sawmill.Debug("WebGPU instance created!");
}
private ulong GetInstanceBackendCfg(string backendCvar)
{
if (backendCvar == "all")
return WGPUInstanceBackend_Primary | WGPUInstanceBackend_Secondary;
var backends = 0ul;
foreach (var opt in backendCvar.Split(","))
{
backends |= opt switch
{
"vulkan" => WGPUInstanceBackend_Vulkan,
"gl" => WGPUInstanceBackend_GL,
"metal" => WGPUInstanceBackend_Metal,
"dx12" => WGPUInstanceBackend_DX12,
"dx11" => WGPUInstanceBackend_DX11,
"browser" => WGPUInstanceBackend_BrowserWebGPU,
_ => throw new ArgumentException($"Unknown wgpu backend: '{opt}'")
};
}
return backends;
}
private void InitAdapterAndDevice(in RhiInitParams initParams, WGPUSurface forSurface)
{
var powerPreference = ValidatePowerPreference(initParams.PowerPreference);
var requestAdapterOptions = new WGPURequestAdapterOptions
{
compatibleSurface = forSurface,
powerPreference = powerPreference
};
WgpuRequestAdapterResult result;
wgpuInstanceRequestAdapter(
_wgpuInstance,
&requestAdapterOptions,
new WGPURequestAdapterCallbackInfo
{
callback = &WgpuRequestAdapterCallback,
userdata1 = &result,
});
if (result.Status != WGPURequestAdapterStatus.WGPURequestAdapterStatus_Success)
throw new RhiException($"Adapter request failed: {result.Message}");
_sawmill.Debug("WebGPU adapter created!");
_wgpuAdapter = result.Adapter.P;
WGPUAdapterInfo adapterProps = default;
wgpuAdapterGetInfo(_wgpuAdapter, &adapterProps);
WGPULimits adapterLimits = default;
wgpuAdapterGetLimits(_wgpuAdapter, &adapterLimits);
_sawmill.Debug($"adapter device: {GetString(adapterProps.device)}");
_sawmill.Debug($"adapter vendor: {GetString(adapterProps.vendor)} ({adapterProps.vendorID})");
_sawmill.Debug($"adapter description: {GetString(adapterProps.description)}");
_sawmill.Debug($"adapter architecture: {GetString(adapterProps.architecture)}");
_sawmill.Debug($"adapter backend: {adapterProps.backendType}");
_sawmill.Debug($"adapter type: {adapterProps.adapterType}");
_sawmill.Debug($"adapter UBO alignment: {adapterLimits.minUniformBufferOffsetAlignment}");
_adapterProperties = new RhiAdapterInfo(
adapterProps.vendorID,
adapterProps.deviceID,
GetString(adapterProps.vendor) ?? "",
GetString(adapterProps.architecture) ?? "",
GetString(adapterProps.device) ?? "",
GetString(adapterProps.description) ?? "",
(RhiAdapterType) adapterProps.adapterType,
(RhiBackendType) adapterProps.backendType
);
_description += $", backend: {_adapterProperties.BackendType}";
// Default limits, from WebGPU spec.
var requiredLimits = new WGPULimits();
if (false)
{
// GLES3.0
requiredLimits.maxComputeWorkgroupStorageSize = 16384;
requiredLimits.maxComputeInvocationsPerWorkgroup = 256;
requiredLimits.maxComputeWorkgroupSizeX = 256;
requiredLimits.maxComputeWorkgroupSizeY = 256;
requiredLimits.maxComputeWorkgroupSizeZ = 256;
requiredLimits.maxComputeWorkgroupsPerDimension = 65536;
requiredLimits.maxDynamicStorageBuffersPerPipelineLayout = 0;
requiredLimits.maxStorageBuffersPerShaderStage = 4;
requiredLimits.maxStorageBufferBindingSize = 134217728;
}
// Required minimums
requiredLimits.minStorageBufferOffsetAlignment = 256;
requiredLimits.minUniformBufferOffsetAlignment = 256;
requiredLimits.maxTextureArrayLayers = 256;
requiredLimits.maxBindGroups = 4;
requiredLimits.maxBindingsPerBindGroup = 1000;
requiredLimits.maxDynamicUniformBuffersPerPipelineLayout = 8;
requiredLimits.maxSampledTexturesPerShaderStage = 16;
requiredLimits.maxSamplersPerShaderStage = 16;
requiredLimits.maxUniformBuffersPerShaderStage = 12;
requiredLimits.maxUniformBufferBindingSize = 65536;
requiredLimits.maxVertexBuffers = 8;
requiredLimits.maxVertexAttributes = 16;
requiredLimits.maxVertexBufferArrayStride = 2048;
requiredLimits.maxInterStageShaderVariables = 16;
requiredLimits.maxColorAttachments = 8;
requiredLimits.maxColorAttachmentBytesPerSample = 32;
requiredLimits.maxBufferSize = 268435456;
// Custom limits
// Take as low UBO alignment as we can get.
requiredLimits.minUniformBufferOffsetAlignment = adapterLimits.minUniformBufferOffsetAlignment;
// Take as large textures as we can get.
requiredLimits.maxTextureDimension1D = adapterLimits.maxTextureDimension1D;
requiredLimits.maxTextureDimension2D = adapterLimits.maxTextureDimension2D;
requiredLimits.maxTextureDimension3D = adapterLimits.maxTextureDimension3D;
// TODO: clear this.
var errorGCHandle = GCHandle.Alloc(this);
var deviceDesc = new WGPUDeviceDescriptor();
deviceDesc.requiredLimits = &requiredLimits;
deviceDesc.uncapturedErrorCallbackInfo = new WGPUUncapturedErrorCallbackInfo
{
callback = &UncapturedErrorCallback,
userdata1 = (void*)GCHandle.ToIntPtr(errorGCHandle),
};
WgpuRequestDeviceResult deviceResult;
wgpuAdapterRequestDevice(
_wgpuAdapter,
&deviceDesc,
new WGPURequestDeviceCallbackInfo
{
callback = &WgpuRequestDeviceCallback,
userdata1 = &deviceResult
});
if (deviceResult.Status != WGPURequestDeviceStatus.WGPURequestDeviceStatus_Success)
throw new Exception($"Device request failed: {deviceResult.Message}");
_sawmill.Debug("WebGPU device created!");
_wgpuDevice = deviceResult.Device;
_wgpuQueue = wgpuDeviceGetQueue(_wgpuDevice);
_deviceLimits = new RhiLimits(
requiredLimits.maxTextureDimension1D,
requiredLimits.maxTextureDimension2D,
requiredLimits.maxTextureDimension3D,
requiredLimits.maxTextureArrayLayers,
requiredLimits.maxBindGroups,
requiredLimits.maxBindingsPerBindGroup,
requiredLimits.maxDynamicUniformBuffersPerPipelineLayout,
requiredLimits.maxDynamicStorageBuffersPerPipelineLayout,
requiredLimits.maxSampledTexturesPerShaderStage,
requiredLimits.maxSamplersPerShaderStage,
requiredLimits.maxStorageBuffersPerShaderStage,
requiredLimits.maxStorageTexturesPerShaderStage,
requiredLimits.maxUniformBuffersPerShaderStage,
requiredLimits.maxUniformBufferBindingSize,
requiredLimits.maxStorageBufferBindingSize,
requiredLimits.minUniformBufferOffsetAlignment,
requiredLimits.minStorageBufferOffsetAlignment,
requiredLimits.maxVertexBuffers,
requiredLimits.maxBufferSize,
requiredLimits.maxVertexAttributes,
requiredLimits.maxVertexBufferArrayStride,
requiredLimits.maxInterStageShaderVariables,
requiredLimits.maxColorAttachments,
requiredLimits.maxColorAttachmentBytesPerSample,
requiredLimits.maxComputeWorkgroupStorageSize,
requiredLimits.maxComputeInvocationsPerWorkgroup,
requiredLimits.maxComputeWorkgroupSizeX,
requiredLimits.maxComputeWorkgroupSizeY,
requiredLimits.maxComputeWorkgroupSizeZ,
requiredLimits.maxComputeWorkgroupsPerDimension
);
}
private void InitLogging()
{
// TODO: clear this.
var gcHandle = GCHandle.Alloc(this);
wgpuSetLogCallback(&LogCallback, (void*)GCHandle.ToIntPtr(gcHandle));
wgpuSetLogLevel(WGPULogLevel.WGPULogLevel_Warn);
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void LogCallback(WGPULogLevel level, WGPUStringView message, void* userdata)
{
var self = (RhiWebGpu)GCHandle.FromIntPtr((nint)userdata).Target!;
var messageString = GetString(message)!;
var robustLevel = level switch
{
WGPULogLevel.WGPULogLevel_Error => RLogLevel.Error,
WGPULogLevel.WGPULogLevel_Warn => RLogLevel.Warning,
WGPULogLevel.WGPULogLevel_Info => RLogLevel.Info,
WGPULogLevel.WGPULogLevel_Debug => RLogLevel.Debug,
WGPULogLevel.WGPULogLevel_Trace or _ => RLogLevel.Verbose,
};
self._apiLogSawmill.Log(robustLevel, messageString);
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void UncapturedErrorCallback(WGPUDevice* device, WGPUErrorType level, WGPUStringView message, void* userdata1, void* userdata2)
{
var self = (RhiWebGpu)GCHandle.FromIntPtr((nint)userdata1).Target!;
var messageString = GetString(message);
self._apiLogSawmill.Error(messageString ?? "Unknown error");
}
internal override void Shutdown()
{
}
private record struct WgpuRequestAdapterResult(WGPURequestAdapterStatus Status, Ptr<WGPUAdapterImpl> Adapter, string Message);
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void WgpuRequestAdapterCallback(
WGPURequestAdapterStatus status,
WGPUAdapter adapter,
WGPUStringView message,
void* userdata1,
void* userdata2)
{
*(WgpuRequestAdapterResult*)userdata1 = new WgpuRequestAdapterResult(
status,
adapter,
GetString(message) ?? "");
}
private record struct WgpuRequestDeviceResult(WGPURequestDeviceStatus Status, Ptr<WGPUDeviceImpl> Device, string Message);
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void WgpuRequestDeviceCallback(
WGPURequestDeviceStatus status,
WGPUDevice device,
WGPUStringView message,
void* userdata1,
void* userdata2)
{
*(WgpuRequestDeviceResult*)userdata1 = new WgpuRequestDeviceResult(
status,
device,
GetString(message) ?? "");
}
}

View File

@@ -19,8 +19,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="17.8.3" />
<PackageReference Include="Mono.Cecil" Version="0.11.5" />
<PackageReference Include="Microsoft.Build.Framework" Version="18.0.2" />
<PackageReference Include="Mono.Cecil" Version="0.11.6" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\MSBuild\Robust.Engine.props"/>
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>../bin/Client.IntegrationTests</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations"/>
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
<PackageReference Include="Moq" />
<PackageReference Include="NUnit"/>
<PackageReference Include="NUnit3TestAdapter"/>
<PackageReference Include="NUnit.Analyzers"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NetSerializer\NetSerializer\NetSerializer.csproj" />
<ProjectReference Include="..\Robust.Client\Robust.Client.csproj" />
<ProjectReference Include="..\Robust.Server.Testing\Robust.Server.Testing.csproj" />
<ProjectReference Include="..\Robust.Server\Robust.Server.csproj" />
<ProjectReference Include="..\Robust.Shared.Maths\Robust.Shared.Maths.csproj"/>
<ProjectReference Include="..\Robust.Shared.Maths.Testing\Robust.Shared.Maths.Testing.csproj"/>
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj"/>
<ProjectReference Include="..\Robust.UnitTesting\Robust.UnitTesting.csproj" />
</ItemGroup>
<Import Project="..\MSBuild\Robust.Properties.targets"/>
</Project>

View File

@@ -2,7 +2,6 @@ using NUnit.Framework;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
using Robust.UnitTesting.Server;
namespace Robust.UnitTesting.Client.UserInterface.Controls
{

View File

@@ -1,5 +0,0 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Robust.Client")]
[assembly: InternalsVisibleTo("Robust.Client.Graphics.Rhi")]
[module: SkipLocalsInit]

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