Compare commits

...

672 Commits

Author SHA1 Message Date
ElectroJr
796abe1230 Version: 223.1.2 2024-05-25 18:33:46 +12:00
KIBORG04
6c2cf26250 Sync LastTileModifiedTick in MapGridComponent with the client (#5169)
* i think

* forgot

* Add lastTileModifiedTick to MapGridComponentDeltaState

* A
2024-05-25 11:05:40 +10:00
Vasilis
ce262d5ff8 Include a bat to launch the server (#5167) 2024-05-24 18:32:26 +02:00
ElectroJr
c250010dad Version: 223.1.1 2024-05-24 18:58:03 +12:00
ElectroJr
709c7bc808 Fix cvar type cast 2024-05-24 18:57:46 +12:00
ElectroJr
37918da73c Version: 223.1.0 2024-05-24 18:44:51 +12:00
Leon Friedrich
6cc2083b09 Move some EntityCoordinates methods to TransformSystem (#5136)
* Move `EntityCoordinate` methods to `TransformSystem`

* TryGetInterfaceData

* Update IsInSameOrParentContainer

* Don't broadcast BoundUserInterfaceCheckRangeEvent

* Changelog

* Defer fetching component

* Re-add obsoleted methods

* Add obsolete container methods

* update release notes

* Add InRange()

* Make error logging optional
2024-05-24 16:44:17 +10:00
ElectroJr
6a6bfe33ca Version: 223.0.0 2024-05-24 16:10:00 +12:00
Leon Friedrich
9737a4249c Rework delta-states (#5149)
* Remove full "delta" states

* Update MapGridComponentState

* abstract ComponentState

* Release notes

* Fix tests

* Fix nullable errors

* A

* typo

* Turns out testing my own PRs is a good idea

* too many warnings
2024-05-24 14:08:41 +10:00
Leon Friedrich
a48a353939 Add IConfigurationManager.OnCvarValueChanged event (#5161)
* Add `IConfigurationManager.OnCvarValueChanged` event

* Prevent CVars from changing type

* Allow double registrations?

* Turn debug asserts into exceptions

* We should really just start using generics if we can

* Re-use CVarChangeInfo

* Explicit old value

* Rename `OnCvarValueChanged` to `OnCVarValueChanged`

* internal constructor
2024-05-24 14:04:56 +10:00
DrSmugleaf
f0b45d95cb Fix serialization source generator error when multiple partials of the same definition are user-defined (#5160) 2024-05-23 22:48:40 +02:00
Amy
d69c5500f2 better compat getpixel (#5162)
Co-authored-by: amylizzle <amylizzle@users.noreply.github.com>
2024-05-23 22:47:55 +02:00
Pieter-Jan Briers
cf133ca341 Update more URLs to new infra.
Mostly auth but also a reference to the old hub URL.
2024-05-23 17:40:43 +02:00
Tom Leys
b0922b8e0e perf: Budget less memory for Replay Checkpoints (#28052) (#5145)
* perf: Replays use less memory for checkpoints (#28052)

- Simple change of the CVars and some stats
- Based on a Lizard replay, checkpoints move from on average every 70 ticks to every 350.

* Set a minimum number of ticks that must pass between checkpoints

* Fix stat collection, split _checkpointMinInterval, more CheckpointState

* update release notes
2024-05-23 15:35:10 +10:00
AJCM-git
512ebd8422 LineEdit placeholder tweaks (#5153)
* LineEdit focus tweaks

* Doing it the correct way

* Reviews
2024-05-21 22:34:45 +02:00
Leon Friedrich
85f74c3ba3 Fix GetWorldViewbounds() and GetWorldViewport() (#5060)
* Fix GetWorldViewbounds() and GetWorldViewport()

* Remove some uses of `CurrentMap` and `CurrentEye`
2024-05-19 10:47:04 +10:00
Tayrtahn
da7abc6580 Add analyzer and fixer for redundant DataField tag arguments (#5134)
* Add analyzer and fixer for redundant DataField tag arguments

* Share Tag autogeneration logic
2024-05-17 07:44:03 +02:00
Vasilis
b1329d30bf Dont print watchdog token eletric boogaloo (#5143) 2024-05-17 07:15:30 +02:00
Джексон Миссиссиппи
12808d073e Make CVar RenderFOVColor settable by server only (#5142)
* | CVar.SERVER

* Update CVars.cs
2024-05-17 07:15:17 +02:00
Pieter-Jan Briers
ec794ce4e4 Version: 222.4.0 2024-05-17 02:51:44 +02:00
metalgearsloth
6b13475842 Revert "Add physics delta states (#5116)" (#5144)
This reverts commit 1189613908.
2024-05-17 02:21:05 +02:00
Pieter-Jan Briers
b48ee22800 Add more System.Numerics types to sandbox. 2024-05-16 22:25:13 +02:00
Pieter-Jan Briers
0b95a4edeb Version: 222.3.0 2024-05-16 20:14:37 +02:00
Pieter-Jan Briers
ed359481b4 We can't expect god to do the release notes. 2024-05-16 20:14:18 +02:00
metalgearsloth
1189613908 Add physics delta states (#5116)
* Add physics delta states

Significantly cuts down on data being sent + should make client state handling faster.

* Update Robust.Shared/Physics/Components/PhysicsComponentState.cs
2024-05-16 20:09:25 +02:00
DrSmugleaf
30907d8415 Fix ordered subscriptions not working when targeting a parent system type (#5135)
* Fix ordered subscriptions not working when targeting a parent system type

* Fix missing usages of expand ordering

* Extract method
2024-05-16 20:00:15 +02:00
Leon Friedrich
7f2da4d4f3 Fix paused entities not updating on prototype reload (#5128) 2024-05-16 19:06:11 +02:00
Ed
e30e963623 Hidden tiles (#5102)
* hidden tiles

* Update TileSpawningUIController.cs

* Update TileSpawningUIController.cs

* Update ITileDefinition.cs

* Update TileSpawningUIController.cs

* Move EditorHidden where clause out

* Make EditorHidden a DIM

So there's no breaking change

* Release notes.

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-05-16 19:05:39 +02:00
metalgearsloth
b056caeed7 Fix cross-map BUIs (#5115)
Even the ignore range bit is going to break with pvs but uhh not sure on that one unless we do overrides or something.
2024-05-16 19:00:11 +02:00
Leon Friedrich
fbc8086335 Don't iterate over component events when removing components (#5138)
* Don't iterate over component events when removing components

* Welp nevermind, forgot about tests

* A

* AAAAAA

* AAAA
2024-05-16 18:53:33 +02:00
Pieter-Jan Briers
799702b814 Work against .NET SDK update ManagePackageVersionsCentrally change
.NET SDK 8.0.300 changed ManagePackageVersionsCentrally to be implicitly set if Directory.Packages.props exists. We do not want this, as we intentionally have some projects that have it disabled.

We now explicitly unset the value in the Directory.Packages.props file to get the old behavior back.

See https://github.com/dotnet/core/issues/9309
2024-05-15 21:02:35 +02:00
ElectroJr
63df90f86f Version: 222.2.0 2024-05-14 23:01:41 +12:00
Vasilis
51f0c60bd3 Do not log wrong and correct watchdog token into info logs (#5133)
* Lets not do this?

* Webedit 2
2024-05-14 02:12:24 +02:00
metalgearsloth
a9ed53f47b Run BUI range checks in parallel (#5118)
These still take almost half-ms in server tick time on live as it has to do a raycast for everyone that has storage or their PDA open or whatever. Haven't benchmarked with a lot of clients but easiest way to tell is just check grafanaTM and I'm not sure how to check this locally.
2024-05-14 09:03:26 +10:00
Amy
41c40f1a94 Fix checking wrong property in TryGetVariableType() (#5120)
Co-authored-by: amylizzle <amylizzle@users.noreply.github.com>
2024-05-14 08:44:19 +10:00
deltanedas
6e61c35d35 add missing Comp inline to EntityQuery (#5123)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-05-14 08:43:03 +10:00
ShadowCommander
aae0a8bc51 Add doc comments to CreateEntityUninitialized (#5131) 2024-05-14 08:42:34 +10:00
DrSmugleaf
cb543240c6 Fix clients mispredicting a character's gender (#5119)
* Fix clients mispredicting a character's gender

* Allow nullable value in set
2024-05-12 22:20:21 -07:00
metalgearsloth
1654ab06f5 Revert "Add audio stream name to entity name" (#5127)
This reverts commit d2a2afe82e.
2024-05-13 10:53:30 +10:00
Brandon Li
211245215e remove XAMLIL after generating populate method (#5126) 2024-05-13 01:34:32 +02:00
ShadowCommander
10aaaa65c5 Add editorconfig wrapping settings (#5125) 2024-05-12 17:50:19 +02:00
ShadowCommander
d2a2afe82e Add audio stream name to entity name (#5121) 2024-05-10 17:58:38 +02:00
Pieter-Jan Briers
025d90d281 Change build upload server to Suns 2024-05-09 09:48:49 +02:00
metalgearsloth
c229f2e312 Fix valuelist ensurecapacity(0) throwing (#5113)
_items is still null so.
2024-05-08 20:46:45 +02:00
metalgearsloth
fe051a3577 Minor bui thing (#5114) 2024-05-08 19:59:13 +10:00
DrSmugleaf
51a0ef1e60 Fix error when JointRelayTargetComponent shuts down while applying state (#5110) 2024-05-08 19:21:55 +10:00
DrSmugleaf
702dfef5fc Version: 222.1.1 2024-05-07 22:28:07 -07:00
DrSmugleaf
a0c1ad246f Fix never setting BoundUserInterface.State (#5111)
* Fix never setting BoundUserInterface.State

* Make setter internal
2024-05-07 20:51:26 -07:00
metalgearsloth
1153888bd1 Add truncate for filesaving (#5098)
* Add truncate for filesaving

If I expose it to content I pretty much always want truncate to be honest.

* Update Robust.Client/UserInterface/FileDialogManager.cs

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2024-05-07 00:58:57 +02:00
Amy
ccbb6ddec7 Add method for getting type of var (#5070)
Co-authored-by: amylizzle <amylizzle@users.noreply.github.com>
2024-05-06 20:39:32 +02:00
metalgearsloth
970da5f717 Version: 222.1.0 2024-05-06 13:43:11 +10:00
Pieter-Jan Briers
4d528dd577 Analyzer to ban uncached regexes (#5107)
Using static Regex functions that take in a pattern is bad, because they constantly have to be re-parsed. Cache the Regex instance.
2024-05-06 10:30:31 +10:00
Pieter-Jan Briers
c83720b163 Update Lidgren to v0.3.1 2024-05-06 01:55:05 +02:00
Pieter-Jan Briers
bd87a805d4 Add CVars to turn Lidgren's error/warning logs off.
Combined with upcoming Lidgren changes, this should make DDoS-induced warning log spam not cause huge server perf issues anymore.
2024-05-06 01:47:27 +02:00
Leon Friedrich
fff42fb2b4 Partially fix UI assert (#5100)
* Partially fix UI assert

* Avoid breaking change in BoundKeyEventArgs

This is a public constructor, as much as it maybe shouldn't be. Adding this parameter is a breaking change.

* Replace .Disposed checks with ! .VisibleInTree

Control disposal should not be used anymore.

* Release notes

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-05-06 01:29:34 +02:00
metalgearsloth
4500669f65 Version: 222.0.0 2024-05-06 08:54:19 +10:00
Pieter-Jan Briers
7d19ea9338 Fix compiler error from merges
EntProtoId PR was incompatible with the PR to change EntityPrototype methods to require IComponentFactory passed in
2024-05-06 00:32:54 +02:00
Leon Friedrich
2dc610907d Make IComponentFactory argument in EntityPrototype mandatory (#5101) 2024-05-05 23:00:10 +02:00
DrSmugleaf
beb1c4b1fb Add EntProtoId<T> (#5097)
* Add EntProtoId<T>

* Fix error messages

* Shorten error messages

* Make services non-optional
2024-05-05 22:59:45 +02:00
metalgearsloth
7e331eaa75 Defer clientside BUI opens (#5073)
* Defer clientside BUI opens

Needs content fix first as storage UI breaks.

* tweaks

* Re-revert this because it seems needed
2024-05-03 12:58:19 +10:00
Leon Friedrich
caf9e45ad9 Fix PVS iterating over duplicate chunks when a a client has multiple viewers/eyes (#5094) 2024-05-03 05:52:56 +10:00
metalgearsloth
7cb3aeccc2 Version: 221.2.0 2024-05-02 12:20:54 +10:00
metalgearsloth
ae83e606d6 Add SetWorldRotNoLerp method (#5091)
* Add SetWorldRotNoLerp method

I neeeeed it.

* Also this one

* dum
2024-05-02 12:14:27 +10:00
metalgearsloth
d9d5ef7471 Add audio helpers for map-based audio (#5086)
Doesn't need to be a flag because we just set it as global, whereas gridaudio cares about stuff every frame.
2024-05-02 09:51:14 +10:00
Pieter-Jan Briers
0f97f366a6 Copy CopyToShaderParameters in SpriteComponent.CopyFrom.
Fixes dragging displacement-mapped mobs in SS14 making the displacement map visible.
2024-05-01 23:45:58 +02:00
Jezithyr
35ab0b8cc8 Version: 221.1.0 2024-04-30 12:51:22 -07:00
metalgearsloth
5a14e939bf TileChangedEvent bool (#5089)
Shows whether IsEmpty is different, useful in circumstances.

Also NotNullWhen null handling consistency.
2024-04-30 07:28:29 -07:00
T-Stalker
ccba6b5d1c Reduce default sound range to 15 (#5085) 2024-04-30 15:41:00 +10:00
DrSmugleaf
254a5987c7 Fix Array.Resize sandbox signature (#5084) 2024-04-30 02:14:56 +02:00
metalgearsloth
8550056e68 Version: 221.0.0 2024-04-29 18:46:57 +10:00
Leon Friedrich
25211e3781 Improve transform & state handling exception tolerance (#5081)
* Improve transform & state exception tolerance

* release notes

* Fix pvs assert

* Fix velocity conservation
2024-04-29 18:42:05 +10:00
Leon Friedrich
3500abfd47 Add IUserInterfaceManager.UpdateHovered() (#5083)
* Add `IUserInterfaceManager.UpdateHovered()`

* Try fix tests
2024-04-29 18:37:52 +10:00
Leon Friedrich
7d1915096a Use more entity queries in physics systems & entity manager (#5082) 2024-04-29 13:46:10 +10:00
Nemanja
4504731588 Add a method in SharedTransformSystem for swapping the position of two entities (#4988)
* swap pos method

* no forcing

* Sluth review

* weh
2024-04-29 13:45:12 +10:00
Leon Friedrich
701fa95a82 Temporarily disable macos tests (#5079) 2024-04-29 02:35:04 +10:00
ShadowCommander
40a9048704 Add margin input value order as a comment (#5067)
* Add margin input value order as a comment

* Make a better comment and move value to remark
2024-04-27 20:17:11 +02:00
Leon Friedrich
cee8d42776 Improve MergeImplicitData exception tolerance (#5075) 2024-04-28 02:23:56 +10:00
metalgearsloth
3330d96177 Version: 220.2.0 2024-04-27 16:05:51 +10:00
Pieter-Jan Briers
4033d96327 Engine changes for displacement maps. (#5023)
* Add load parameter support to RSIs.

Currently only supports turning sRGB off. RSIs with custom load parameters are not thrown into the meta-atlas.

As part of this, TextureLoadParameters and TextureSampleParameters has been made to support equality.

* Add UV2 channel to vertices.

This is a bad hack to make displacement maps work in Robust. The UV2 channel goes from 0 -> 1 across the draw and can therefore be used by displacement maps to map a separate displacement map layer on top of the regular meta-atlas RSI texture.

This creates float inaccuracy issues but they weren't bad enough to completely void the feature. I'm thinking I learn from this experience and completely re-do how UVs work with the renderer rewrite, so that hopefully won't happen anymore.

This required dumping the optimized PadVerticesV2 because the changed struct size made it impractical. RIP.

I don't like this approach at all but the renderer is slated for a rewrite anyways, and all shaders will need to be rewritten regardless.

* Add CopyToShaderParameters for sprite layers.

This effectively allows copying the parameters of a sprite layer into another layer's shader parameters. The use case is to copy texture coordinates for displacement maps, as the exact map used changes depending on orientation. It also enables animations to be used though I didn't use that personally.
2024-04-27 16:03:35 +10:00
metalgearsloth
6e0205d1a8 Version: 220.1.0 2024-04-27 12:33:45 +10:00
ShadowCommander
7cd95351c3 Remove IP address and HWId from ViewVariables (#5062)
* Remove IP address from ViewVariables

* Remove HWId from ViewVariables
2024-04-27 12:30:34 +10:00
Leon Friedrich
2a102f048f Fix client-side replay exception (#5068)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-04-27 12:30:00 +10:00
metalgearsloth
16bab1bc03 Close UIs on disconnect (#5071)
Engine handles it fine but content does not as the state gets handled before all comps are initialized.
2024-04-27 12:21:46 +10:00
metalgearsloth
123d0ae6ac Version: 220.0.0 2024-04-26 18:15:47 +10:00
metalgearsloth
d72de032fa Predicted BUIs (#5059)
* Add TryGetOpenBUI

Avoids having to get the component and openinterfaces separately.

* Couple more helpers

* entityquery

* reviews

* Shared BUIs

* zawehdo

* More boilerplate

* Bunch more work

* Building

* Stuff

* More state handling

* API cleanup

* Slight tweak

* Tweaks

* gabriel

* Disposies

* Active UI support

* Lots of fixes

- Fix states not applying properly, fix predicted messages, remove redundant message type, add RaiseUiMessage for an easy way to do it from shared, add the old BUI state change events back.

* Fix test failures

* weh

* Remove unncessary closes.

* release note
2024-04-26 18:12:55 +10:00
Tayrtahn
0fdba836ee Remove debug assert for Fixture.Owner equality (#5066)
* Removed debug assert for Fixture owner equality

* Blah
2024-04-26 14:47:09 +10:00
metalgearsloth
eb63809999 Version: 219.2.0 2024-04-25 00:31:04 +10:00
Leon Friedrich
4c3c74865c Fix yaml linter & improve validation of static fields (#5056) 2024-04-25 00:27:02 +10:00
Nemanja
b624f5b70f Add SetMapCoordinates (#5065)
* add set map coordinates function

* mmmmmmm yes

* Update Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-04-24 22:55:58 +10:00
Pieter-Jan Briers
6566a7658a Fix DebugCoordsPanel freezing when hovering a UI control.
It would bail out of the entire update logic if you aren't hovering over a map position, which isn't great when the control displays far more than in-simulation mouse position info.
2024-04-23 20:34:29 +02:00
metalgearsloth
9e3e1cc929 Optimise physics networking a lot (#5064)
Avoids unnecessarily dirtying every single tick when a mob is moving. Also avoids the getcomponent every time which is nice.
2024-04-23 22:36:40 +10:00
ElectroJr
4e87d93009 Version: 219.1.3 2024-04-23 00:26:17 -04:00
Leon Friedrich
1031ae4cc5 Fix mapping not pausing maps (#5063) 2024-04-23 14:25:40 +10:00
ElectroJr
73da147b88 Version: 219.1.2 2024-04-21 04:58:44 -04:00
Leon Friedrich
0ab59d70b1 More mapinit fixes (#5058) 2024-04-21 18:57:20 +10:00
ElectroJr
8e8470ac7e Version: 219.1.1 2024-04-21 02:50:34 -04:00
Leon Friedrich
15f94bd094 Fix mapinit persistence when overwriting existing maps (#5057)
* Fix mapinit persistence when overwriting existing maps

* Hours wasted chasing fucking chickens in a crate
2024-04-21 16:48:39 +10:00
DrSmugleaf
68888c4370 Make remaining IPrototypes partial (#5053) 2024-04-21 07:39:54 +10:00
ElectroJr
19f87dfbb3 Version: 219.1.0 2024-04-20 16:52:47 -04:00
Leon Friedrich
68e5b6924d Add ComponentRegistry overrides to more entity spawn methods (#5051) 2024-04-19 13:16:17 +10:00
Leon Friedrich
9f913cd2d9 Fix RecursiveMapInit (#5052)
* Fix RecursiveMapInit

* re-use sawmill
2024-04-19 13:15:42 +10:00
Vasilis
ec37d1c137 Auth is now required by default (#5050) 2024-04-18 17:51:25 +02:00
metalgearsloth
ea58924495 Audio stuff (#5048)
Better overlay debug and uses the adjusted distance for cutoff instead.
2024-04-18 14:36:29 +10:00
keronshb
c5aa735506 Adds rotation to Map Position spawns (#5047)
* add angle to rotation

* fixes inheritance

* proxy

* Fixes maprot

* changes angle to default and uses set coords overload
2024-04-18 14:25:07 +10:00
metalgearsloth
f5a6e52c7f Version: 219.0.0 2024-04-18 14:16:40 +10:00
Leon Friedrich
d5c4981648 Partial MapManager refactor (#5042)
* MapManager rejig

* Update Tests

* A
2024-04-18 14:05:02 +10:00
Pieter-Jan Briers
8c6170661d Die 2024-04-18 03:11:24 +02:00
metalgearsloth
1901059755 Version: 218.2.0 2024-04-17 19:06:59 +10:00
metalgearsloth
8cdec92be6 Add slider locking (#5044)
* Add slider locking

Useful if you just want like a playback slider without the client intefering.

* Name alignment
2024-04-17 19:04:52 +10:00
metalgearsloth
0a00e7ec29 Network audio states (#5024)
* Network audio states

Lets server pause or stop audio or whatever.

* Better API and state fix

* Bunch of fixes

- TimedDespawn proccing too early.
- PlaybackPosition setting
- SetState setting.

* Clamps and despawn fixes

* fix
2024-04-17 17:55:22 +10:00
DrSmugleaf
8c25a83066 Expose worldPosition in SpriteComponent.Render (#5043)
* Expose worldPosition in SpriteComponent.Render

* Set default value to zero
2024-04-17 16:17:12 +10:00
Pieter-Jan Briers
0fadfc2d9b Allow control layout properties to be set via style sheet. (take 2) (#5037)
* Reapply "Allow control layout properties to be set via style sheet." (#5035)

This reverts commit af36d24892.

* Fix tests

MaxSize properties had wrong default. Oops.
2024-04-17 00:42:12 +02:00
metalgearsloth
a6e7224672 Version: 218.1.0 2024-04-16 22:39:23 +10:00
metalgearsloth
37796f4806 Add a Vertical tab container (#4948)
* Vertical tab container

* weh

* Tab review
2024-04-16 22:33:43 +10:00
Pieter-Jan Briers
a5494d1df2 Change default hub URL 2024-04-15 22:27:25 +02:00
Errant
e2525a2103 Fix division remainder issue in color.cs (#5040) 2024-04-15 19:13:43 +02:00
Pieter-Jan Briers
b50f68866f Enable roslyn extension tests in CI (#5038)
* Enable roslyn extension tests in CI

* I'll be real I kinda just hoped that last one would work. dotnet test's --help documentation is useless garbage so I couldn't tell if that was supported or not. Guess not.

* Actually fix the Roslyn tests.

As far as I can tell, Roslyn tests haven't worked since #2976.

The tests used a pretty awful technique of linking the test code against the analyzer, so that the analyzer's copy of the relevant attributes got included into the test. This then broke when the namespace got changed by the linked PR.

Now the tests get an EmbeddedResource for the necessary test files compiled instead.

Also applied this to DependencyAssignAnalyzerTest because why not.
2024-04-14 09:26:07 +02:00
Pieter-Jan Briers
03a4d3e0a0 Add IEquatable`1.Equals to sandbox
Why wasn't this in here wtf.
2024-04-14 08:36:25 +02:00
metalgearsloth
af8fb52a4f Version: 218.0.0 2024-04-14 15:00:07 +10:00
metalgearsloth
fd60dc2887 Add EntManager + ProtoManager helpers for random picks (#4869)
* Add EntManager + ProtoManager helpers for random picks

Lets us cleanup content a bit from these being re-implemented.

* weh
2024-04-14 14:55:55 +10:00
metalgearsloth
cd24fd46b6 Default worldpos to null (#5036)
I think this is slightly more robust than defaulting to 0 actually while still fixing the issue.
2024-04-14 14:47:30 +10:00
metalgearsloth
44cc7127fa Default SetWorldPos to 0 rotation (#5034)
More closely aligns with the old default.
2024-04-14 14:28:16 +10:00
metalgearsloth
af36d24892 Revert "Allow control layout properties to be set via style sheet." (#5035)
This reverts commit 8c4deb2067.

# Conflicts:
#	RELEASE-NOTES.md
2024-04-14 14:14:44 +10:00
Leon Friedrich
caa8ff0f2d Modify container/spawn helper methods (#5030)
* Modify container/spawn helper methods

* A
2024-04-14 13:47:21 +10:00
Pieter-Jan Briers
cd67c67a5c Add analyzer to warn for assignment to dependency fields. 2024-04-14 05:14:12 +02:00
deltanedas
57b328e8c2 add CopyData to appearance system (#5022)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-04-14 02:24:31 +02:00
Pieter-Jan Briers
4874b1db68 Update UI themes on prototype reload. 2024-04-14 02:15:53 +02:00
Pieter-Jan Briers
814ad08884 Allow scaling the line height of a RichTextLabel 2024-04-14 02:10:01 +02:00
Pieter-Jan Briers
8c4deb2067 Allow control layout properties to be set via style sheet.
This works by setting the stylesheet values into the regular control property fields when updated. This means 0 performance overhead except when updating styles, and even then it's probably negligible. A bitfield is used to track which properties are set and how.

This code is all done manual for now. I wanted to make a source gen for this but couldn't be arsed at the moment. The code manually written here is basically what a future source gen would generate optimally.
2024-04-14 02:09:00 +02:00
Pieter-Jan Briers
f6a5120e56 Fix exception when inspecting element in some cases.
Happened when I was trying to develop item status stuff.
2024-04-14 02:05:40 +02:00
Pieter-Jan Briers
c1b8bf8e52 Make StatusHost request headers case insensitive
Woops that's how HTTP is supposed to work.
2024-04-11 02:28:46 +02:00
Pieter-Jan Briers
4b193bad26 Add non-generic GetCVar.
Surprised we didn't have this.
2024-04-11 02:25:40 +02:00
Tayrtahn
8db3da4852 Add Type tracking to FieldNotFoundErrorNode (#5032)
* Add Type tracking to FieldNotFoundErrorNode

* Suggested changes, plus xmldoc and primary constructor conversion.
2024-04-08 19:27:02 +02:00
Leon Friedrich
0c271fc2f8 Remove uneccesary Exists() checks in container system (#5031)
* Remove `Exists()` checks in container system

* A

* A
2024-04-08 01:24:09 +02:00
Pieter-Jan Briers
ed406c06b7 Fix HTTP errors on watchdog ping not being reported 2024-04-05 23:23:46 +02:00
Pieter-Jan Briers
b31940b489 Improve logging for watchdog pinsg 2024-04-05 02:22:06 +02:00
Pieter-Jan Briers
84360c653d Add better environment variable config system
ROBUST_CVARS had multiple issues:

* Not composable, i.e. two independent systems can't easily layer CVars to set as they all have to go into one var
* Not sanitary, there's no way to store things that have a ";" in them because it'd always get used as separator.

This adds a new ROBUST_CVAR_* system. For example I can set ROBUST_CVAR_game__hostname=foobar to set a CVar via a single env var. A double underscore ("__") is replaced with a period to make the CVar names safe for environment variables.

Also made Robust.Shared.Configuration.EnvironmentVariables internal because wtf that should not be public no.
2024-04-04 02:24:57 +02:00
metalgearsloth
6764ed56b0 Version: 217.2.1 2024-03-31 17:02:37 +11:00
Leon Friedrich
2b55d39e51 Make various ValueList enumerators use spans (#5019)
* Make various ValueList enumerators use spans

* Remove reference to EntityEventBus.OrderedRegistration

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-03-31 17:01:03 +11:00
metalgearsloth
fdc1de2430 Fix LineEdit tests (#5021) 2024-03-31 16:58:15 +11:00
metalgearsloth
99c5b0ad08 Version: 217.2.0 2024-03-31 15:29:26 +11:00
faint
9e2ab2a917 Double-clicking in LineEdit (#4831)
* Double-clicking in LineEdit

epic

* Fix IGameTiming dependency

* remove iocmanager.resolves

* test fix

* Update Robust.Client/UserInterface/Controls/LineEdit.cs

Co-authored-by: ShadowCommander <shadowjjt@gmail.com>

* review

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: ShadowCommander <shadowjjt@gmail.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-03-31 15:25:31 +11:00
metalgearsloth
36eb857b55 Fix TimeSpan addition (#5018)
Rider thinks nullable timespan += is fine but it no fine.
2024-03-31 15:02:18 +11:00
metalgearsloth
92d7f2723a Add ComponentRegistry helpers to EntityManager (#4934)
* Add ComponentRegistry helpers to EntityManager

* Metadata here too

* Remove object cast

* knock

* Plural
2024-03-31 14:54:01 +11:00
Leon Friedrich
91d3f67a94 Make IntersectRayWithPredicate ignore non-hard fixtures (#5017) 2024-03-31 14:48:37 +11:00
ElectroJr
b28b5ed09b Version: 217.1.0 2024-03-30 21:16:44 -04:00
Leon Friedrich
30eed7957f Add an EffectiveCurTime for physics subticks (#5014)
* Add an `EffectiveCurTime` for physics subticks

* Release notes
2024-03-31 12:15:30 +11:00
Fildrance
73c1449811 Add GetItems() extension for IRobustRandom (#4975)
* refactor: RobustRandom and RandomExtensions namespace change to file-scoped

* refactor: IRobustRandom xml-doc methods rearranged to be more structured.

* feat: GetItems methods added to RandomExtensions, tests for new methods added.

* fix: GetItems will not request count-1 from next random, as System.Random.Next have upper bound excluded.

* fix: enforced standard deviation on picking next items in RandomExtensions.GetItems + fixed hashet initial capacity +removed mandatory hashset allocation

* refactor: specified border values interaction in IRobustRandom xml-doc

* refactor: updated relese-notes

* refactor: changed release-notes PROPERLY

* fix: order by which unique random items are picked in RandomExtensions.GetItems were fixed to ACTUALLY follow normal distribution

* refractor: added comment for devious RandomExtensions.GetItems only-unique logic

* reduce code duplication

* Cleanup code a bit

Rename variables, and make it a bit more compact.
Also, IMO the description is unnecessary

* Remove obsolete extension

* Remove incorrect O(n) comments.

---------

Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
2024-03-30 10:42:39 +11:00
Pieter-Jan Briers
958b5dd06d Fix MapComponent.LightingEnabled funky FOV.
There's two separate bool checks, one wasn't turned off, meaning the FOV kept being rendered but not updated.
2024-03-29 16:54:27 +01:00
metalgearsloth
4002cbddb9 Version: 217.0.0 2024-03-29 17:04:51 +11:00
metalgearsloth
c38a14e78f WorldPos review fix (#5011)
Forgot I merged ghub and tried pushing and didn't check it failed.
2024-03-29 16:56:25 +11:00
metalgearsloth
5164d99996 Implement VV for AudioParams + dump obsolete params (#4994)
* Implement sound VV

No params yet because I'm lazy but paths and collections work.

* Fixes

* rn

* review

* pitch

* VV sound params + dump unneeded params

BusName never used and per-source Attenuation got dropped.

* weh?
2024-03-29 16:54:14 +11:00
metalgearsloth
c79217ab66 Rework SetWorldPosition (#4915)
* Remove ents from containers for worldpos updates

Avoids bugs from content not checking for this every time. Physics and anything else important manually uses localposition updates so shouldn't adversely impact performance that much.

* Fixes

* Fix grid pos

* Also fix the joint setter

* Add both

* Update Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs

Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>

---------

Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
2024-03-29 16:49:26 +11:00
deltanedas
9ef7f7cb37 add AddUi to shared ui system (#4984)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-03-27 14:36:11 +11:00
ShadowCommander
02ac314b1a Fix first measure of ScrollContainer scroll bars (#5005)
The first measure would include scroll bar size even if the available size fit all the contents. This would make the returned size larger than it should be.
2024-03-27 14:33:59 +11:00
metalgearsloth
8607ba1f16 Version: 216.0.0 2024-03-27 14:14:54 +11:00
Pieter-Jan Briers
2a9de462d5 Preserve tile maps when saving maps & related changes (#5003)
* Un-hardcode behavior to make a component not saved to map file.

MapSaveId is a special component that can't be saved to map files due to a hardcoded type check. This behavior can now be applied to any component with [UnsavedComponent].

Moved "component registration" attributes into a single file because they don't deserve their own (poorly organized) .cs files.

* Add ITileDefinitionManager.TryGetDefinition

Try-pattern version of the existing indexers.

* Preserve tile maps when saving maps

This changes the map saver and loader code so that the "tilemap" can be preserved between map modifications as much as possible.

The tile map from the loaded map gets stored onto MapSaveTileMapComponent components on all loaded grids. This tile map is then used when saving, meaning that changes to the engine's internal tile IDs do not cause diffs.

Fixes #5000

* Changelog

* Fix tests
2024-03-27 14:14:19 +11:00
metalgearsloth
c59ef5ab2d Fix buffered audio disposals (#5009) 2024-03-27 14:07:54 +11:00
Leon Friedrich
eba1d866fb Change default PVS LoD cvars (#5008) 2024-03-27 13:58:52 +11:00
Pieter-Jan Briers
ab2bff8f40 Make sawmill levels work differently. (#5006)
Before, sawmill log levels were very clunky to use. We set the root sawmill to something like debug or info, but then it becomes impossible to specify specific sawmills to be verbose. This is because sawmills checked the log level at every part of the hierarchy when navigating "up" to find log handlers to write into.

This changes the behavior so that the filter log level is the FIRST set log level encountered on the hierarchy. This means the log level is compared only once, and a sawmill down the hierarchy that has a level like Verbose set can cause verbose messages to come up even if the root is still set to Debug. This matches the logging behavior in libraries such as ASP.NET Core.
2024-03-27 13:27:54 +11:00
Pieter-Jan Briers
536fca4115 Version: 215.3.1 2024-03-26 18:47:35 +01:00
Pieter-Jan Briers
7a0d02463c Revert "Zstd Update (#5002)"
This reverts commit eee771c5f1.
2024-03-26 18:46:34 +01:00
Pieter-Jan Briers
6df53d60ed Changelog 2024-03-26 15:06:38 +01:00
Pieter-Jan Briers
ff38e9f12a Version: 215.3.0 2024-03-26 02:52:36 +01:00
Pieter-Jan Briers
00c58c76a8 Disable MTU Expansion again
Reports of problems, double checked with Grafana. Great.

I am going to do manual test runs of this system against Miros again if I end up looking to improve the situation.
2024-03-26 02:49:47 +01:00
Pieter-Jan Briers
0bf99e173c Texture GetPixel() fixes
Wow this API is bad. Well the API is just terrible for perf for multiple reasons but not fixing that...

1. Was using DSA GetTextureImage() instead of GetnTexImage() so only worked if DSA was available.
2. Was using stackalloc with a potentially huge amount of memory (original bug).
3. Had an off-by-one for the vertical coordinate.

All fixed now.

Fixes #5001
2024-03-26 01:11:30 +01:00
Wrexbe (Josh)
eee771c5f1 Zstd Update (#5002) 2024-03-25 22:20:39 +01:00
Tayrtahn
2946cd866c Added length comparison helpers for Vector2 (#4999) 2024-03-25 16:08:43 +01:00
chromiumboy
9a2a3d658d Provide option to stop PlaceManager from changing the player control scheme (#4977) 2024-03-25 17:44:11 +11:00
Leon Friedrich
d933f03a54 Add TryComp and HasComp to EntityQuery<T> (#4996) 2024-03-24 21:40:28 +01:00
metalgearsloth
25bbb21dc8 Version: 215.2.0 2024-03-25 00:57:09 +11:00
metalgearsloth
4460454563 Implement sound VV (#4966)
* Implement sound VV

No params yet because I'm lazy but paths and collections work.

* Fixes

* rn

* review
2024-03-24 16:59:40 +11:00
KISS
a2d8fa7a9b Making possible to QueueDeleteEntity on EndCollideEvent (#4883)
* made possible to destroy entity on EndCollideEvent

* figured queue delete issue

* review

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-03-24 16:32:57 +11:00
metalgearsloth
71f0491f10 Version: 215.1.0 2024-03-24 14:21:33 +11:00
Leon Friedrich
b4c1618338 Misc Toolshed tweaks (#4990)
* Toolshed tweaks

* oops

* Apply suggestions from code review

Co-authored-by: Moony <moony@hellomouse.net>

* Re-add NotImplementedException

* Move error message

---------

Co-authored-by: Moony <moony@hellomouse.net>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-03-24 14:18:49 +11:00
Wrexbe (Josh)
df0945f3cd VV editor for EntProtoId? (#4986)
Co-authored-by: wrexbe <wrexbe@protonmail.com>
2024-03-24 14:13:07 +11:00
metalgearsloth
8d477716b0 Add audio filepath completion helper (#4968)
* Add audio filepath completion helper

Due to how audio is packaged server doesn't have most audio files.

* RN

* weh
2024-03-24 14:12:31 +11:00
metalgearsloth
5c1a5e9826 Add 2 random methods (#4980)
* Add 2 random methods

One for valuelist shuffle and one for NextAngle range.

* rn

* Fix RN
2024-03-24 14:00:21 +11:00
metalgearsloth
6daa3ad2fc Version: 215.0.0 2024-03-24 13:24:11 +11:00
Vasilis
033a617102 Requirement for https://github.com/space-wizards/space-station-14/pull/25569 (#4992) 2024-03-23 22:23:39 +01:00
Tayrtahn
b9b565d53e Fix uncaught overflow exception when parsing NetEntities from strings. (#4989)
* Fixed uncaught overflow exception when parsing NetEntities from strings.

* or
2024-03-23 20:55:45 +01:00
Pieter-Jan Briers
b7ea4d0cca Add release notes for #4987 2024-03-23 16:38:19 +01:00
Pieter-Jan Briers
919ec01477 Enable MTU expansion by default.
Due to yet another need to lower the MTU we should enable this by default. Improves network efficiency.
2024-03-23 16:27:33 +01:00
Pieter-Jan Briers
e484eac29c Changelog for 3097784cd7 2024-03-23 16:25:49 +01:00
Pieter-Jan Briers
eedadb250f Add net.mtu_ipv6 CVar.
Wires up to the new MaximumTransmissionUnitV6 configuration in Lidgren. Default is that of Lidgren.
2024-03-23 16:25:25 +01:00
Kevin Zheng
3097784cd7 Lower default MTU to 900 (#4985)
Some players continue to have "stuck at connected" issues connecting to
most servers, but apparently this issue has become more prominent in the
last two weeks or so?

It is almost certainly an MTU problem because there are at least two
servers on the hub that run a lower default MTU, and these players had
no problem connecting to them. For one of these reporters, I actually
increased the MTU to 1000 and they could no longer connect, and could
connect again once it was lowered to 900.

It's not clear what recent changes, either to the codebase or to the
public Internet that have been exercising this MTU issue more. For those
experiencing MTU issues, it seems that connecting to a less full server
results in higher probability of success.

Nevertheless, bringing down the default MTU and then possibly enabling
MTU expansion in the future would make this game playable for a small
but not insignificant bit of players.
2024-03-23 16:22:06 +01:00
Pieter-Jan Briers
0fb41e06c8 Upgrade to Lidgren v0.3.0 2024-03-23 16:21:34 +01:00
nikthechampiongr
0a79382a62 Add helper command for Player toolshed commands (#4987)
Allows you to invoke players:entity with a username to immediate get that player's attached entity(if any)
2024-03-23 15:27:54 +01:00
Tayrtahn
1f2b38a6d1 Code cleanup: Purge calls to obsolete EntityCoordinates methods (#4983) 2024-03-23 13:07:33 +11:00
Pieter-Jan Briers
f760929527 Add IMeterFactory implementation to IoC
This will be useful as we start using more System.Diagnostics.Metrics.
2024-03-22 22:19:18 +01:00
Pieter-Jan Briers
f5ade69f6d Show MTU in network debug panel. 2024-03-22 22:19:17 +01:00
Tayrtahn
9bfe889c86 Code cleanup: Purge obsolete MapManager methods (#4981)
* GetGrid

* GridExists

* TryGetGrid
2024-03-21 16:31:33 +01:00
Tayrtahn
e3954494e7 Just two (#4982) 2024-03-20 15:48:19 +01:00
Pieter-Jan Briers
6697e36e84 Fix ResizableMemoryRegion metrics
This was broken for more reason than one.

So the obvious reason they weren't counting right is because I forgot to add a call to update the metric in Shrink(). Whoops.

The second issue is that .NET's new metric types are unintuitive as shit and Microsoft would've done better to just link to the OpenTelemetry specification instead of whatever garbage they documented instead.

It turns out UpDownCounter just wasn't the correct choice at all! Because it only ever gets DELTA VALUES sent to collection tools, it can never be used for absolute measurements (such as memory usage) **despite Microsoft literally using examples of absolute values in their docs** ("queue size"). The OpenTelemetry spec is clear on this.

This means writing more code, because the API is shit. Great.
2024-03-20 11:58:11 +01:00
Pieter-Jan Briers
dae4041e61 Make CheckBox texture vertically centered.
This makes it look better in circumstances where the checkbox is vertically big, for example due to surrounding inline buttons.
2024-03-20 11:12:06 +01:00
Pieter-Jan Briers
390f399750 Add IMetricsManager.UpdateMetrics system
This callback enables code to update its metrics only when required. Needed this for SS14 since online admin count stats are not something I want to update on an "arbitrary" basis.

Tons of consideration and commenting for how this plays in with stuff like dotnet-counters. Added the metrics.update_interval CVar to act as a fallback for this event when dotnet-counters and such is in use.
2024-03-20 11:11:32 +01:00
Pieter-Jan Briers
28cf7442ce Fix naming of ResizableMemoryRegion metrics 2024-03-20 11:09:28 +01:00
Leon Friedrich
d8b03be651 Add debug assert to Dirty(uid, comp) (#4978) 2024-03-18 21:37:25 +01:00
Tayrtahn
16c7c71ca6 Code cleanup: Dirty(Comp) (#4979)
* The easy ones

* SharedPhysicsSystem
2024-03-18 21:36:52 +01:00
Leon Friedrich
0245c371ae Make the replay has error use the ReplayIgnoreErrors cvar (#4974) 2024-03-17 22:50:00 +01:00
Leon Friedrich
c8cb13f832 Fix serialization error logging (#4973) 2024-03-17 18:01:00 +01:00
Pieter-Jan Briers
86ecfaa56b Allow replays with mismatching type hashes.
Things like .NET bumps and engine minor version updates break the hashes. Just allow mismatching hashes, printing a warning to the log at least.

If the hash mismatches it'll explode with some weird error like invalidcast/index so whatever.

It'd be better if there was a "hey this might not work, you sure you want to load this?" screen, but I don't feel like rewriting the entire replay loading system right now.

Fixes https://github.com/space-wizards/SS14.Launcher/issues/142
2024-03-17 16:43:01 +01:00
Pieter-Jan Briers
43a32e7015 Fix dump_netserializer_type_map to show full data.
The output it gave didn't show the full info to generate the manifest (e.g. assembly version numbers)
2024-03-17 16:40:06 +01:00
Leon Friedrich
83371885fa Support transform states with unknown parents (#4972) 2024-03-17 11:28:50 +11:00
ShadowCommander
e686e1b4cc Add collection parsing to the dev window for UI (#4959)
* Debug window collection popup

* Try fixing?

* DevUI shows children and string collections

* XAMLify the popup

* Rename popup

* Deduplicate code

* Simplify popup creation

* MouseFilter.Ignore is default so it can be removed
2024-03-16 23:35:04 +01:00
ElectroJr
b1f9d011ce Version: 214.2.0 2024-03-16 16:17:59 -04:00
Leon Friedrich
a2d0504368 Replace PVS dictionaries with memory magic (#4795)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-03-17 06:57:13 +11:00
metalgearsloth
7aa951ca48 Add undetachable PVS flag (#4889)
Useful in some rare cases, mainly for grid-related activities.
Specifically:
- Audio entity where we never want it detached.
- FTL previs effects to show impending squish.
2024-03-16 14:58:08 +11:00
metalgearsloth
75a80b7a8a Fix tooltips underflowing left side of screen (#4952)
* Fix tooltips underflowing left side of screen

If the tooltip is so large it would clip the right side then it would underflow completely off-screen. This just clamps it instead.

* Better

* rubb
2024-03-16 14:45:17 +11:00
metalgearsloth
69706b0257 Fix global audio (#4964)
* Fix global audio

* Better
2024-03-16 11:59:57 +11:00
Pieter-Jan Briers
10b191dff8 Version: 214.1.1 2024-03-16 01:13:47 +01:00
Pieter-Jan Briers
92ab3fb64b Fix connection denials always redialling
Bug caused by changes to connection denial.

Fixes #4963
2024-03-16 01:13:34 +01:00
metalgearsloth
92a0c14383 Version: 214.1.0 2024-03-15 20:20:20 +11:00
metalgearsloth
5aaf6d0994 Fix VV for entity prototypes (#4956)
* Fix VV for entity prototypes

* Fix ProtoId
2024-03-15 20:16:09 +11:00
metalgearsloth
15f4da5e4b Audio limit fix (#4962)
I screm. See https://github.com/space-wizards/RobustToolbox/issues/4961
2024-03-15 20:14:49 +11:00
Leon Friedrich
a528e87f3d Add pvs_override_info command (#4958) 2024-03-15 14:32:23 +11:00
Pieter-Jan Briers
4af67b1394 Version: 214.0.0 2024-03-14 20:42:17 +01:00
metalgearsloth
e8de9b98d3 Add basic audio limits (#4921)
* Add basic audio limits

* RN

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-03-14 11:54:43 +01:00
Pieter-Jan Briers
a0ffeff4e5 Release notes for last commit 2024-03-14 08:10:54 +01:00
Pieter-Jan Briers
07654564f3 TextEdit fixes
Fixed being able to position the cursor vertically if placeholder text was visible and multi-line. This is because the code was using line break info for the place holder. On top of not being correct behavior, this caused further exceptions since the cursor would get outside the editable text rope.

Fixed index exception if you try to move left in an empty text edit.

Has regression tests.

Fixes #4957, fixes #4953
2024-03-14 08:09:28 +01:00
Pieter-Jan Briers
7fbf8d05eb Add ability to add structured deny data to NetConnectingArgs. (#4487)
* Add ability to add structured deny data to NetConnectingArgs.

Builds on the (horrifying) NetStructuredDisconnectMessages so that content can do more stuff.

To be used by SS14 to throttle people when they try to connect to a full server.

* Completely rewrite NetStructuredDisconnectMessages

So this class was a mess, and it was so bad it wasn't usable from content! System.Text.Json isn't sandbox safe (and I don't want to look into that right now), so the previous API surface of "pass the JsonNode around everywhere" just didn't work at all for content.

I decided the easiest solution would be to completely rewrite the entire thing to be a layer over a Dictionary<string, object> instead. This warranted a complete rewrite of the API, which should be fine as I doubt anybody was using it anyways.

Also, fully tested.
2024-03-14 07:27:22 +01:00
ShadowCommander
c12971cb9b Add decimal variable to Range Control rounding (#4954)
* Add decimal variable to Range Control rounding

* Remove unnecessary virtual and add ViewVariables
2024-03-13 00:43:27 +01:00
metalgearsloth
2b6381c332 Version: 213.0.0 2024-03-11 14:36:45 +11:00
TemporalOroboros
8149a3aaad Removes Obsolete BaseContainer methods. (#4843) 2024-03-11 14:35:31 +11:00
Kot
4b39bf1f2d Check entity for existence before drawing it in the SpriteView (#4886)
* Check entity for existence before drawing it in the SpriteView

* Slightly refactor ResolveEntity to be more straightforward
2024-03-11 14:34:44 +11:00
metalgearsloth
53394fff44 Add GetEntitiesInRange for sets (#4951)
* Add GetEntitiesInRange for sets

Need it for an old method.

* rn

* Fix SO
2024-03-11 13:43:13 +11:00
metalgearsloth
4bed20e070 Add RaiseSharedEvent (#4950)
Used in some rare cases on content (popups + pickup prediction). I was too lazy to make system proxy methods because it's very infrequent.
2024-03-10 19:33:45 +01:00
metalgearsloth
e4b6af09f1 Version: 212.2.0 2024-03-11 02:08:08 +11:00
metalgearsloth
1ef29ae781 Add some physics helpers (#4946)
Thought I had these but couldn't find them.
2024-03-11 02:05:50 +11:00
Vasilis
5686950421 Increase replay compressed size (#4925)
They tend to get cut off (or well did on wizden before pjb changed it to this exact value bigger along with another patch). Before you got around 2ish hours in a replay before it stopped. I doupt most servers will reach 6ish hours before this takes effect. But those servers can increase this value of needed.
2024-03-08 12:24:41 +01:00
Pieter-Jan Briers
2b54aa8984 Version: 212.1.0 2024-03-07 21:29:29 +01:00
Pieter-Jan Briers
859f150404 YIPPEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE IMAGESHARP VULNERABILITY 2024-03-07 21:29:00 +01:00
metalgearsloth
558f4b5b16 ScrollContainer niceties (#4940)
- If scroll is not visible we don't handle it. This means nested containers don't interfere with their parents anymore.
- Fallback to Y-scrolling for H-scroll only containers.
2024-03-05 23:32:22 +01:00
metalgearsloth
108366152b Fix TextureRect KeepCentered (#4937)
Easiest way to repro is set a non-1.0 UIScale and open the main menu up, the logo will be fonky.
I checked the control dimensions and this aligned with my expectations.
2024-03-05 23:28:28 +01:00
I.K
c55327e1d1 Set a minimum of 0.05 for the light resolution (#4942) 2024-03-05 23:27:39 +01:00
metalgearsloth
370e0fa0d0 Add nullable versions for protomanager (#4938) 2024-03-05 15:24:01 +11:00
metalgearsloth
4f9f82c20c Version: 212.0.1 2024-03-03 19:42:48 +11:00
metalgearsloth
43670a8ddd Pass array by-ref (#4936)
Yeah idk how to fix this otherwise but using this would be nicer.
2024-03-03 19:41:29 +11:00
metalgearsloth
250313e1ed Version: 212.0.0 2024-03-03 18:34:22 +11:00
metalgearsloth
18d511d4b6 Minor fixes (#4935)
- Swear I pushed this array change
- Update changelog
2024-03-03 18:32:51 +11:00
metalgearsloth
da9e5fb370 Add grid tile to Vector2 methods (#4851)
* Add grid tile to Vector2 methods

Avoids me having to do it on content.

* Release note

* Engine

* Collapsible

* Add entitylookup methods for parent / map

Content's done it a bunch so make it reusable.

* Add MaxDimension property to Box2

Sometimes I want to pretend it's a circle radius.

* Add GetLocalPosition to controls

In my case I want the mouse's position inside of the control to show something under it unless there's a better way.

* Add global rectangles for controls

Like my other PR used to check if mouse is inbounds on the control without doing some skrunkly caching with mousemove.

* Add dotted line drawing to screen handle

Probably needs anti-aliasing but idk an easy way to do it.

* weh

* weh

* a

* weh

* weh

* Optimise ChunkEnumerator

It never unioned the AABB passed in with the grid's AABB so it might inadvertantly iterate a lot more dummy chunks than it needs to.

This helps speedup FindGridsIntersecting.

* weh

* Add DrawPrimitives overload for List<Vector2>

Storing ValueList in a field seems sussy so this is the next best thing.

* weh

* Bump pool size

* oop wrong method

* Add drawing methods for lists

Content may be using it over a valuelist for whatever reason.

* Add more ValueList conveniences

* Add more CollectionExtension methods

Maybe array.resize is bad for sandbox coin, in which case I'd also settle for changing it to a list instead.

* Add ToMapCoordinates method for NetCoordinates

* fr

* mraow

* Release notes
2024-03-03 18:29:35 +11:00
Tayrtahn
e3bac382ce Add some helper methods to PVS Filters (#4933) 2024-03-03 11:51:40 +11:00
DrSmugleaf
179c6790b6 Add support for automatically networking component dictionary fields with entity keys and values (#4932)
* Add support for automatically networking component dictionary fields with entity keys and values

* Fix using

* Fix order

* Add support for both key and value being entity uid
2024-03-03 11:51:23 +11:00
metalgearsloth
a7db5634df Add more CollectionExtension methods (#4910)
Maybe array.resize is bad for sandbox coin, in which case I'd also settle for changing it to a list instead.
2024-03-03 11:51:13 +11:00
deltanedas
2daa86ff59 add PushMarkup to FormattedMessage (#4924)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-03-03 11:49:41 +11:00
metalgearsloth
d6803f5294 Add DrawPrimitives overload for List<Vector2> (#4900)
* Add DrawPrimitives overload for List<Vector2>

Storing ValueList in a field seems sussy so this is the next best thing.

* weh
2024-03-02 21:34:42 +01:00
metalgearsloth
bdcc0f7b9d Add more ValueList conveniences (#4911)
* Add more ValueList conveniences

* Review

* a
2024-03-02 22:15:18 +11:00
metalgearsloth
ce49aa47cf Add ToMapCoordinates method for NetCoordinates (#4914) 2024-03-02 21:47:05 +11:00
metalgearsloth
c7d48b2526 Remove ISerHooks obsoletion (#4928)
Still needed in rare cases so not really deprecated and we already discourage coders from using it where possible.
2024-02-28 19:01:08 +01:00
rene-descartes2021
6bb7f5b4ef Allow for use of new .NET 8 MSBuild property <UseArtifactsOutput> (#4929) 2024-02-28 19:00:51 +01:00
Brandon Hu
2974310450 chore: Remove some typos (#4927)
* chore: Remove some typos

* Apply suggestions from code review

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2024-02-27 12:31:29 +01:00
metalgearsloth
2694dce076 Version: 211.0.2 2024-02-25 14:14:45 +11:00
metalgearsloth
8960d1d995 Fix TextureRect scaling (#4923)
From moony my git patch didn't apply so done manually
2024-02-25 14:13:54 +11:00
metalgearsloth
0a4683d33e Version: 211.0.1 2024-02-23 19:32:16 +11:00
metalgearsloth
379bcfabe0 Fix Map Grid chunk enumerators (#4920)
They have empty AABBs so always returned early.
2024-02-23 19:31:20 +11:00
metalgearsloth
1d91838166 Version: 211.0.0 2024-02-23 18:01:07 +11:00
metalgearsloth
a5d4b8096f Move chunk enumerators to engine (#4901)
* Move chunk enumerators to engine

* notes

* Cleanup
2024-02-23 17:51:34 +11:00
Pieter-Jan Briers
a77eee5658 Fix async console command completions on ServerConsoleHost
Fixes #4828

Asynchronous console command completions were just not being run on the server, the wrong function was being called. Hooray.

This caused sudo to break because it actually uses an async command completion (as other command completions it invokes might in turn be async).
2024-02-23 00:22:58 +01:00
metalgearsloth
156187a0dd Optimise ChunkEnumerator (#4899)
* Optimise ChunkEnumerator

It never unioned the AABB passed in with the grid's AABB so it might inadvertantly iterate a lot more dummy chunks than it needs to.

This helps speedup FindGridsIntersecting.

* weh

* oop wrong method

* Update RELEASE-NOTES.md
2024-02-22 13:26:04 +11:00
metalgearsloth
852f002f59 Make collinear vertices check public (#4913) 2024-02-21 21:08:23 +11:00
metalgearsloth
9dc49c1904 Make physics constants public (#4912) 2024-02-20 22:02:28 -08:00
Moony
1995b13e5d Fix TextureRect. (#4908)
Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
2024-02-20 15:41:01 -08:00
Pieter-Jan Briers
f985d10ed9 In which I spend too much time SIMDizing PadVerticesV2 2024-02-20 12:27:20 +01:00
Pieter-Jan Briers
ae6cebbfbb Source gen reorganizations + component unpause generator. (#4896)
* Source gen reorganizations + component unpause generator.

This commit (and subsequent commits) aims to clean up our Roslyn plugin (source gens + analyzers) stack to more sanely re-use common code

I also built a new source-gen that automatically generates unpausing implementations for components, incrementing attributed TimeSpan field when unpaused.

* Fix warnings in all Roslyn projects
2024-02-20 10:15:32 +01:00
Pieter-Jan Briers
ef0bc1a2e4 Version: 210.1.1 2024-02-17 22:17:52 +01:00
Pieter-Jan Briers
72ba484f5b Changelog for key binding fix PRs 2024-02-17 22:12:33 +01:00
Pieter-Jan Briers
a70e511fcb Change default of ButtonGroup.IsNoneSetAllowed to true.
This brings default ButtonGroup behavior back to before #4841.

The original comments in the code *did* clearly intend for the other behavior to be the default, but the code was blatantly bugged (whoops) so this didn't happen. Content relied on this A LOT and it's quite sane behavior regardless so just change the default back call it a day.
2024-02-17 22:09:23 +01:00
Errant
e7f9e95525 fix default keybinds not knowing their place (#4903)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-02-17 22:03:36 +01:00
nikthechampiongr
bd908f9db6 Invalid keybinds will no longer mess up your game. (#4902)
* Fix issues when saving invalid keybinds.

* Fix horrible thing I forgot to fix.

* Change error log to debug

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2024-02-17 21:54:35 +01:00
Leon Friedrich
f8cb1729a3 Version: 210.1.0 2024-02-16 11:21:40 +13:00
Leon Friedrich
fd9d5c8aa8 Make ButtonGroup setter behaviour consistent with comment 2024-02-16 11:19:14 +13:00
Pieter-Jan Briers
4677296934 Add IsNoneSetAllowed mode to ButtonGroup.
This allows a button group to have no button pressed by default, which is the behavior of most radio buttons.
2024-02-15 01:16:33 +01:00
Pieter-Jan Briers
708f5dd376 Un-hardcode C:\Windows in MidiManager
Keeping an eye out for our bros who put Windows on a drive that isn't C:
2024-02-14 14:32:05 +01:00
Pieter-Jan Briers
4a06acda32 NetUserId implement ISelfSerialize
For @VasilisThePikachu
2024-02-14 01:08:38 +01:00
Pieter-Jan Briers
e7beb2032b Version: 210.0.3
This version changes nothing but I need it because I pushed the previous
version after amending it which means the tag is the wrong commit SORRY.
2024-02-13 16:11:47 +01:00
Pieter-Jan Briers
c7bd75f800 Version: 210.0.2 2024-02-13 16:06:28 +01:00
Pieter-Jan Briers
b4165e8661 ALSO revert changes to TextureRect from #4841
Stretch modes are broken or something, SS14 lobby art looks wrong. Can't be arsed to debug it myself.
2024-02-13 16:05:46 +01:00
Pieter-Jan Briers
ad339b5bfd Version: 210.0.1 2024-02-13 15:30:20 +01:00
Pieter-Jan Briers
e1197af8ce Revert changes to TextureButton from #4841
Breaks SS14 stylesheets due to not responding to style properties anymore.

At least one of those seems to be unfixable (ModulateSelf usage) which makes me think we should just deprecate ModulateSelf instead. However I'm not fixing that here.
2024-02-13 15:29:46 +01:00
metalgearsloth
102cadf3a6 Version: 210.0.0 2024-02-13 18:26:01 +11:00
Hannah Giovanna Dawson
e7723b61bc Add UnicodeRange to sandbox (#4894)
* Add GetEncoding to sandbox (#4892)
Need this struct allowlisted to for nice unicode sanitization.

* Add UnicodeRanges too

* Changelog

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-02-11 17:22:11 +01:00
Moony
a9d17337a3 RT Patches for UI improvements (#4841)
* fix up buttons

* wah

* ough

* huhwuhuahsdhsfdj

* loud incorrect buzzer

* wawa

* Allow XmlnsDefinition

* wawa

* Release notes.

* Expose keybind loading.

* address reviews and other things

---------

Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-02-11 17:18:39 +01:00
Pieter-Jan Briers
74622bac83 Add DateTimeSerializer 2024-02-11 16:40:52 +01:00
Pieter-Jan Briers
a3047b1687 More warning fixes 2024-02-11 15:51:07 +01:00
Pieter-Jan Briers
3a55118143 Replace CVar OnValueChanged in systems with Subs.CVar 2024-02-11 13:29:27 +01:00
Pieter-Jan Briers
3c5fbc648a Add Subs.CVar helper for subscribing to CVar changes from entity systems 2024-02-11 13:29:27 +01:00
Pieter-Jan Briers
f9c39bce0b Use ValueList for EntitySystem subscriptions list.
That's a hundred something lists just gone and I don't have to ??=
2024-02-11 13:29:27 +01:00
Hannah Giovanna Dawson
0e8c803c0f Add GetEncoding to sandbox (#4892) 2024-02-10 17:59:13 +01:00
c4llv07e
6bb7b88c69 Save discord rich presense to the user config (#4884)
Signed-off-by: c4llv07e <kseandi@gmail.com>
2024-02-02 00:36:59 +01:00
metalgearsloth
9e0fc7017c Version: 209.0.1 2024-02-02 01:02:42 +11:00
metalgearsloth
76317b7ab3 Fix bad import (#4882)
fork why are you so SLOW.
2024-02-02 01:01:21 +11:00
metalgearsloth
d5f4d4bf2f Version: 209.0.0 2024-02-02 00:40:18 +11:00
metalgearsloth
156d1a6b14 Add SharedMapSystem helper for tile methods (#4845)
* Add SharedMapSystem helper for tile methods

* weh

* release

* Resolves

* Move some of these

* note

* Remove obsolete

* note
2024-02-02 00:39:20 +11:00
metalgearsloth
3fa456fd44 Fix relay refreshes where the entity gets contained to itself (#4865)
If the parent is somewhere below the transform hierarchy should still be okay, this just fixes where something getting pulled gets picked up as pulling doesn't seem to be handling this anymore.
2024-02-01 20:33:33 +11:00
LordCarve
bf9bb46154 Added ReferenceEquals() tests for RobustIntegrationTest (#4839)
* Added a test that checks that RobustIntegrationTest Client and Server do not end up with same sub-ComponentState reference objects.

* Un-ignore and adjust the test.

* review

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-02-01 20:22:52 +11:00
metalgearsloth
fb3da0b53c Add entitylookup methods for parent / map (#4870)
* Add entitylookup methods for parent / map

Content's done it a bunch so make it reusable.

* weh

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-01-31 17:21:41 +01:00
Pieter-Jan Briers
5c635c09b4 Replay int overflows, fixed again (#4878)
* Reapply "Fix replay int overflow issues." (#4802)

This reverts commit 32049e34f2.

* IConfigurationManager.LoadDefaultsFromTomlStream now does required type conversions.

This fixes scenarios like loading of `long` CVars.
2024-01-31 17:17:28 +01:00
Pieter-Jan Briers
fad539212d Cache TotalTicks in MIDI renderer
This is the "total length" of the MIDI file, but it's not cached by FluidSynth and expensive to calculate.

This property was used in the render code (for local files only) and the instrument menu. Caching it drastically improves performance.
2024-01-31 00:29:03 +01:00
Pieter-Jan Briers
7275302639 Stop multithreading FluidSynth with synth.cpu-cores.
It makes no sense for our use case, and it caused FluidSynth to allocate a different thread pool *per* mixer. And every one of those threads have *high* priority. That's like *really* bad.

Furthermore, it was based on ParallelProcessCount which is currently bugged, and because of that we were always allocating  256 (!!!) real OS threads for a MIDI synthesizer. CHRIST. (fix for ParallelProcessCount is separate)

I assume this is responsible for a ton of people's MIDI lag, it just murdering their PC's CPU scheduler.

The ability to multithread FluidSynth still exists as a CVar but it'll default to 1 and I don't think it makes sense to ever change it.

Also there was code to dynamically change the parameter, as far as I could test this just always crashed the process so out it goes.
2024-01-31 00:27:30 +01:00
metalgearsloth
5057ff97a3 Add MaxDimension property to Box2 (#4871)
* Add MaxDimension property to Box2

Sometimes I want to pretend it's a circle radius.

* a

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-01-30 13:28:05 +01:00
metalgearsloth
754d5a1fbb Add GetLocalPosition to controls (#4872)
* Add GetLocalPosition to controls

In my case I want the mouse's position inside of the control to show something under it unless there's a better way.

* weh

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-01-30 13:27:28 +01:00
Kara
7cee5b67a7 More easings (#4876)
* More easings

* yeah
2024-01-30 13:24:16 +01:00
Kara
21729e7e48 Smarter EntitySystem sawmill name conversion (#4875) 2024-01-29 22:52:39 +01:00
metalgearsloth
394d1e6cc2 Add global rectangles for controls (#4873)
* Add global rectangles for controls

Like my other PR used to check if mouse is inbounds on the control without doing some skrunkly caching with mousemove.

* weh

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-01-29 11:08:02 +01:00
metalgearsloth
f73d7f7285 Add dotted line drawing to screen handle (#4874)
Probably needs anti-aliasing but idk an easy way to do it.
2024-01-29 11:07:15 +01:00
DrSmugleaf
e505cfffd8 Fix TileEdgeOverlay flickering with tiles that are barely out of view (#4868) 2024-01-29 14:13:19 +11:00
collinlunn
d0fe3591ef Fixes parsing of ShaderBlendMode in ShaderPrototype (#4867)
* Fixes parsing of ShaderBlendMode in ShaderPrototype

* Changes ShaderPrototype parser to use case insensitive overload
2024-01-28 12:20:06 -08:00
James Simonson
1868f32457 Exposed "Bottom Margin" to itemlist (#4862)
* Exposed "Bottom Margin" to itemlist

* Changed from ItemBottomMargin to ItemSeparation

* Update ItemList.cs
2024-01-27 17:26:37 +01:00
metalgearsloth
ca82767b07 Version: 208.0.0 2024-01-25 17:47:44 +11:00
Kevin Zheng
76024330a7 Do not crash failing to load user keybindings (#4844) 2024-01-25 17:44:34 +11:00
metalgearsloth
21e8107eb1 Don't serialize metadata flags (#4861)
* Don't serialize metadata flags

Probably fine, saves us saving it to map files.

* Release notes
2024-01-25 17:44:16 +11:00
metalgearsloth
bcaa97a79b Version: 207.1.0 2024-01-21 18:55:44 +11:00
metalgearsloth
19727f6a25 Localise merge_grids command (#4850) 2024-01-21 18:20:21 +11:00
metalgearsloth
2102b96323 Update uploadfile dependencies (#4849)
* Update uploadfile dependencies

Also removed the 1 warning in there.

* Thanks rider
2024-01-21 18:20:13 +11:00
metalgearsloth
59b3ffda4f Add grid merging (#3627)
* Add grid merging

* More preliminary cleanup

* Fixes

* Fixes

* weh

* tweaks

* Tests

* weh

* Fix direction test

* Release notes
2024-01-21 17:41:04 +11:00
metalgearsloth
aae929966c Add NotNullWhen true to EntMan HasComp (#4848)
The proxy methods already have it but not the interface itself.
2024-01-20 15:03:47 +11:00
metalgearsloth
0cf842cacc Version: 207.0.0 2024-01-19 13:30:24 +11:00
metalgearsloth
96885c5b53 Quick lookup fix (#4847)
* Quick lookup fix

Forgor to push, just makes the Box2Rotated query use polyshape so it's accurate.

* a
2024-01-19 13:28:29 +11:00
metalgearsloth
d45ce7742e Entitylookup approx / shape changes (#4842)
* Entitylookup approx / shape changes

- Make the shape queries respect the approx flag.
- Make everything use shape queries.
- Hopefully reduce some of the internal cruft.
- Add some new methods I need for trade station.

* Bunch of deduplication

* Remove some more duplication abuse

* Remove intersection duplication

* Bunch more cleanup

* MapManager rejig

* Fix some stuff

* More fixes

* Release notes

* Fix TryFindGrid

* Sensor check

* Fix query

* Fix map queries

* More cleanup

* Fix whatever this is.

* also dis

* Fix entity query

* Smol optimisations

* Also this
2024-01-19 13:02:22 +11:00
Kot
50e27fd204 Fix full state updates (#4833)
* Clear entities seen by a session when the full update is requested

* Disable PVS budged for the full update

It helps preventing weird intermediate states when a client observes
themselves in a void with all the alert notifications going wild.
However it introduces a spike on CPU and networking.

* Add changelog line

* Move the changelog line to the proper place

* Update RELEASE-NOTES.md

* Revert "Disable PVS budged for the full update"

This reverts commit 6976ca04b8.

* Update RELEASE-NOTES.md

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-01-18 17:40:55 +13:00
Pieter-Jan Briers
af98933173 Isolate net messages in integration tests. (#4838)
* Isolate net messages in integration tests.

Integration tests don't use Lidgren to connect client and send, instead they just use some in-process channels to communicate. Because of this, the original implementation of net messages *directly* passed the net message instances between client and server instances. This caused issues whenever content would mutate data in a NetMessage after it "passed through".

Now we run the messages through WriteToBuffer() and ReadFromBuffer() so they pass through binary serialization. This means there's no more implicit sharing of the objects.

Note that this requires some trickery: Lidgren's message types have internal constructors. Really ideally we'd change the engine to make this more testable... but that's a content breaking change. Instead I just added InternalsVisibleTo to Lidgren so we can mess with it. We maintain the library ourselves anyways I can do what I want.

Fixes #4836

* Register Robust.UnitTesting as assembly for reflection.

This is necessary so that serialized types in the assembly can be picked up by NetSerializer.

Have to disable automatic reflection on all entity systems/components that tests register manually right now, because otherwise tests break.

* Stop shallow cloning specific net messages in integration tests.

This isn't necessary anymore now that we have a thorough fix.

* Wow I really forgot to copy-paste that line to the other side huh.

* Add test that serializer hash matches.

* Another test one I missed earlier.

* Changelog
2024-01-16 21:04:39 +01:00
metalgearsloth
e357dada65 Version: 206.0.0 2024-01-14 19:13:14 +11:00
metalgearsloth
19c48862e2 Entitylookup tweaks (#4808)
* Entitylookup tweaks

- Removed a dupe internal method.
- Removed some dupe internal code.
- Renamed some methods in line with local vs non-local.

* Update release

* Also this
2024-01-14 19:10:33 +11:00
metalgearsloth
448ce94b35 Make MIDI update rate comically low (#4830)
Was around this before but apparently avoiding lock contention not hard enough.
2024-01-14 18:19:56 +11:00
metalgearsloth
3681b7f0d5 Cleanup and tweak teleport commands (#4835)
* Cleanup and tweak teleport commands

- Cleanup the code duplication.
- Teleports to grid-local center instead.

* Release notes.
2024-01-14 17:44:02 +11:00
DrSmugleaf
7b3c883653 Add support for forcing prototypes in a file or directory to be parsed as abstract (#4829)
* Add support for forcing prototypes in a file or directory to be parsed as abstract

* Simplify mapping add abstract true call

* Fix docs

* Address reviews
2024-01-13 03:51:36 -08:00
Pieter-Jan Briers
c81004ddb4 Skip serialization generator with SkipRobustAnalyzer
Skips it in GLFW and Lidgren.
2024-01-12 23:18:53 +01:00
Pieter-Jan Briers
f844011348 Version: 205.0.0 2024-01-12 23:08:42 +01:00
Pieter-Jan Briers
0094040d68 Dependency update / fixes / skrungle bungle (#4825)
* Move to Central Package Management.

Allows us to store NuGet package versions all in one place. Yay!

* Update NuGet packages and fix code for changes.

Notable:

Changes to ILVerify.
Npgsql doesn't need hacks for inet anymore, now we need hacks to make the old code work with this new reality.
NUnit's analyzers are already complaining and I didn't even update it to 4.x yet.
TerraFX changed to GetLastSystemError so error handling had to be changed.
Buncha APIs have more NRT annotations.

* Remove dotnet-eng NuGet package source.

I genuinely don't know what this was for, and Central Package Management starts throwing warnings about it, so YEET.

* Fix double loading of assemblies due to ALC shenanigans.

Due to how the "sideloading" code for the ModLoader was set up, it would first try to load Microsoft.Extensions.Primitives from next to the content dll. But we already have that library in Robust!

Chaos ensues.

We now try to forcibly prioritize loading from the default ALC first to avoid this.

* Remove Robust.Physics project.

Never used.

* Remove erroneous NVorbis reference.

Should be VorbisPizza and otherwise wasn't used.

* Sandbox fixes

* Remove unused unit test package references.

Castle.Core and NUnit.ConsoleRunner.

* Update NUnit to 4.0.1

This requires replacing all the old assertion methods because they removed them 🥲

* Mute CA1416 (platform check) errors

TerraFX started annotating APIs with this and I can't be arsed to entertain this analyzer so out it goes.

* Fine ya cranky, no more CPM for Robust.Client.Injectors

* Changelog

* Oh so that's what dotnet-eng was used for. Yeah ok that makes sense.

* Central package management for remaining 2 robust projects

* Ok that was a bad idea let's just use NUnit 3 on the analyzer test project

* Oh right forgot to remove this one

* Update to a newer version of RemoteExecutor

* Disable RemoteExecutor test

https://github.com/dotnet/arcade/issues/8483 Yeah this package is not well maintained and clearly we can't rely on it.

* Fix immutable list serialization
2024-01-12 22:59:52 +01:00
metalgearsloth
dfb5369664 Version: 204.1.0 2024-01-12 12:57:42 +11:00
Pieter-Jan Briers
2462c906b3 Remove Herobrine
Idk according to the changelog I already did this once but clearly I didn't so ???
2024-01-11 02:01:23 +01:00
metalgearsloth
8cbc05840f Remove some unnecessary GetEntityQuery<T> (#4823) 2024-01-10 18:31:32 +01:00
Pieter-Jan Briers
510846321d BUI Improvements / fixes (#4826)
* Extension helper function for registering BUI events filtered to UI key.

Usages of events like BoundUIClosedEvent frequently did not check the UI key or do it improperly. This has, and will continue to cause, bugs.

The new helper (accessible as Subs.BuiEvents() from an entity system) makes it easy to subscribe to BUI events while also filtering to the correct UI key.

Also added missing SubscribeLocalEvent overloads with new handler types to EntitySystem.Subscriptions.

* Sprinkle [ViewVariables] around BUI

* Avoid buggy behavior if a Bound UI is closed inside the `BoundUIOpenedEvent` that's opening it.
2024-01-10 18:30:57 +01:00
metalgearsloth
ac60567583 Version: 204.0.0 2024-01-09 21:52:36 +11:00
metalgearsloth
7592997f4e Add random pick / take methods for sets (#4821)
* Add random pick / take methods for sets

I want it don't at me.

* cleanup (mraow)

* Revert "cleanup (mraow)"

This reverts commit e279957f21.

* flatpak

* notes
2024-01-09 00:37:19 +11:00
Pieter-Jan Briers
c68b3dccb7 Fix server NRE in console command completions.
Happens if you just type "tp:to<space>" into the console.

Toolshed can fail to provide completion results, in which case a null propagates and possibly crashes the server.
2024-01-08 11:18:11 +01:00
Pieter-Jan Briers
da2a2ce4ff Remove "Do not use from content" public members. 2024-01-07 04:43:59 +01:00
Pieter-Jan Briers
538418ea93 Fix exception when VVing non-networked components
The VV code forcibly dirties all components, which trips an assert for non-networked components.
2024-01-07 04:22:38 +01:00
Kara
2bf284bce8 Infer VVAccess.ReadWrite for all datafields (#4442) 2024-01-06 18:27:58 -08:00
Pieter-Jan Briers
b51cb06d53 Changelog for #4818 2024-01-06 19:23:58 +01:00
Kot
bab6c29fbe Add TextEdit.OnTextChanged event (#4818) 2024-01-06 18:59:19 +01:00
Pieter-Jan Briers
9502c86a65 Fix GLFW clipboard sometimes returning null.
Fixes #4817
2024-01-06 18:52:28 +01:00
Pieter-Jan Briers
359811f71e Fix incorrect IPostInjectInit in DebugConsole
IPostInjectInit does not run through IoCManager.InjectDependencies(). This meant the logger was not initialized, and loading failures in the console history would crash the client startup due to an NRE while trying to log a warning.

See https://github.com/space-wizards/space-station-14/issues/23624
2024-01-06 15:47:55 +01:00
DrSmugleaf
9de5840017 Make IEntityManager.EntityNetManager not nullable, make EntityManager abstract (#4445)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-01-07 00:05:02 +11:00
metalgearsloth
45af00096f Version: 203.0.0 2024-01-06 18:43:23 +11:00
Leon Friedrich
2f0283edb7 Freeze event bus dictionaries (#4727)
* Make component factory use frozen collections

* Fix integration tests

* Also freeze _entTraitDict

* A

* I love integration test setup logic

* Re-add public method

* Freeze event bus

* Remove per-component dictionary lookup on EntAddComponent

* release notes + fix test jank

* Fix merge

* fix tests

* Shutdown
2024-01-06 13:50:19 +11:00
Vasilis
d142393221 Clamp volume gain calculations (#4815) 2024-01-06 13:48:04 +11:00
metalgearsloth
ac147dc2a1 Remove physics sleep cancelling (#4813)
* Remove physics sleep cancelling

Was a bad idea, in the future I'm going to add even more asserts around this but for now I can fix content.

* Fix this
2024-01-06 13:38:41 +11:00
Pieter-Jan Briers
16af5cff9c Delete FodyWeavers.xsd
We used to use Fody. We don't anymore. Yeet.
2024-01-05 12:42:25 +01:00
ElectroJr
7725dbff78 Version: 202.1.1 2024-01-04 23:24:07 -05:00
Leon Friedrich
f2f8824678 Fix pvs error (#4812)
* Fix pvs error

* rename variable
2024-01-05 15:17:44 +11:00
Leon Friedrich
f2e9ed0b73 NetSyncEnabled fixes (#4811)
* NetSyncEnabled fixes

* A
2024-01-05 15:11:09 +11:00
Leon Friedrich
9e6d0aa44a Fix state handling bugs (#4810)
* Fix container state bug

* Fix _pendingReapplyNetStates bug
2024-01-05 15:11:00 +11:00
Leon Friedrich
1758fced75 Revert some grid/map related changes (#4809) 2024-01-05 13:48:43 +11:00
metalgearsloth
31e2a3efe4 Version: 202.1.0 2024-01-04 15:18:07 +11:00
metalgearsloth
371db7db2f Add GetLocalEntitiesIntersecting for griduid + tilesize (#4807) 2024-01-04 15:05:18 +11:00
metalgearsloth
bb23bc6acc Version: 202.0.0 2024-01-04 13:44:56 +11:00
metalgearsloth
270326e188 Add IComponentState / struct support (#4610)
Still boxes but hey at least the struct dream is real.
2024-01-04 13:42:47 +11:00
Leon Friedrich
7d27835543 Use frozen collections in component factory and EntityManager._entTraitDict (#4726)
* Make component factory use frozen collections

* Fix integration tests

* Also freeze _entTraitDict

* A

* I love integration test setup logic

* Re-add public method

* changes

* Better notes

* And the new method

* Fix notes
2024-01-04 12:21:24 +11:00
Leon Friedrich
cfb88b2e8e Arch related engine changes (#4806) 2024-01-04 12:18:03 +11:00
ElectroJr
8a83787d58 Version: 201.0.0 2024-01-03 19:16:36 -05:00
deathride58
6dd9f9e0f5 Blindness hotfix (engine side) (#4804) 2024-01-04 11:13:14 +11:00
Leon Friedrich
7f3445b1c6 Try fix pvs null exception (#4805) 2024-01-04 11:07:42 +11:00
metalgearsloth
5c15f26f09 Version: 200.0.0 2024-01-03 17:29:49 +11:00
faint
b8fbe5e465 Compiler error for AutoNetworkedField fields in a component without a AutoGenerateComponentState attribute (#4670)
* yeah

* remove autonetfield

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-01-03 17:24:08 +11:00
metalgearsloth
32049e34f2 Revert "Fix replay int overflow issues." (#4802)
This reverts commit 9877323195.
2024-01-03 16:16:15 +11:00
Leon Friedrich
26b09283f3 Prevent /tpgrid from moving grids to nullspace (#4798)
* Prevent `/tpgrid` from moving grids to nullspace

* Undo breaking change

* a
2024-01-03 16:07:33 +11:00
Leon Friedrich
6c6360e50a Avoid calling DirtyEntity() when repeatedly dirtying the same component. (#4797) 2024-01-03 16:06:10 +11:00
Pieter-Jan Briers
9877323195 Fix replay int overflow issues.
This came up while we were configuring stuff for the 24h round.
2024-01-03 01:18:50 +01:00
Letter N
b6f52f4c27 Implement OSWindowStyles.* on linux (#4790)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-01-03 01:04:03 +01:00
Leon Friedrich
c1789cbbaf Make MappingDataNode ordered (#4800) 2024-01-03 10:22:44 +11:00
metalgearsloth
f44f5b5a98 Version: 199.0.0 2024-01-02 17:31:55 +11:00
Leon Friedrich
f4faa1ad3d Run grid traversal on entity spawn (#4796)
* Run grid traversal on entity spawn

* Add test

* Fix tests
2024-01-02 17:25:13 +11:00
Leon Friedrich
97d03c6954 VisMask and EntityLifeStage changes (#4794) 2024-01-02 11:43:08 +11:00
Leon Friedrich
8accbc700a PVS fixes & cleanup (#4793)
* PVS fixes & cleanup

* More cleanup

* Move `CacheGlobalOverrides()` onto the main thread
2024-01-01 01:06:47 -08:00
metalgearsloth
9f013534b3 Add Box2i union for Vector2i (#4777) 2024-01-01 00:00:47 -08:00
Leon Friedrich
0b8febf6a6 FlushEntity() and EntityManager event changes (#4791) 2024-01-01 11:04:11 +11:00
Leon Friedrich
188985ecc2 Try prevent some PVS NREs (#4792) 2024-01-01 11:03:50 +11:00
Leon Friedrich
42434d1f49 Process pvs-leave in parallel (#4787)
* Process pvs-leave in parallel

* oops

* Try fix eternal tests
2024-01-01 05:53:24 +11:00
deathride58
87492cb0c3 zLibrary expansion - Grayscale, noise, and circular gradient utilities (#4784) 2023-12-30 18:35:53 -08:00
metalgearsloth
517ae7f1bd Better audio logs for gain (#4789)
* Better audio logs for gain

Need to track down the -10 gain setter.

* Release checks
2023-12-31 13:34:43 +11:00
ElectroJr
73357f022b Version: 198.1.0 2023-12-30 14:32:05 -05:00
Leon Friedrich
012faa0a16 Don't log errors when attempting to delete invalid entities (#4786) 2023-12-31 06:24:33 +11:00
Letter N
7a8abb3db7 debug ui ui update (#4782)
* UI Debugger: Make it look better

* cleanup

- Use GetOrNew()
- Use typeof(Control)
- Don't check string prefixes
2023-12-31 06:24:06 +11:00
Leon Friedrich
d6ce7e950b Add per-session force send pvs override (#4781) 2023-12-31 05:26:56 +11:00
metalgearsloth
e39b249070 Add gaussian extension for System.Random (#4776) 2023-12-31 05:26:39 +11:00
Letter N
5b889936be add styleclass on menubar so it can be styled (#4780)
* add styleclass on this

* public const string and concistent naming
2023-12-31 05:22:40 +11:00
deathride58
712809195d Allows getting the light rendertarget from overlays (#4783) 2023-12-31 05:17:42 +11:00
Leon Friedrich
64b5d6e323 Pvs cleanup & expose methods for benchmarking (#4779)
* Pvs cleanup & expose methods for benchmarking

* Fix tests

* Minimalist PvsData
2023-12-30 13:23:29 +11:00
metalgearsloth
baa607532d Version: 198.0.1 2023-12-29 13:46:29 +11:00
metalgearsloth
ff064dd859 Fix DefaultMagicAczProvider on FULL_RELEASE (#4778) 2023-12-29 13:45:24 +11:00
metalgearsloth
8305bffcac Version: 198.0.0 2023-12-29 11:51:31 +11:00
metalgearsloth
17a8972052 Fix GetEntitiesIntersecting for maps (#4764)
* Fix GetEntitiesIntersecting for maps

Still valid to call it in this way. Also added the collections versions because muh allocs.

* Add local ones too
2023-12-28 16:16:57 -08:00
metalgearsloth
56e03eae3e Add UI click sound support (#4705)
* Add UI click sound support

* Audio option

* Add Cvar

* Note and fix disabled sounds

* Fix double sound

* Comment removal

* ou

---------

Co-authored-by: Kara <lunarautomaton6@gmail.com>
2023-12-28 16:15:42 -08:00
Pieter-Jan Briers
1f948e17c4 Disable automatic UI scaling for non-main windows by default
This causes tons of problems with OpenDream's small popup windows, and even for SS14 it doesn't make much sense.

The behavior can be re-enabled per window by setting WindowRoot.DisableAutoScaling to false.
2023-12-28 03:38:20 +01:00
Pieter-Jan Briers
efaed42b24 DefaultMagicAczProvider refuses to work on FULL_RELEASE.
It's intended for development only so having it run on a FULL_RELEASE is probably a bug.
2023-12-28 02:31:34 +01:00
Pieter-Jan Briers
be2e31ff9d Print additional runtime diagnostics on server startup.
Brought to you by "I went insane debugging something stupid my own fault for 30 minutes"
2023-12-28 02:31:34 +01:00
ElectroJr
a891cacae5 Version: 197.1.0 2023-12-27 20:26:57 -05:00
Leon Friedrich
e9e0117402 Fix pvs bug (#4775)
* Fix pvs bug

* Moar asserts
2023-12-28 12:24:41 +11:00
Pieter-Jan Briers
24114d87e6 Add Full Hybrid ACZ
This is Hybrid ACZ (Content.Client.zip in server package) but with the ability to add extra files on top (OpenDream rsc)

Also added IStatusHost.InvalidateAcz().
2023-12-28 01:37:57 +01:00
Leon Friedrich
7cd78f3f4e Fix sprite animation bug (#4774)
* Fix sprite animation bug

* CL
2023-12-28 11:13:25 +11:00
ElectroJr
ca64aae7f0 Version: 197.0.0 2023-12-27 17:29:01 -05:00
ElectroJr
be0fb4250c Replace scale with override 2023-12-27 17:22:07 -05:00
Leon Friedrich
eba58cb893 Remove RobustTree & PVSCollection (#4759)
* Cut down RobustTree

* Better LoD

* Add PvsPriority flag

* Undo cvar name change

* reorganize grafana metrics

* Fix tests

* Fix replays

* Don't try process empty chunks

* Fix move benchmark

* Fix benchmark

* Remove obsolete audio methods

* Moar benchmarks

* Rename EntityData

* A

* B
2023-12-28 09:10:13 +11:00
ElectroJr
ced6d5a8b0 Version: 196.0.0 2023-12-27 16:58:33 -05:00
metalgearsloth
495671576e Add cycling for auto-animated states (#4763)
* Add cycling for auto-animated states

* Non-nullable and AdvanceFrameAnimation() tweak
2023-12-28 08:51:00 +11:00
Leon Friedrich
0611674915 Fix toolshed commands not working on reconnect (#4766)
* Fix toolshed commands not working on reconnect

* fix tests?

* space

* I love #if block errors
2023-12-28 08:48:33 +11:00
Leon Friedrich
e6ab61fe42 Prevent recursion in GridTraversalSystem (#4773) 2023-12-28 08:47:09 +11:00
Leon Friedrich
5232e61d38 Add multi-component Dirty() methods (#4770) 2023-12-28 08:20:06 +11:00
Leon Friedrich
a3e90aa04e Add NetEntity support to SpriteView (#4771) 2023-12-28 08:19:42 +11:00
Leon Friedrich
7a719d9f61 Stop toolshed from generating completions for non-toolshed commands (#4767)
* Stop generating toolshed completions for non-toolshed commands

* Better readability????

* Fix missing completions
2023-12-28 08:19:25 +11:00
Pieter-Jan Briers
61385b7c21 Fix WebView processes hanging around on unclean shutdown.
The processes now voluntarily exit themselves if the parent process exits.
2023-12-27 15:33:31 +01:00
Leon Friedrich
2503f9c4e0 Reduce Loc.GetString allocs (#4757)
* Reduce LocalizationManager allocs

* Make _fallbackCultures non-nullable

* Reduce allocs even more
2023-12-27 07:44:18 +11:00
ElectroJr
eb092e90ef Version: 195.0.1 2023-12-25 23:03:00 -05:00
Leon Friedrich
eb556c8728 Fix prototype manager enumeration exception (#4769) 2023-12-26 15:01:00 +11:00
ike709
2e398ded08 Fix audiostreams double-setting their source (#4768)
* Fix audiostreams double-setting their source

* loaded bool

* ok

---------

Co-authored-by: ike709 <ike709@github.com>
2023-12-26 14:51:27 +11:00
metalgearsloth
75cf15caa2 Fix placementmgr rotation (#4765)
If the entity is a self-deleting one this fixes it getting placed and immediately throwing.
2023-12-26 14:25:33 +11:00
ike709
8d7d6a26ba Fix playing audio streams (#4761)
* Fix playing audio streams

* mostly address review

---------

Co-authored-by: ike709 <ike709@github.com>
2023-12-25 18:36:24 -08:00
Pieter-Jan Briers
d593ffbb47 The small innocent change that broke the packaging workflow and surely there's nothing else going to break from this. 2023-12-25 18:18:53 +01:00
Pieter-Jan Briers
15b377dbd6 Version: 195.0.0 2023-12-25 18:00:13 +01:00
Leon Friedrich
d212479689 Don't defer adding joints (#4762) 2023-12-25 17:41:04 +11:00
Pieter-Jan Briers
2d3379d7f4 Bring CEF up to snuff (#4760) 2023-12-25 00:28:51 +01:00
Leon Friedrich
7b171b2212 Fix some physics bugs (#4746)
* Fix SetAwake()

* Remove bad debug assert

* EntityQuery & misc optimizations

* Remove bad(?) code, and add new test

* I love engine tests

* Add debug assert
2023-12-24 17:28:43 +11:00
metalgearsloth
7e8a5e199f Don't defer grid traversals (#4750)
* Don't defer grid traversals

Transform is in a slightly better state so we can remove this hacky thing.

It got to the point it was doing a bunch of unnecessary checks (no need to check container if you're just checking map / grid parent assuming no map / grid has containers).

The main concern now is: Does not deferring cause any issues, which it doesn't seem to (previously this mostly happened with transform state handling). On server-side the main worry is broadphasesystem seeing as this is being run manually to check for entities entering grids.

* Comment cleanup

* Obsolete MoveEvent .FromStateHandling

* Faster map position calculation

* Check Xform.GridUid instead of checking for `MapGridComponent`

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2023-12-24 16:33:00 +11:00
faint
ae74a5d7d4 PrototypeAttribute name parameter is optional now (#4672)
* PrototypeAttribute name parameter is optional now

* (^///^)

* suggestion

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

* add anther attribute ctor

* [Virtual] -> sealed
2023-12-23 17:36:44 +11:00
Leon Friedrich
9446ab76f9 Make integration tests fail when logging console errors (#4747)
* Make tests fail when logging console errors

* Add log convenience property
2023-12-23 13:53:23 +11:00
Kara
bb5cb10d57 Generic variants of AssertEqual methods (#4756)
* Generic variants of `AssertEqual` methods

* Remove invalid AssertionMethod
2023-12-23 13:52:08 +11:00
Pieter-Jan Briers
d6ccaee6d4 Version: 194.1.0 2023-12-22 22:25:26 +01:00
metalgearsloth
ce8982e371 Make audiostream methods public (#4752) 2023-12-22 21:14:08 +01:00
metalgearsloth
371cd7ddf1 Reduce robust mapped string allocs (#4754) 2023-12-22 17:08:07 +01:00
metalgearsloth
6573531d5d GetEntitiesInRange flag + TryDelta (#4749)
Needed for tesla too lazy to split
2023-12-22 14:17:46 +11:00
Leon Friedrich
b1e1f27aa3 Add stack trace to audio errors (#4748) 2023-12-22 13:23:28 +11:00
Jordan Dominion
80ce583454 Minor note about TGS using the watchdog API (#4742)
* Minor note about TGS using the watchdog API

* Move comment to new line

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

---------

Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
2023-12-22 09:20:37 +11:00
ElectroJr
2b4a428f9f Version: 194.0.2 2023-12-20 01:57:01 -05:00
Leon Friedrich
41ee330828 Add more PVS null checks (#4743) 2023-12-20 17:54:24 +11:00
ElectroJr
f82452c855 Version: 194.0.1 2023-12-19 19:33:05 -05:00
Leon Friedrich
785e2f84f6 Turn EntityData struct into a class (#4729)
* Turn EntityData struct into a class

* Stop overbudget dictionary removals

* Merge additional logs

* More logging
2023-12-20 11:32:14 +11:00
Leon Friedrich
2bdc1d77ca Add grid deletion/reparenting logs (#4741) 2023-12-20 11:24:40 +11:00
metalgearsloth
51205beb56 Fix audio occlusion (#4738)
Saw this a week ago but forgot to PR.
2023-12-20 11:17:30 +11:00
metalgearsloth
ef325b4780 Fix SetPositionInParent throwing for end position (#4740)
If a child is at the end spot this will ensure it doesn't throw.
2023-12-20 11:15:20 +11:00
ElectroJr
ede337a869 Version: 194.0.0 2023-12-18 20:29:41 -05:00
Kara
d416344aef MoveEvent broadcast -> C# event (#4732)
* MoveEvent broadcast -> C# event

* Broadcast MoveEvent -> C# event

* oops

* forgob

* release notes

* Update description of moveevent
2023-12-19 12:26:13 +11:00
Kara
fb98eb1a0c TreeRecursiveMoveEvent -> C# event (#4733)
* TreeRecursiveMoveEvent -> C# event

* entity query

* Make query protected
2023-12-19 12:19:19 +11:00
Leon Friedrich
ed9a0b4812 Fix freezing while loading ogg files (#4731)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2023-12-19 00:45:21 +01:00
ElectroJr
e7c6151310 Version: 193.2.0 2023-12-17 20:39:52 -05:00
Leon Friedrich
005c2e784a Add more PVS logs (#4730) 2023-12-18 12:36:48 +11:00
ElectroJr
1009dd3ea0 Version: 193.1.1 2023-12-16 23:29:50 -05:00
ElectroJr
87b82160b0 Fix #if FULL_RELEASE error 2023-12-16 23:28:58 -05:00
ElectroJr
fc318c9ecd Version: 193.1.0 2023-12-16 22:56:16 -05:00
Leon Friedrich
ac957ca7fc Try fix PVS bugs (#4728)
* Try fix PVS bugs

* change order
2023-12-17 14:55:46 +11:00
Leon Friedrich
1bdd82b0bf Raise ECS event on prototype-reload (#4724)
* Raise system event on prototype-reload

* Oops
2023-12-17 08:30:53 +11:00
Leon Friedrich
0150c5e6ff Add IPrototypeManager.GetInstances() (#4723) 2023-12-17 08:29:31 +11:00
Pieter-Jan Briers
b2cc90d00f Convert static readonly dictionaries to FrozenDictionary (#4722)
* Convert static readonly dictionaries to FrozenDictionary

* Also `IReadOnlyDictionary`
2023-12-17 04:47:01 +11:00
Leon Friedrich
1f8b89e92f Add frozen collections to sandbox and add yaml type serializers (#4721) 2023-12-16 18:36:58 +01:00
ElectroJr
33d394295e Version: 193.0.0 2023-12-16 11:42:37 -05:00
Leon Friedrich
4934a9c5a5 Combine PVS dictionaries (#4693)
* Try combine dictionaries

* Remove last-ack

* Remove lastSent

* Use structs

* Don't clear previous-sent on deletion

* Pass data into ProcessEntry

* Store visibility data in EntityData

* Turn ToSend into hashset

* Add EntityStringRepresentation constructors

* Add MetaDataComponent to EntityData

* Remove metadata cache

* Create GetStates partial class

* add ToSend partial class

* fix debug asserts

* Cleanup

* Rename partial class

* Try to optimize `ProcessLeavePvs`

* Add ProcessLeavePvsBenchmark

* Turn HashSet into List

* Collect expensive asserts into one block

* Revert "Add ProcessLeavePvsBenchmark"

This reverts commit 93d984e929.

* remove comment
2023-12-17 03:37:48 +11:00
Leon Friedrich
91ebc3eb02 Use FrozenDictionary for prototype instances (#4719)
* Use FrozenDictionary for prototype instances

* Add assert

* Change log to verbose

* RStopwatch.StartNew

* Rename TryGetKindInstances
2023-12-17 03:22:18 +11:00
Leon Friedrich
bc84590a33 Make ChildEnumerator out value non-nullable (#4707)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2023-12-17 03:10:20 +11:00
Kelrak
e3944dc6fb Should resolve some error spam in non EFX systems (#4704)
* Should resolve some error spam in non EFX systems

* Exit early if not supported
2023-12-16 15:57:59 +11:00
Leon Friedrich
6a77f4c27b Use frozen dictionaries for prototype kinds (#4718)
* Use frozen dictionaries for prototype kinds

* More logs

* AAAA

* Change log to verbose

* RStopwatch.StartNew()
2023-12-16 15:51:55 +11:00
Leon Friedrich
6246ae412e Make RobustMappedStringSerializer use frozen dictionaries (#4717)
* Make RobustMappedStringSerializer use frozen dictionaries

* Update RobustMappedStringSerializer.MappedStringDict.cs

* Change log to verbose

* RStopwatch.StartNew()
2023-12-16 15:48:57 +11:00
ElectroJr
ac86accc20 Version: 192.0.0 2023-12-15 23:43:53 -05:00
Leon Friedrich
19ff7f25ca Slightly speed up IDependencyCollection (#4716) 2023-12-16 00:23:12 +01:00
Pieter-Jan Briers
3f83733a03 Decode ogg files as shorts instead of floats (#4715) 2023-12-15 20:48:07 +01:00
Leon Friedrich
438fed2f0e Make TryGetEntity protected (#4713)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2023-12-15 19:40:43 +01:00
Pieter-Jan Briers
816a535a92 Fix obsoletion warnings for BinaryFormatter stuff
This became an outright warning in .NET 8. Into the trash, never used it.
2023-12-15 19:37:13 +01:00
Pieter-Jan Briers
01df42aa8f Fix ICommonSession.ConnectedClient obsoletion warnings 2023-12-15 19:34:30 +01:00
Pieter-Jan Briers
a200d73ef9 Use FrozenDictionary in DependencyCollection.
Did not test the perf of this at all but it's the perfect use case so...
2023-12-15 19:28:56 +01:00
metalgearsloth
8d30735ffb Run ProcessQueuedAcks asynchronously (#4696) 2023-12-15 19:10:09 +01:00
Pieter-Jan Briers
2686150f9d Version: 191.0.1 2023-12-15 19:08:35 +01:00
Pieter-Jan Briers
d720e9393b Fix .NET 8 sandbox.
Oops.
2023-12-15 19:08:06 +01:00
Pieter-Jan Briers
f5b1c26bec Update Lidgren to fix nullable compilation issue 2023-12-15 18:28:10 +01:00
Pieter-Jan Briers
3204002c72 Version: 191.0.0 2023-12-15 18:09:15 +01:00
Pieter-Jan Briers
4c79d0c6d0 .NET 8 (#4712) 2023-12-15 18:07:41 +01:00
Pieter-Jan Briers
e0d38fb8bd Fix TryGetResource silently eating exceptions
why god
2023-12-15 16:13:17 +01:00
Leon Friedrich
250f6ca7db Obsolete TransformComponent.ChildEntities (#4706)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2023-12-14 17:50:29 +11:00
KP
773365c185 Add a workaround for MacOS' bad CoreAudio OpenAL gain behaviour (#4710)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2023-12-14 17:41:52 +11:00
metalgearsloth
9a1e6af586 Remove DeferMoveEvent (#4709) 2023-12-14 17:20:02 +11:00
Pieter-Jan Briers
dc96318379 Fix Lidgren compile on GitHub hopefully 2023-12-13 19:08:08 +01:00
Pieter-Jan Briers
31a3f145de Add [ViewVariables] to BaseButton.EnableAllKeybinds
Makes it show up in the devwindow UI inspector.
2023-12-13 00:28:33 +01:00
Pieter-Jan Briers
331e1fcc81 Update Lidgren to v0.2.7
This adds NRT annotations. Fix compatibility with those.
2023-12-13 00:06:00 +01:00
Leon Friedrich
dc7a51e582 Fix showrays networking (#4701) 2023-12-12 20:17:36 +11:00
metalgearsloth
bfe8e687da Fix audio params variation (#4699) 2023-12-12 20:16:43 +11:00
metalgearsloth
04b6d60d76 Fix TileEdgeOverlay drawing over grids (#4698) 2023-12-12 20:13:20 +11:00
Leon Friedrich
5bc5bfd58a Add more implicit Entity<T> casts (#4552) 2023-12-12 13:27:31 +11:00
TemporalOroboros
56899b4e64 Some ContainerManagerComponent ECSing (#4622) 2023-12-12 13:24:18 +11:00
DrSmugleaf
a23915e0dd Make MapLoaderSystem.GetSaveData public (#4702) 2023-12-12 13:09:39 +11:00
metalgearsloth
726d91c5e8 Version: 190.1.1 2023-12-11 21:20:58 +11:00
metalgearsloth
d8a8783680 Revert "Avoid recontruction broadphase job every tick (#4653)" (#4695) 2023-12-11 20:11:57 +11:00
metalgearsloth
8839dd9a3b Version: 190.1.0 2023-12-11 19:56:10 +11:00
metalgearsloth
0296d9635c Fix some grid setting asserts (#4580) 2023-12-11 19:49:55 +11:00
metalgearsloth
f24d9751d4 Add GetRotation for matrices and better precision (#4691) 2023-12-11 18:40:57 +11:00
wixoa
58ac82ae55 Add OnGrabbed and OnReleased events to the Slider control (#4694) 2023-12-11 18:39:09 +11:00
metalgearsloth
b9130bf236 Version: 190.0.0 2023-12-10 21:33:56 +11:00
metalgearsloth
a2cd33afe5 Add better OpenAL logging (#4687) 2023-12-10 21:25:43 +11:00
metalgearsloth
1772651049 Fix color_picker shader compilation (#4690) 2023-12-10 21:21:55 +11:00
Leon Friedrich
826fa4d131 Ensure shaders parameters are updated when swapping instances (#4689) 2023-12-10 20:36:23 +11:00
Leon Friedrich
0b712ae86c Add colour gradients to sliders (#4688) 2023-12-10 20:35:43 +11:00
Łukasz Mędrek
22528fc484 Fix colour space translation feedback loop(#3839) (#4681) 2023-12-10 14:10:43 +11:00
Leon Friedrich
20a411e6ce Remove non-networked comps from NetComponents dictionary (#4686) 2023-12-10 13:56:34 +11:00
Leon Friedrich
6f9ed8a242 Stop terminating entities from being prematurely detached to nullspace (#4675) 2023-12-10 13:18:42 +11:00
Łukasz Mędrek
790f4c1309 Fix HSV and HSL producing black color on hue 360 (#4682) 2023-12-10 13:14:15 +11:00
metalgearsloth
0b62cb6445 Change netsyncenabled to an assert (#4604) 2023-12-10 13:03:25 +11:00
metalgearsloth
9d0f4d8a08 Update release notes (#4684) 2023-12-10 12:49:47 +11:00
metalgearsloth
5069b0ccf9 Version: 189.0.0 2023-12-10 12:44:00 +11:00
chromiumboy
12cfdb2175 Tweaked how ExpandPvsEvent is raised (#4665) 2023-12-10 12:35:00 +11:00
metalgearsloth
b5e079815d Add MIDI semaphore (#4643) 2023-12-10 12:34:24 +11:00
metalgearsloth
ca3a3279c5 Set priorGain to 0 if gain is 0 (#4680) 2023-12-10 12:31:06 +11:00
Leon Friedrich
003752a161 Improve yaml linter error messages (#4683) 2023-12-10 12:18:52 +11:00
Leon Friedrich
55e51cba9c Fix client-side entity error spam (#4673) 2023-12-10 12:17:46 +11:00
Leon Friedrich
2cd829f4f6 SpriteView Modulation (#4584) 2023-12-10 12:02:21 +11:00
metalgearsloth
43138669ec Network base not adjusted audio params (#4679) 2023-12-09 17:12:09 +11:00
metalgearsloth
3fe30bc00f Version: 188.0.0 2023-12-09 15:17:37 +11:00
metalgearsloth
3ccbdeac6a Fix GetDimensions for screenhandle (#4677) 2023-12-09 15:04:45 +11:00
metalgearsloth
9eb9c91da6 Fix predicted audio not using adjust params (#4676) 2023-12-09 14:59:22 +11:00
metalgearsloth
28d2b47a2c Log errors on spawning audio to deleting ents (#4629) 2023-12-09 14:30:18 +11:00
metalgearsloth
049ffa05e4 Remove EntityQuery<T> from MapVelocity API (#4648) 2023-12-09 14:12:04 +11:00
metalgearsloth
2f36a0a5fc Change midi volume to gain (#4639) 2023-12-09 14:03:01 +11:00
Kelrak
41d03db59d Possible fix to some audio issues (#4640) 2023-12-09 13:39:09 +11:00
Łukasz Mędrek
68df887a65 Fix sorting order in entity spawn panel (#3767) (#4671) 2023-12-09 12:46:57 +11:00
metalgearsloth
e0bbcd7b08 Return null buffered audio on exception (#4624) 2023-12-09 12:44:16 +11:00
metalgearsloth
525815427e Version: 187.2.0 2023-12-06 20:07:30 +11:00
Tom Richardson
70224ac100 Update map physics after events (#4660)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2023-12-06 20:02:10 +11:00
metalgearsloth
dabb090dc2 Version: 187.1.2 2023-12-06 14:00:54 +11:00
metalgearsloth
ace8334a3e Bandaid physics contacts getting modified during collision (#4666) 2023-12-06 13:36:33 +11:00
metalgearsloth
d8e70b4d52 Version: 187.1.1 2023-12-05 00:44:56 +11:00
metalgearsloth
2fca0e03ee Don't RegenerateContacts for disabled bodies (#4658) 2023-12-05 00:42:09 +11:00
metalgearsloth
b6980964b6 Revert physics jobs (#4663) 2023-12-05 00:30:02 +11:00
metalgearsloth
34d02256fd Version: 187.1.0 2023-12-03 00:59:49 +11:00
metalgearsloth
34637fb430 Avoid recontruction broadphase job every tick (#4653) 2023-12-03 00:49:05 +11:00
metalgearsloth
d905ef2a50 Apply default audio to MIDIs (#4626) 2023-12-03 00:34:28 +11:00
metalgearsloth
c3f7ef1b5c Version: 187.0.0 2023-12-02 19:38:56 +11:00
metalgearsloth
ced2a5c6cd Make PVS overrides less bad and fix audio (#4656) 2023-12-02 19:36:55 +11:00
metalgearsloth
9ec927543f Remove audio logging (#4654) 2023-12-02 10:43:06 +11:00
Pieter-Jan Briers
215fc8c229 Begone, toolshed logs
They're verbose now
2023-12-01 22:30:44 +01:00
Pieter-Jan Briers
f82ff9e581 Improve error message for network failing to initialize.
The "make sure port XXXX is open" thing now only appears if the error was an actual "address in use" error.
2023-12-01 22:18:38 +01:00
metalgearsloth
906f4598a2 Version: 186.1.0 2023-12-01 20:37:02 +11:00
metalgearsloth
0eb3c37bd8 Add detailed audio logging (#4652) 2023-12-01 20:33:29 +11:00
metalgearsloth
9cc7cf80ba Version: 186.0.0 2023-12-01 19:14:21 +11:00
metalgearsloth
7ba02b5ca6 Store audio on its own map (#4651) 2023-12-01 18:48:33 +11:00
metalgearsloth
3ca7121f5b Don't stop playing audio on game disposal (#4649) 2023-12-01 18:45:37 +11:00
metalgearsloth
d3b31c1d58 Fix out of range MIDI (#4650) 2023-12-01 17:36:58 +11:00
metalgearsloth
92b5bb4660 Version: 185.2.0 2023-12-01 00:18:51 +11:00
metalgearsloth
33caf9c1ba Cap MIDI update rate (#4644) 2023-11-30 21:56:56 +11:00
metalgearsloth
58da8a6001 Fix deleted entity spam for midis (#4647) 2023-11-30 21:56:11 +11:00
metalgearsloth
962f5dc650 Version: 185.1.1 2023-11-30 11:24:34 +11:00
metalgearsloth
c324562513 Fix audio z-offset not applying correctly (#4641) 2023-11-30 10:10:33 +11:00
metalgearsloth
f5a2a710f0 Nuke some non-approx grid queries (#4637) 2023-11-29 22:55:33 +11:00
metalgearsloth
357283e2bc Version: 185.1.0 2023-11-29 16:40:44 +11:00
metalgearsloth
5991bfa106 Fix audio position floating point imprecision (#4634) 2023-11-29 16:39:23 +11:00
metalgearsloth
4160b120e0 Set listener velocity for audio (#4635) 2023-11-29 16:31:22 +11:00
metalgearsloth
98a1fa1fba Version: 185.0.0 2023-11-29 11:02:53 +11:00
metalgearsloth
fb08451849 Replace Parallel.For with ParallelManager (#4588) 2023-11-29 10:57:52 +11:00
metalgearsloth
ebea0d7572 Add grid audio flag (#4632) 2023-11-29 10:19:17 +11:00
metalgearsloth
eb6f28cce0 Version: 184.1.0 2023-11-28 23:55:55 +11:00
metalgearsloth
a1d02d7c55 Add gain setter for audio params + API cleanup (#4627) 2023-11-28 22:54:19 +11:00
metalgearsloth
777ab85cff Version: 184.0.1 2023-11-28 20:46:53 +11:00
metalgearsloth
d33a8465b0 Fix global audio (#4625) 2023-11-28 20:44:40 +11:00
metalgearsloth
6572fdb404 Midi tweaks (#4618) 2023-11-28 20:39:59 +11:00
Uriende
6273b1b80d Only change the offset if has already started (#4619) 2023-11-28 20:29:19 +11:00
Nemanja
a09a60efe9 Adjust how KeyBindUp retreives the focused control (#4620) 2023-11-28 19:22:05 +11:00
metalgearsloth
d3339964ee Version: 184.0.0 2023-11-28 19:13:18 +11:00
metalgearsloth
3ffef625ec Pool MsgState streams (#4582) 2023-11-28 19:10:30 +11:00
metalgearsloth
4fd9b2bc3b Add another GetEntitiesInRange overload (#4587) 2023-11-28 14:18:40 +11:00
metalgearsloth
adc5051841 Version: 183.0.0 2023-11-27 22:14:46 +11:00
metalgearsloth
2733435218 Audio rework unrevert + audio packaging (#4555)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2023-11-27 22:12:26 +11:00
metalgearsloth
24b0165ec9 Revert Arch (#4613) 2023-11-27 21:41:01 +11:00
metalgearsloth
7b9aa09b18 Version: 182.1.1 2023-11-26 13:36:08 +11:00
metalgearsloth
7bee6f6fc1 Update Arch (#4609) 2023-11-26 13:35:26 +11:00
metalgearsloth
ff75495894 Version: 182.1.0 2023-11-26 12:45:21 +11:00
metalgearsloth
4cb51af733 Add arch trimming back (#4608) 2023-11-26 12:35:13 +11:00
Leon Friedrich
89c1e90646 Add IRobustRandom.SetSeed() (#4606) 2023-11-25 15:32:16 -08:00
metalgearsloth
b6cadfedd5 Update arch (#4605) 2023-11-25 14:56:56 +11:00
metalgearsloth
9f57b705d7 Version: 182.0.0 2023-11-24 00:21:00 +11:00
metalgearsloth
68be9712ad Add entity gen to hashcode (#4601) 2023-11-24 00:19:58 +11:00
metalgearsloth
aaa446254c Version: 181.0.2 2023-11-23 23:43:47 +11:00
metalgearsloth
5e2d2ab317 Fix too many pointlights causing blackscreen (#4599) 2023-11-23 23:39:51 +11:00
metalgearsloth
20ae63fbbd Replace tile intersecting with enumerator (#4595) 2023-11-23 22:36:40 +11:00
metalgearsloth
a92c0cbef4 Fix nullable comps being raised for client gamestates (#4596) 2023-11-23 22:07:27 +11:00
metalgearsloth
95649a2dd0 Version: 181.0.1 2023-11-23 16:53:03 +11:00
metalgearsloth
861807f8b4 Fix HasComp(uid, Type) (#4594) 2023-11-23 16:52:31 +11:00
metalgearsloth
bd73f1c05a Version: 181.0.0 2023-11-23 15:28:35 +11:00
metalgearsloth
7dce51e2cf Arch PR two electric boogaloo (#4388)
Co-authored-by: DrSmugleaf <drsmugleaf@gmail.com>
Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2023-11-23 14:29:37 +11:00
DrSmugleaf
d9b0f3a227 Version: 180.2.1 2023-11-22 17:02:13 -08:00
Vasilis
05766a2eaa Fix not using dotnet 7 for actions in engine (#4591) 2023-11-23 00:43:15 +01:00
metalgearsloth
a761fbc09e Version: 180.2.0 2023-11-22 22:00:05 +11:00
metalgearsloth
f69440b3f2 Minor PVS stuff (#4573)
Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2023-11-22 21:54:07 +11:00
Leon Friedrich
b459d2ce21 Add more map system helper methods. (#4589) 2023-11-22 21:51:46 +11:00
Leon Friedrich
202182e3d4 Add new EnsureEntity variants (#4586) 2023-11-20 17:44:36 +11:00
metalgearsloth
96cb52e5d2 Version: 180.1.0 2023-11-20 16:31:41 +11:00
metalgearsloth
82e0c0baeb Add cvar for lidgren pool size (#4585) 2023-11-20 16:28:13 +11:00
Leon Friedrich
54d6552164 Fix shape lookups for non-hard fixtures (#4583) 2023-11-20 16:15:27 +11:00
Jordan Dominion
c21b6c993c Fix potential error when writing runtime log (#4575) 2023-11-19 15:47:53 +01:00
metalgearsloth
2fe4a8b859 Add map name to lsmap (#4576) 2023-11-19 15:47:19 +01:00
metalgearsloth
8325966dbb Fix contact constraints allocs (#4581) 2023-11-19 15:47:07 +01:00
metalgearsloth
2459a9d688 Version: 180.0.0 2023-11-19 15:09:59 +11:00
Leon Friedrich
2cd2d1edd6 Add misc helpful methods (#4577) 2023-11-19 15:05:26 +11:00
metalgearsloth
b982350851 Use NetEntities for F3 panel (#4571) 2023-11-16 20:53:23 +11:00
DrSmugleaf
4a50bc2154 Add AddEntitiesIntersecting for phys shapes, change float range overload to use circles, remove obsolete methods (#4572) 2023-11-16 20:44:21 +11:00
metalgearsloth
4c85e205b9 Add chain support to TryGetNearest (#4567) 2023-11-16 20:40:23 +11:00
ElectroJr
0b447d9d82 Version: 179.0.0 2023-11-12 13:35:17 -05:00
Leon Friedrich
ceb205ad52 Allow per-eye lighting toggling. (#4569) 2023-11-13 05:30:00 +11:00
Leon Friedrich
a48ff3dbf1 Fix PlacementManager bug (#4568) 2023-11-13 05:29:18 +11:00
DrSmugleaf
2b85fa88c1 Print stack trace when adding a component while iterating net comps in ResetPredictedEntities (#4541) 2023-11-13 05:26:13 +11:00
Leon Friedrich
19564a421b Fix deserialization of empty grid chunks (#4565) 2023-11-13 05:22:20 +11:00
Leon Friedrich
b3f0e467ee Improve UnknownPrototypeException error message (#4566) 2023-11-13 05:22:05 +11:00
Leon Friedrich
216292c849 Make EyeComponent.Eye not nullable (#4564) 2023-11-13 04:20:09 +11:00
Jerry
68753d15e0 Fix stack overflow error on planet station (#4563) 2023-11-12 13:28:35 +11:00
ElectroJr
2a357051ae Version: 178.0.0 2023-11-10 20:58:35 -05:00
Leon Friedrich
58e0b62145 Merge ActorSystem and IPlayerManager (#4530) 2023-11-11 12:50:21 +11:00
Leon Friedrich
14cc273997 Add NetListAsArray<T>.Value to sandbox whitelist (#4537) 2023-11-11 11:57:58 +11:00
DrSmugleaf
93f4428635 Version: 177.0.0 2023-11-08 00:21:12 -08:00
DrSmugleaf
164bf68aca Move TryGetUi/TryToggleUi/ToggleUi/TryOpen/OpenUi/TryClose/CloseUi to SharedUserInterfaceSystem (#4562) 2023-11-08 16:52:38 +11:00
Leon Friedrich
773b87672b Fix terminating entity reparenting bug (#4549) 2023-11-08 15:39:08 +11:00
metalgearsloth
eecf834039 Fix PlacementManager warnings (#4557) 2023-11-08 15:34:54 +11:00
Leon Friedrich
325fe46aa3 Add More Entity<T> query methods (#4550) 2023-11-07 20:24:42 -08:00
metalgearsloth
2f6c29ab43 Add GetMapCoordinates to TransformSystem (#4556) 2023-11-07 20:23:05 -08:00
metalgearsloth
aab1a2dba9 Fix transform test warnings (#4558) 2023-11-07 20:22:07 -08:00
DrSmugleaf
f36fbd9c83 Fix inverted GetAllMapGrids mapid check (#4561) 2023-11-07 14:26:01 -08:00
metalgearsloth
126c863f45 Hotfix containersystem.remove (#4560) 2023-11-07 16:18:08 +11:00
Leon Friedrich
618a8491bf Add BeforeApplyState event to replay playback (#4536) 2023-11-07 15:07:26 +11:00
Leon Friedrich
2743b64a2b Mark container methods as obsolete (#4551) 2023-11-07 15:05:32 +11:00
Leon Friedrich
28cc91934c Change PVS error log into warning (#4548) 2023-11-07 15:02:13 +11:00
metalgearsloth
eadfcd4c09 Specify RichTextLabel VAlignment as Center (#4520) 2023-11-07 10:27:49 +11:00
metalgearsloth
7871b0010e Version: 176.0.0 2023-11-07 09:51:32 +11:00
metalgearsloth
3da04ed17e Robust.Packaging updates (#4547) 2023-11-07 09:36:33 +11:00
metalgearsloth
170d192791 Revert audio rework (#4554) 2023-11-07 09:34:09 +11:00
Leon Friedrich
dcd9939554 Fix PVS initial list capacity bug (#4546) 2023-11-06 04:41:56 +11:00
Leon Friedrich
98ef58eca6 Add max game state buffer size cvar (#4543) 2023-11-05 02:58:48 +11:00
metalgearsloth
ab1e99a0df Add GetEntitiesInRange that takes in a set (#4544) 2023-11-04 15:02:20 +11:00
Leon Friedrich
499c236798 Fix replay lerp error spam (#4534) 2023-10-30 04:29:47 +11:00
metalgearsloth
8dc2345ceb Fix audio position on first tick (#4533) 2023-10-29 15:30:59 +11:00
metalgearsloth
9b04270178 Version: 175.0.0 2023-10-29 15:03:09 +11:00
metalgearsloth
d75dbc901f Audio rework (#4421) 2023-10-29 14:58:19 +11:00
Leon Friedrich
19a3e82848 Cache prototype data for IEntityManager.IsDefault() (#4531) 2023-10-29 12:54:52 +11:00
Leon Friedrich
911abf2693 Remove empty planet-map chunks (#4529) 2023-10-29 12:52:03 +11:00
ElectroJr
f5874ea402 Version: 174.0.0 2023-10-28 13:26:49 -04:00
metalgearsloth
b486ef885c Add NextAngle for System.Random (#4522) 2023-10-29 04:22:32 +11:00
metalgearsloth
9d55d77e48 Sprite GetFrame (#4528) 2023-10-29 04:21:52 +11:00
Leon Friedrich
5af3cb969c Move ActorComponent to shared (#4527) 2023-10-29 04:21:09 +11:00
metalgearsloth
429bc806dc Version: 173.1.0 2023-10-28 15:36:26 +11:00
metalgearsloth
81484699a8 Add chain shapes (#4523)
* Add chain shapes

* rar only

* that too

* weh

* a

* Update Robust.Shared/Physics/Dynamics/Contacts/Contact.cs

Co-authored-by: Moony <moony@hellomouse.net>

* Update Robust.Shared/Physics/Dynamics/Contacts/Contact.cs

Co-authored-by: Moony <moony@hellomouse.net>

---------

Co-authored-by: Moony <moony@hellomouse.net>
2023-10-28 15:29:30 +11:00
metalgearsloth
7cad8d5ba3 Version: 173.0.0 2023-10-28 14:02:06 +11:00
Leon Friedrich
3aa04a3c86 Fix grid chunk bugs (#4525)
* Fix grid rendering

* Use TileChangedEvent

* Other empty chunk fixes

* Remove assert

Good ol integration tests at it again, adding invalid components
2023-10-28 13:57:54 +11:00
metalgearsloth
9750b113c8 Version: 172.0.0 2023-10-24 20:22:31 +11:00
Leon Friedrich
5a6c4220fc IPlayerManager refactor (#4518) 2023-10-24 20:18:58 +11:00
Leon Friedrich
b2d389f184 Remove TryLifestage() helpers (#4519) 2023-10-24 18:46:46 +11:00
Leon Friedrich
ad0cb05dd6 Add EnsureComponent(ref Entity<T?>) (#4516) 2023-10-24 17:19:38 +11:00
Leon Friedrich
ad134d9e4e Fix game state logging spam (#4517) 2023-10-24 14:09:55 +11:00
Leon Friedrich
be33bc2219 Re-add force ack threshold (#4423) and fix bugs. (#4438)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2023-10-22 23:27:15 +11:00
metalgearsloth
aa2fd2107d Add mgs to physics codeowners (#4510)
Please ping me for this is creates more work if you do not ping me.
2023-10-22 05:03:47 -07:00
metalgearsloth
554e0777b1 Version: 171.0.0 2023-10-22 16:58:48 +11:00
metalgearsloth
21b7c5f93e Cleanup relays on joint deletion (#4497) 2023-10-22 16:53:10 +11:00
Leon Friedrich
9e5c1e9c95 Change place-next-to helper methods (#4506) 2023-10-22 16:52:58 +11:00
Leon Friedrich
6825f09fb9 Set EntityLastModifiedTick when an entity spawns (#4509) 2023-10-22 16:51:40 +11:00
DrSmugleaf
58e3a4eb4a Version: 170.0.0 2023-10-21 14:31:08 -07:00
DrSmugleaf
9a342f0d11 Fix double delete entity command, fix not being able to delete individual entities (#4508) 2023-10-21 14:19:34 -07:00
DrSmugleaf
f754ddb96d Remove all usages of obsolete Dirty method, remove some obsoleted methods (#4500) 2023-10-21 14:19:07 -07:00
Leon Friedrich
7feede0d95 Fix duplicate command error (#4507) 2023-10-21 14:18:50 -07:00
Jordan Dominion
ea152366e3 Allow deletion of FileLogHandler logs while engine is running (#4501) 2023-10-21 15:07:10 +02:00
DrSmugleaf
ab47d4e009 Version: 169.0.1 2023-10-21 03:55:02 -07:00
DrSmugleaf
81b2a3825e Fix help command, let the client know about server toolshed commands (#4502) 2023-10-21 03:54:17 -07:00
DrSmugleaf
56d850f389 Version: 169.0.0 2023-10-19 12:27:27 -07:00
DrSmugleaf
b737ecf9b3 Add generic EntityUid, remove some usages of .Owner (#4498) 2023-10-19 12:23:48 -07:00
DrSmugleaf
ed5223b592 Remove by-refness subscription test (#4499) 2023-10-19 02:04:32 -07:00
DrSmugleaf
f87012e681 Allow handling by-value events by ref (#4373) 2023-10-18 18:37:43 -07:00
wixoa
54529fdbe3 Respect the manifest's assemblyPrefix value on the server (#4492) 2023-10-18 20:29:28 +02:00
DrSmugleaf
1745a12e5a Remove casts to Component (#4495) 2023-10-17 20:45:21 -07:00
DrSmugleaf
d201d787b7 Remove obsoletion from localized and console commands (#4496) 2023-10-17 20:18:30 -07:00
DrSmugleaf
904ddea274 Version: 168.0.0 2023-10-17 19:38:56 -07:00
DrSmugleaf
6b6ec844e8 Replace all T : Component constraints with T : IComponent (#4494) 2023-10-17 19:37:46 -07:00
Jordan Dominion
f24d18f470 Allow for ushort CVars (#4493) 2023-10-17 16:44:18 -07:00
900 changed files with 46411 additions and 18911 deletions

View File

@@ -7,8 +7,23 @@ indent_size = 4
trim_trailing_whitespace = true
charset = utf-8
max_line_length = 120
# ReSharper properties
resharper_csharp_max_line_length = 120
resharper_csharp_wrap_after_declaration_lpar = true
resharper_csharp_wrap_arguments_style = chop_if_long
resharper_csharp_wrap_parameters_style = chop_if_long
resharper_keep_existing_attribute_arrangement = true
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}]
indent_size = 2
[nuget.config]
indent_size = 2
[*.gdsl]
indent_style = tab

3
.github/CODEOWNERS vendored
View File

@@ -7,3 +7,6 @@
**/Toolshed/** @moonheart08
*Command.cs @moonheart08
*Commands.cs @moonheart08
# Physics
**/Robust.Shared/Physics/** @metalgearsloth

View File

@@ -14,7 +14,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Install dependencies
run: dotnet restore

View File

@@ -10,7 +10,7 @@ jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
os: [ubuntu-latest, windows-latest ] # , macos-latest] - temporarily disabled due to libfreetype.dll errors.
runs-on: ${{ matrix.os }}
@@ -22,12 +22,13 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
- name: Test Engine
- name: Robust.UnitTesting
run: dotnet test --no-build Robust.UnitTesting/Robust.UnitTesting.csproj -- NUnit.ConsoleOut=0
- name: Robust.Analyzers.Tests
run: dotnet test --no-build Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj -- NUnit.ConsoleOut=0

View File

@@ -23,7 +23,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Package client
run: Tools/package_client_build.py -p windows mac linux
@@ -33,10 +33,10 @@ jobs:
mkdir "release/${{ steps.parse_version.outputs.version }}"
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
- name: Upload files to centcomm
- name: Upload files to Suns
uses: appleboy/scp-action@master
with:
host: centcomm.spacestation14.io
host: suns.spacestation14.com
username: robust-build-push
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
source: "release/${{ steps.parse_version.outputs.version }}"
@@ -46,7 +46,7 @@ jobs:
- name: Update manifest JSON
uses: appleboy/ssh-action@master
with:
host: centcomm.spacestation14.io
host: suns.spacestation14.com
username: robust-build-push
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }}

View File

@@ -21,7 +21,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Disable submodule autoupdate
run: touch BuildChecker/DISABLE_SUBMODULE_AUTOUPDATE

74
Directory.Packages.props Normal file
View File

@@ -0,0 +1,74 @@
<Project>
<PropertyGroup>
<!--
We actually set ManagePackageVersionsCentrally manually in another import file.
Since .NET SDK 8.0.300, ManagePackageVersionsCentrally is automatically set if Directory.Packages.props exists.
https://github.com/NuGet/NuGet.Client/pull/5572
We actively negate this here, as we have some packages in tree we don't want such automatic behavior for.
We use Directory.Build.props to get copy the state *after* our MSBuild config but before Nuget's config.
-->
<ManagePackageVersionsCentrally />
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="ILReader.Core" Version="1.0.0.4" />
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageVersion Include="JetBrains.Profiler.Api" Version="1.4.0" />
<PackageVersion Include="Linguini.Bundle" Version="0.1.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit" Version="1.1.1"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeCoverage" Version="17.8.0" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="8.0.0" />
<PackageVersion Include="Microsoft.DotNet.RemoteExecutor" Version="8.0.0-beta.24059.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Primitives" Version="8.0.0" />
<PackageVersion Include="Microsoft.ILVerification" Version="8.0.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
<PackageVersion Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageVersion Include="Moq" Version="4.20.70" />
<PackageVersion Include="NUnit" Version="4.0.1" />
<PackageVersion Include="NUnit.Analyzers" Version="3.10.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageVersion Include="Nett" Version="0.15.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
<PackageVersion Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
<PackageVersion Include="Pidgin" Version="3.2.2" />
<PackageVersion Include="Robust.Natives" Version="0.1.1" />
<PackageVersion Include="Robust.Natives.Cef" Version="120.1.9" />
<PackageVersion Include="Robust.Shared.AuthLib" Version="0.1.2" />
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.7" />
<PackageVersion Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.7" />
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.3" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageVersion Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageVersion Include="System.Memory" Version="4.5.5" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.22621.5" />
<PackageVersion Include="TerraFX.Interop.Xlib" Version="6.4.0" />
<PackageVersion Include="VorbisPizza" Version="1.3.0" />
<PackageVersion Include="YamlDotNet" Version="13.7.1" />
<PackageVersion Include="prometheus-net" Version="8.2.1" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
<PackageVersion Include="PolySharp" Version="1.14.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
using System.Runtime.CompilerServices;
// So I wanted to mess with NetIncomingMessage and NetOutgoingMessage from tests.
// Now.. the instructors are internal...
// Unless...
// I mean we have this project here from the weird way we're compiling Lidgren.
// I could just put this in here... it wouldn't touch the main Lidgren repo at all...
[assembly: InternalsVisibleTo("Robust.UnitTesting")]

View File

@@ -10,9 +10,14 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<SkipRobustAnalyzer>true</SkipRobustAnalyzer>
<Nullable>enable</Nullable>
<LangVersion>12.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="CursedHorrorsBeyondOurWildestImagination.cs" />
<Compile Include="Lidgren.Network\Lidgren.Network\**\*.cs">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Compile>

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>net7.0</TargetFramework>
<LangVersion>11</LangVersion>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
</PropertyGroup>

View File

@@ -3,7 +3,8 @@
<!-- Import this at the end of any project files in Robust and Content. -->
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<Import Project="Robust.Custom.targets" Condition="Exists('Robust.Custom.targets')"/>
@@ -27,5 +28,5 @@
<Import Project="Robust.Analyzers.targets" Condition="'$(SkipRobustAnalyzer)' != 'true'" />
<!-- serialization generator -->
<Import Project="Robust.Serialization.Generator.targets" />
<Import Project="Robust.Serialization.Generator.targets" Condition="'$(SkipRobustAnalyzer)' != 'true'" />
</Project>

View File

@@ -24,12 +24,16 @@
<RobustInjectorsConfiguration>$(Configuration)</RobustInjectorsConfiguration>
<RobustInjectorsConfiguration Condition="'$(Configuration)' == 'DebugOpt'">Debug</RobustInjectorsConfiguration>
<RobustInjectorsConfiguration Condition="'$(Configuration)' == 'Tools'">Release</RobustInjectorsConfiguration>
<RobustInjectorsConfiguration Condition="'$(UseArtifactsOutput)' == 'true' And '$(RuntimeIdentifier)' != ''">$(RobustInjectorsConfiguration)_$(RuntimeIdentifier)</RobustInjectorsConfiguration>
<RobustInjectorsConfiguration Condition="'$(UseArtifactsOutput)' == 'true'">$(RobustInjectorsConfiguration.ToLower())</RobustInjectorsConfiguration>
<CompileRobustXamlTaskAssemblyFile Condition="'$(UseArtifactsOutput)' != 'true'">$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\bin\$(RobustInjectorsConfiguration)\netstandard2.0\Robust.Client.Injectors.dll</CompileRobustXamlTaskAssemblyFile>
<CompileRobustXamlTaskAssemblyFile Condition="'$(UseArtifactsOutput)' == 'true'">$(MSBuildThisFileDirectory)\..\..\artifacts\bin\Robust.Client.Injectors\$(RobustInjectorsConfiguration)\Robust.Client.Injectors.dll</CompileRobustXamlTaskAssemblyFile>
</PropertyGroup>
<UsingTask
Condition="'$(_RobustUseExternalMSBuild)' != 'true' And $(DesignTimeBuild) != true"
TaskName="CompileRobustXamlTask"
AssemblyFile="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\bin\$(RobustInjectorsConfiguration)\netstandard2.0\Robust.Client.Injectors.dll"/>
AssemblyFile="$(CompileRobustXamlTaskAssemblyFile)"/>
<Target
Name="CompileRobustXaml"
Condition="Exists('@(IntermediateAssembly)')"

View File

@@ -61,18 +61,5 @@ namespace OpenToolkit.GraphicsLibraryFramework
: base(message, innerException)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GLFWException"/> class with the specified context
/// and the serialization information.
/// </summary>
/// <param name="info">The <see cref="SerializationInfo"/> associated with this exception.</param>
/// <param name="context">
/// A <see cref="StreamingContext"/> that represents the context of this exception.
/// </param>
protected GLFWException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
- type: entity
id: Audio
name: Audio
description: Audio entity used by engine
save: false

File diff suppressed because it is too large Load Diff

View File

@@ -12,3 +12,8 @@
id: bgra
kind: source
path: "/Shaders/Internal/bgra.swsl"
- type: shader
id: ColorPicker
kind: source
path: "/Shaders/color_picker.swsl"

View File

@@ -9,7 +9,9 @@ cmd-parse-failure-float = {$arg} is not a valid float.
cmd-parse-failure-bool = {$arg} is not a valid bool.
cmd-parse-failure-uid = {$arg} is not a valid entity UID.
cmd-parse-failure-mapid = {$arg} is not a valid MapId.
cmd-parse-failure-grid = {$arg} is not a valid grid.
cmd-parse-failure-entity-exist = UID {$arg} does not correspond to an existing entity.
cmd-parse-failure-session = There is no session with username: {$username}
cmd-error-file-not-found = Could not find file: {$file}.
cmd-error-dir-not-found = Could not find directory: {$dir}.
@@ -17,15 +19,15 @@ 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-oldhelp-desc = Display general help or help text for a specific command
cmd-oldhelp-help = Usage: help [command name]
cmd-help-desc = Display general help or help text for a specific command
cmd-help-help = Usage: help [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-oldhelp-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>'.
cmd-oldhelp-unknown = Unknown command: { $command }
cmd-oldhelp-top = { $command } - { $description }
cmd-oldhelp-invalid-args = Invalid amount of arguments.
cmd-oldhelp-arg-cmdname = [command name]
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>'.
cmd-help-unknown = Unknown command: { $command }
cmd-help-top = { $command } - { $description }
cmd-help-invalid-args = Invalid amount of arguments.
cmd-help-arg-cmdname = [command name]
## 'cvar' command
cmd-cvar-desc = Gets or sets a CVar.
@@ -489,7 +491,7 @@ cmd-net_entityreport-help = Usage: net_entityreport
cmd-net_refresh-desc = Requests a full server state.
cmd-net_refresh-help = Usage: net_refresh
cmd-net_graph-desc = Toggles the net statistics pannel.
cmd-net_graph-desc = Toggles the net statistics panel.
cmd-net_graph-help = Usage: net_graph
cmd-net_watchent-desc = Dumps all network updates for an EntityId to the console.
@@ -561,3 +563,13 @@ 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-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-arg-file-name = <file name>
## PVS
cmd-pvs-override-info-desc = Prints information about any PVS overrides associated with an entity.
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}.

View File

@@ -0,0 +1,8 @@
cmd-merge_grids-desc = Combines 2 grids into 1 grid
cmd-merge_grids-help = merge_grids <gridUid1> <gridUid2> <offsetX> <offsetY> [angle]
cmd-merge_grids-hintA = Grid A
cmd-merge_grids-hintB = Grid B
cmd-merge_grids-xOffset = X offset
cmd-merge_grids-yOffset = Y offset
cmd-merge_grids-angle = [Angle]

View File

@@ -70,9 +70,9 @@ command-description-ls-in =
command-description-methods-get =
Returns all methods associated with the input type.
command-description-methods-overrides =
Returns all methods overriden on the input type.
Returns all methods overridden on the input type.
command-description-methods-overridesfrom =
Returns all methods overriden from the given type on the input type.
Returns all methods overridden from the given type on the input type.
command-description-cmd-moo =
Asks the important questions.
command-description-cmd-descloc =
@@ -418,6 +418,6 @@ command-description-tee =
This essentially lets you have a branch in your code to do multiple operations on one value.
command-description-cmd-info =
Returns a CommandSpec for the given command.
On it's own, this means it'll print the comamnd's help message.
On its own, this means it'll print the command's help message.
command-description-comp-rm =
Removes the given component from the entity.

View File

@@ -10,3 +10,18 @@ view-variable-instance-entity-client-components-search-bar-placeholder = Search
view-variable-instance-entity-server-components-search-bar-placeholder = Search
view-variable-instance-entity-add-window-server-components = Add Component [S]
view-variable-instance-entity-add-window-client-components = Add Component [C]
## SoundSpecifier
vv-sound-none = None
vv-sound-path = Path
vv-sound-collection = Collection
vv-sound-volume = volume
vv-sound-pitch = Pitch
vv-sound-max-distance = Max Distance
vv-sound-rolloff-factor = Rolloff Factor
vv-sound-reference-distance = Reference Distance
vv-sound-loop = Loop
vv-sound-play-offset = Play Offset (s)
vv-sound-variation = Pitch variation

View File

@@ -0,0 +1,46 @@
// Simple shader for creating a box with colours varying along the x and y axes.
uniform highp vec2 size;
uniform highp vec2 offset;
uniform highp vec4 xAxis;
uniform highp vec4 yAxis;
uniform highp vec4 baseColor;
uniform bool hsv;
void fragment()
{
// Calculate local uv coordinates.
// I.e., if using this shader to draw a box to the screen, (0,0) is the bottom left of the box.
highp float yCoords = 1.0/SCREEN_PIXEL_SIZE.y - FRAGCOORD.y;
highp vec2 uv = vec2(FRAGCOORD.x - offset.x, yCoords - offset.y);
uv /= size;
uv.y = 1.0 - uv.y;
highp vec4 modulate = baseColor + uv.x * xAxis + uv.y * yAxis;
if (hsv)
{
modulate.xyz = hsv2rgb(modulate.xyz);
}
// The UV used for the texture lookup is the TEXTURE UV coordinate, which is different from the coordinates computed above.
COLOR = zTexture(UV) * modulate;
}
// hsv to RGB conversion taken from www.shadertoy.com/view/MsS3Wc
// The MIT License
// Copyright © 2014 Inigo Quilez
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// https://www.youtube.com/c/InigoQuilez
// https://iquilezles.org
highp vec3 hsv2rgb( in highp vec3 c )
{
highp vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );
return c.z * mix( vec3(1.0), rgb, c.y);
}

View File

@@ -20,11 +20,16 @@ public sealed class AccessAnalyzer_Test
{
TestState =
{
AdditionalReferences = { typeof(AccessAnalyzer).Assembly },
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.Analyzers.AccessAttribute.cs",
"Robust.Shared.Analyzers.AccessPermissions.cs"
);
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);

View File

@@ -0,0 +1,8 @@
// OH BOY. TURNS OUT IT GETS EVEN MORE CURSED.
//
// So because we're compiling a copy of Robust.Roslyn.Shared into every analyzer project,
// the test project sees multiple copies of it. This would make it impossible to use.
// UNLESS you use this obscure C# feature called "extern alias"
// that I guarantee you you've never heard of before, and are now concerned about.
extern alias SerializationGenerator;

View File

@@ -0,0 +1,340 @@
extern alias SerializationGenerator;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using NUnit.Framework;
using SerializationGenerator::Robust.Roslyn.Shared;
using SerializationGenerator::Robust.Serialization.Generator;
namespace Robust.Analyzers.Tests;
[TestFixture]
[TestOf(typeof(ComponentPauseGenerator))]
[Parallelizable(ParallelScope.All)]
public sealed class ComponentPauseGeneratorTest
{
private const string TypesCode = """
global using System;
global using Robust.Shared.Analyzers;
global using Robust.Shared.GameObjects;
namespace Robust.Shared.Analyzers
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class AutoGenerateComponentPauseAttribute : Attribute
{
public bool Dirty = false;
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class AutoPausedFieldAttribute : Attribute;
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class AutoNetworkedFieldAttribute : Attribute
{
}
}
namespace Robust.Shared.GameObjects
{
public interface IComponent;
}
""";
[Test]
public void TestBasic()
{
var result = RunGenerator("""
[AutoGenerateComponentPause]
public sealed partial class FooComponent : IComponent
{
[AutoPausedField]
public TimeSpan Foo;
}
""");
ExpectNoDiagnostics(result);
ExpectSource(
result,
"""
// <auto-generated />
using Robust.Shared.GameObjects;
public partial class FooComponent
{
[RobustAutoGenerated]
public sealed class FooComponent_AutoPauseSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
}
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
{
component.Foo += args.PausedTime;
}
}
}
""");
}
[Test]
public void TestNullable()
{
var result = RunGenerator("""
[AutoGenerateComponentPause]
public sealed partial class FooComponent : IComponent
{
[AutoPausedField]
public TimeSpan? Foo;
}
""");
ExpectNoDiagnostics(result);
ExpectSource(
result,
"""
// <auto-generated />
using Robust.Shared.GameObjects;
public partial class FooComponent
{
[RobustAutoGenerated]
public sealed class FooComponent_AutoPauseSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
}
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
{
if (component.Foo.HasValue)
component.Foo = component.Foo.Value + args.PausedTime;
}
}
}
""");
}
[Test]
public void TestAutoState()
{
var result = RunGenerator("""
[AutoGenerateComponentPause]
public sealed partial class FooComponent : IComponent
{
[AutoPausedField, AutoNetworkedField]
public TimeSpan Foo;
}
""");
ExpectNoDiagnostics(result);
ExpectSource(
result,
"""
// <auto-generated />
using Robust.Shared.GameObjects;
public partial class FooComponent
{
[RobustAutoGenerated]
public sealed class FooComponent_AutoPauseSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
}
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
{
component.Foo += args.PausedTime;
Dirty(uid, component);
}
}
}
""");
}
[Test]
public void TestExplicitDirty()
{
var result = RunGenerator("""
[AutoGenerateComponentPause(Dirty = true)]
public sealed partial class FooComponent : IComponent
{
[AutoPausedField]
public TimeSpan Foo;
}
""");
ExpectNoDiagnostics(result);
ExpectSource(
result,
"""
// <auto-generated />
using Robust.Shared.GameObjects;
public partial class FooComponent
{
[RobustAutoGenerated]
public sealed class FooComponent_AutoPauseSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
}
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
{
component.Foo += args.PausedTime;
Dirty(uid, component);
}
}
}
""");
}
[Test]
public void TestDiagnosticNotIComponent()
{
var result = RunGenerator("""
[AutoGenerateComponentPause]
public sealed partial class FooComponent
{
[AutoPausedField]
public TimeSpan Foo;
}
""");
ExpectNoSource(result);
ExpectDiagnostics(result, [
(Diagnostics.IdComponentPauseNotComponent, new LinePositionSpan(new LinePosition(1, 28), new LinePosition(1, 40)))
]);
}
[Test]
public void TestDiagnosticNoFields()
{
var result = RunGenerator("""
[AutoGenerateComponentPause]
public sealed partial class FooComponent : IComponent
{
public TimeSpan Foo;
}
""");
ExpectNoSource(result);
ExpectDiagnostics(result, [
(Diagnostics.IdComponentPauseNoFields, new LinePositionSpan(new LinePosition(1, 28), new LinePosition(1, 40)))
]);
}
[Test]
public void TestDiagnosticNoParentAttribute()
{
var result = RunGenerator("""
public sealed partial class FooComponent : IComponent
{
[AutoPausedField]
public TimeSpan Foo, Fooz;
[AutoPausedField]
public TimeSpan Bar { get; set; }
}
""");
ExpectNoSource(result);
ExpectDiagnostics(result, [
(Diagnostics.IdComponentPauseNoParentAttribute, new LinePositionSpan(new LinePosition(3, 20), new LinePosition(3, 23))),
(Diagnostics.IdComponentPauseNoParentAttribute, new LinePositionSpan(new LinePosition(3, 25), new LinePosition(3, 29))),
(Diagnostics.IdComponentPauseNoParentAttribute, new LinePositionSpan(new LinePosition(6, 20), new LinePosition(6, 23)))
]);
}
[Test]
public void TestDiagnosticWrongType()
{
var result = RunGenerator("""
[AutoGenerateComponentPause]
public sealed partial class FooComponent : IComponent
{
[AutoPausedField]
public int Foo, Fooz;
[AutoPausedField]
public int Bar { get; set; }
}
""");
ExpectNoSource(result);
ExpectDiagnostics(result, [
(Diagnostics.IdComponentPauseWrongTypeAttribute, new LinePositionSpan(new LinePosition(4, 15), new LinePosition(4, 18))),
(Diagnostics.IdComponentPauseWrongTypeAttribute, new LinePositionSpan(new LinePosition(4, 20), new LinePosition(4, 24))),
(Diagnostics.IdComponentPauseWrongTypeAttribute, new LinePositionSpan(new LinePosition(7, 15), new LinePosition(7, 18)))
]);
}
private static void ExpectSource(GeneratorRunResult result, string expected)
{
Assert.That(result.GeneratedSources, Has.Length.EqualTo(1));
var source = result.GeneratedSources[0];
Assert.That(source.SourceText.ToString(), Is.EqualTo(expected));
}
private static void ExpectNoSource(GeneratorRunResult result)
{
Assert.That(result.GeneratedSources, Is.Empty);
}
private static void ExpectNoDiagnostics(GeneratorRunResult result)
{
Assert.That(result.Diagnostics, Is.Empty);
}
private static void ExpectDiagnostics(GeneratorRunResult result, (string code, LinePositionSpan span)[] diagnostics)
{
Assert.Multiple(() =>
{
Assert.That(result.Diagnostics, Has.Length.EqualTo(diagnostics.Length));
foreach (var (code, span) in diagnostics)
{
Assert.That(
result.Diagnostics.Any(x => x.Id == code && x.Location.GetLineSpan().Span == span),
$"Expected diagnostic with code {code} and location {span}");
}
});
}
private static GeneratorRunResult RunGenerator(string source)
{
var compilation = (Compilation)CSharpCompilation.Create("compilation",
new[]
{
CSharpSyntaxTree.ParseText(source, path: "Source.cs"),
CSharpSyntaxTree.ParseText(TypesCode, path: "Types.cs")
},
new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) },
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
var generator = new ComponentPauseGenerator();
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out _);
var result = driver.GetRunResult();
return result.Results[0];
}
}

View File

@@ -0,0 +1,58 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.DependencyAssignAnalyzer>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
public sealed class DependencyAssignAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<DependencyAssignAnalyzer, NUnitVerifier>()
{
TestState =
{
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.IoC.DependencyAttribute.cs"
);
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task Test()
{
const string code = """
using Robust.Shared.IoC;
public sealed class Foo
{
[Dependency]
private object? Field;
public Foo()
{
Field = "A";
}
}
""";
await Verifier(code,
// /0/Test0.cs(10,9): warning RA0025: Tried to assign to [Dependency] field 'Field'. Remove [Dependency] or inject it via field injection instead.
VerifyCS.Diagnostic().WithSpan(10, 9, 10, 20).WithArguments("Field"));
}
}

View File

@@ -0,0 +1,57 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.NoUncachedRegexAnalyzer>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
public sealed class NoUncachedRegexAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<NoUncachedRegexAnalyzer, NUnitVerifier>()
{
TestState =
{
Sources = { code }
},
};
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task Test()
{
const string code = """
using System.Text.RegularExpressions;
public static class Foo
{
public static void Bad()
{
Regex.Replace("foo", "bar", "baz");
}
public static void Good()
{
var r = new Regex("bar");
r.Replace("foo", "baz");
}
}
""";
await Verifier(code,
// /0/Test0.cs(7,9): warning RA0026: Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.
VerifyCS.Diagnostic().WithSpan(7, 9, 7, 43)
);
}
}

View File

@@ -1,24 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\MSBuild\Robust.Properties.targets" />
<Import Project="..\MSBuild\Robust.Engine.props" />
<PropertyGroup>
<SkipRobustAnalyzer>true</SkipRobustAnalyzer>
</PropertyGroup>
<Import Project="..\MSBuild\Robust.Properties.targets"/>
<Import Project="..\MSBuild\Robust.Engine.props"/>
<!-- Engine source files needed to make the tests work -->
<ItemGroup>
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" LogicalName="Robust.Shared.Analyzers.AccessAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" LogicalName="Robust.Shared.Analyzers.AccessPermissions.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit" Version="1.1.1"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1"/>
<PackageReference Include="NUnit" Version="3.13.2"/>
<PackageReference Include="NUnit.ConsoleRunner" Version="3.15.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
<PackageReference Include="NUnit.Analyzers" Version="3.3.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageVersion Update="NUnit" Version="3.14.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzer.Testing"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces"/>
<PackageReference Include="NUnit"/>
<PackageReference Include="NUnit3TestAdapter"/>
<PackageReference Include="NUnit.Analyzers"/>
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Robust.Analyzers\Robust.Analyzers.csproj"/>
<ProjectReference Include="..\Robust.Serialization.Generator\Robust.Serialization.Generator.csproj" Aliases="SerializationGenerator" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Text;
namespace Robust.Analyzers.Tests;
public static class TestHelper
{
public static void AddEmbeddedSources(SolutionState state, params string[] embeddedFiles)
{
AddEmbeddedSources(state, (IEnumerable<string>) embeddedFiles);
}
public static void AddEmbeddedSources(SolutionState state, IEnumerable<string> embeddedFiles)
{
foreach (var fileName in embeddedFiles)
{
using var stream = typeof(AccessAnalyzer_Test).Assembly.GetManifestResourceStream(fileName)!;
state.Sources.Add((fileName, SourceText.From(stream)));
}
}
}

View File

@@ -5,6 +5,7 @@ using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
using Robust.Shared.Analyzers.Implementation;
namespace Robust.Analyzers

View File

@@ -4,6 +4,7 @@ using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
using static Microsoft.CodeAnalysis.SymbolEqualityComparer;
namespace Robust.Analyzers;
@@ -16,27 +17,17 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor ByRefEventSubscribedByValueRule = new(
Diagnostics.IdByRefEventSubscribedByValue,
"By-ref event subscribed to by value",
"Tried to subscribe to a by-ref event '{0}' by value.",
"Tried to subscribe to a by-ref event '{0}' by value",
"Usage",
DiagnosticSeverity.Error,
true,
"Make sure that methods subscribing to a ref event have the ref keyword for the event argument."
);
private static readonly DiagnosticDescriptor ByValueEventSubscribedByRefRule = new(
Diagnostics.IdValueEventRaisedByRef,
"Value event subscribed to by-ref",
"Tried to subscribe to a value event '{0}' by-ref.",
"Usage",
DiagnosticSeverity.Error,
true,
"Make sure that methods subscribing to value events do not have the ref keyword for the event argument."
);
private static readonly DiagnosticDescriptor ByRefEventRaisedByValueRule = new(
Diagnostics.IdByRefEventRaisedByValue,
"By-ref event raised by value",
"Tried to raise a by-ref event '{0}' by value.",
"Tried to raise a by-ref event '{0}' by value",
"Usage",
DiagnosticSeverity.Error,
true,
@@ -46,7 +37,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor ByValueEventRaisedByRefRule = new(
Diagnostics.IdValueEventRaisedByRef,
"Value event raised by-ref",
"Tried to raise a value event '{0}' by-ref.",
"Tried to raise a value event '{0}' by-ref",
"Usage",
DiagnosticSeverity.Error,
true,
@@ -55,7 +46,6 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
ByRefEventSubscribedByValueRule,
ByValueEventSubscribedByRefRule,
ByRefEventRaisedByValueRule,
ByValueEventRaisedByRefRule
);
@@ -64,71 +54,9 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();
context.RegisterOperationAction(CheckEventSubscription, OperationKind.Invocation);
context.RegisterOperationAction(CheckEventRaise, OperationKind.Invocation);
}
private void CheckEventSubscription(OperationAnalysisContext context)
{
if (context.Operation is not IInvocationOperation operation)
return;
var subscribeMethods = context.Compilation
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntitySystem")?
.GetMembers()
.Where(m => m.Name.Contains("SubscribeLocalEvent"))
.Cast<IMethodSymbol>();
if (subscribeMethods == null)
return;
if (!subscribeMethods.Any(m => m.Equals(operation.TargetMethod.OriginalDefinition, Default)))
return;
var typeArguments = operation.TargetMethod.TypeArguments;
if (typeArguments.Length < 1 || typeArguments.Length > 2)
return;
if (operation.Arguments.First().Value is not IDelegateCreationOperation delegateCreation)
return;
if (delegateCreation.Target is not IMethodReferenceOperation methodReference)
return;
var eventParameter = methodReference.Method.Parameters.LastOrDefault();
if (eventParameter == null)
return;
ITypeSymbol eventArgument;
switch (typeArguments.Length)
{
case 1:
eventArgument = typeArguments[0];
break;
case 2:
eventArgument = typeArguments[1];
break;
default:
return;
}
var byRefAttribute = context.Compilation.GetTypeByMetadataName(ByRefAttribute);
if (byRefAttribute == null)
return;
var isByRefEventType = eventArgument
.GetAttributes()
.Any(attribute => attribute.AttributeClass?.Equals(byRefAttribute, Default) ?? false);
var parameterIsRef = eventParameter.RefKind == RefKind.Ref;
if (isByRefEventType != parameterIsRef)
{
var descriptor = isByRefEventType ? ByRefEventSubscribedByValueRule : ByValueEventSubscribedByRefRule;
var diagnostic = Diagnostic.Create(descriptor, operation.Syntax.GetLocation(), eventArgument);
context.ReportDiagnostic(diagnostic);
}
}
private void CheckEventRaise(OperationAnalysisContext context)
{
if (context.Operation is not IInvocationOperation operation)

View File

@@ -5,6 +5,8 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
using Robust.Shared.Serialization.Manager.Definition;
namespace Robust.Analyzers;
@@ -18,7 +20,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
Diagnostics.IdDataDefinitionPartial,
"Type must be partial",
"Type {0} is a DataDefinition but is not partial.",
"Type {0} is a DataDefinition but is not partial",
"Usage",
DiagnosticSeverity.Error,
true,
@@ -28,7 +30,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor NestedDataDefinitionPartialRule = new(
Diagnostics.IdNestedDataDefinitionPartial,
"Type must be partial",
"Type {0} contains nested data definition {1} but is not partial.",
"Type {0} contains nested data definition {1} but is not partial",
"Usage",
DiagnosticSeverity.Error,
true,
@@ -38,7 +40,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor DataFieldWritableRule = new(
Diagnostics.IdDataFieldWritable,
"Data field must not be readonly",
"Data field {0} in data definition {1} is readonly.",
"Data field {0} in data definition {1} is readonly",
"Usage",
DiagnosticSeverity.Error,
true,
@@ -48,15 +50,25 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
Diagnostics.IdDataFieldPropertyWritable,
"Data field property must have a setter",
"Data field property {0} in data definition {1} does not have a setter.",
"Data field property {0} in data definition {1} does not have a setter",
"Usage",
DiagnosticSeverity.Error,
true,
"Make sure to add a setter."
);
private static readonly DiagnosticDescriptor DataFieldRedundantTagRule = new(
Diagnostics.IdDataFieldRedundantTag,
"Data field has redundant tag specified",
"Data field {0} in data definition {1} has an explicitly set tag that matches autogenerated tag",
"Usage",
DiagnosticSeverity.Info,
true,
"Make sure to remove the tag string from the data field attribute."
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule,
DataFieldRedundantTagRule
);
public override void Initialize(AnalysisContext context)
@@ -124,6 +136,11 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
}
if (HasRedundantTag(fieldSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
}
}
}
@@ -148,6 +165,11 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
}
if (HasRedundantTag(propertySymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
}
}
private static bool IsReadOnlyDataField(ITypeSymbol type, ISymbol field)
@@ -247,6 +269,29 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
return false;
}
private static bool HasRedundantTag(ISymbol symbol)
{
if (!IsDataField(symbol, out var _, out var attribute))
return false;
// No args, no problem
if (attribute.ConstructorArguments.Length == 0)
return false;
// If a tag is explicitly specified, it will be the first argument...
var tagArgument = attribute.ConstructorArguments[0];
// ...but the first arg could also something else, since tag is optional
// so we make sure that it's a string
if (tagArgument.Value is not string explicitName)
return false;
// Get the name that sourcegen would provide
var automaticName = DataDefinitionUtility.AutoGenerateTag(symbol.Name);
// If the explicit name matches the sourcegen name, we have a redundancy
return explicitName == automaticName;
}
private static bool IsImplicitDataDefinition(ITypeSymbol type)
{
if (HasAttribute(type, ImplicitDataDefinitionNamespace))

View File

@@ -1,23 +1,23 @@
#nullable enable
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxKind;
using static Robust.Analyzers.Diagnostics;
using static Robust.Roslyn.Shared.Diagnostics;
namespace Robust.Analyzers;
[ExportCodeFixProvider(LanguageNames.CSharp)]
public sealed class DefinitionFixer : CodeFixProvider
{
private const string DataFieldAttributeName = "DataField";
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable,
IdDataFieldRedundantTag
);
public override Task RegisterCodeFixesAsync(CodeFixContext context)
@@ -34,6 +34,8 @@ public sealed class DefinitionFixer : CodeFixProvider
return RegisterDataFieldFix(context, diagnostic);
case IdDataFieldPropertyWritable:
return RegisterDataFieldPropertyFix(context, diagnostic);
case IdDataFieldRedundantTag:
return RegisterRedundantTagFix(context, diagnostic);
}
}
@@ -72,6 +74,68 @@ public sealed class DefinitionFixer : CodeFixProvider
return document.WithSyntaxRoot(root);
}
private static async Task RegisterRedundantTagFix(CodeFixContext context, Diagnostic diagnostic)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
var span = diagnostic.Location.SourceSpan;
var token = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<MemberDeclarationSyntax>().First();
if (token == null)
return;
// Find the DataField attribute
AttributeSyntax? dataFieldAttribute = null;
foreach (var attributeList in token.AttributeLists)
{
foreach (var attribute in attributeList.Attributes)
{
if (attribute.Name.ToString() == DataFieldAttributeName)
{
dataFieldAttribute = attribute;
break;
}
}
if (dataFieldAttribute != null)
break;
}
if (dataFieldAttribute == null)
return;
context.RegisterCodeFix(CodeAction.Create(
"Remove explicitly set tag",
c => RemoveRedundantTag(context.Document, dataFieldAttribute, c),
"Remove explicitly set tag"
), diagnostic);
}
private static async Task<Document> RemoveRedundantTag(Document document, AttributeSyntax syntax, CancellationToken cancellation)
{
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
if (syntax.ArgumentList == null)
return document;
AttributeSyntax? newSyntax;
if (syntax.ArgumentList.Arguments.Count == 1)
{
// If this is the only argument, delete the ArgumentList so we don't leave empty parentheses
newSyntax = syntax.RemoveNode(syntax.ArgumentList, SyntaxRemoveOptions.KeepNoTrivia);
}
else
{
// Remove the first argument, which is the tag
var newArgs = syntax.ArgumentList.Arguments.RemoveAt(0);
var newArgList = syntax.ArgumentList.WithArguments(newArgs);
// Construct a new attribute with the tag removed
newSyntax = syntax.WithArgumentList(newArgList);
}
root = root!.ReplaceNode(syntax, newSyntax!);
return document.WithSyntaxRoot(root);
}
private static async Task RegisterDataFieldFix(CodeFixContext context, Diagnostic diagnostic)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);

View File

@@ -0,0 +1,61 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class DependencyAssignAnalyzer : DiagnosticAnalyzer
{
private const string DependencyAttributeType = "Robust.Shared.IoC.DependencyAttribute";
private static readonly DiagnosticDescriptor Rule = new (
Diagnostics.IdDependencyFieldAssigned,
"Assignment to dependency field",
"Tried to assign to [Dependency] field '{0}'. Remove [Dependency] or inject it via field injection instead.",
"Usage",
DiagnosticSeverity.Warning,
true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterOperationAction(CheckAssignment, OperationKind.SimpleAssignment);
}
private static void CheckAssignment(OperationAnalysisContext context)
{
if (context.Operation is not ISimpleAssignmentOperation assignment)
return;
if (assignment.Target is not IFieldReferenceOperation fieldRef)
return;
var field = fieldRef.Field;
var attributes = field.GetAttributes();
if (attributes.Length == 0)
return;
var depAttribute = context.Compilation.GetTypeByMetadataName(DependencyAttributeType);
if (!HasAttribute(attributes, depAttribute))
return;
context.ReportDiagnostic(Diagnostic.Create(Rule, assignment.Syntax.GetLocation(), field.Name));
}
private static bool HasAttribute(ImmutableArray<AttributeData> attributes, ISymbol symbol)
{
foreach (var attribute in attributes)
{
if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, symbol))
return true;
}
return false;
}
}

View File

@@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
using Document = Microsoft.CodeAnalysis.Document;
namespace Robust.Analyzers

View File

@@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;

View File

@@ -2,6 +2,7 @@ using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers
{

View File

@@ -0,0 +1,66 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class NoUncachedRegexAnalyzer : DiagnosticAnalyzer
{
private const string RegexTypeName = "Regex";
private const string RegexType = $"System.Text.RegularExpressions.{RegexTypeName}";
private static readonly DiagnosticDescriptor Rule = new (
Diagnostics.IdUncachedRegex,
"Use of uncached static Regex function",
"Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.",
"Usage",
DiagnosticSeverity.Warning,
true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public static readonly HashSet<string> BadFunctions =
[
"Count",
"EnumerateMatches",
"IsMatch",
"Match",
"Matches",
"Replace",
"Split"
];
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterOperationAction(CheckInvocation, OperationKind.Invocation);
}
private static void CheckInvocation(OperationAnalysisContext context)
{
if (context.Operation is not IInvocationOperation invocation)
return;
// All Regex functions we care about are static.
var targetMethod = invocation.TargetMethod;
if (!targetMethod.IsStatic)
return;
// Bail early.
if (targetMethod.ContainingType.Name != "Regex")
return;
var regexType = context.Compilation.GetTypeByMetadataName(RegexType);
if (!SymbolEqualityComparer.Default.Equals(regexType, targetMethod.ContainingType))
return;
if (!BadFunctions.Contains(targetMethod.Name))
return;
context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.Syntax.GetLocation()));
}
}

View File

@@ -3,6 +3,7 @@ using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
@@ -31,7 +32,7 @@ public sealed class NotNullableFlagAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor InvalidNotNullableImplementationRule = new (
Diagnostics.IdInvalidNotNullableFlagImplementation,
"Invalid NotNullable flag implementation.",
"Invalid NotNullable flag implementation",
"NotNullable flag is either not typed as bool, or does not have a default value equaling false",
"Usage",
DiagnosticSeverity.Error,
@@ -41,7 +42,7 @@ public sealed class NotNullableFlagAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor InvalidNotNullableTypeRule = new (
Diagnostics.IdInvalidNotNullableFlagType,
"Failed to resolve type parameter",
"Failed to resolve type parameter \"{0}\".",
"Failed to resolve type parameter \"{0}\"",
"Usage",
DiagnosticSeverity.Error,
true,
@@ -49,7 +50,7 @@ public sealed class NotNullableFlagAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor NotNullableFlagValueTypeRule = new (
Diagnostics.IdNotNullableFlagValueType,
"NotNullable flag not supported for value types.",
"NotNullable flag not supported for value types",
"Value types as generic arguments are not supported for NotNullable flags",
"Usage",
DiagnosticSeverity.Error,

View File

@@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;

View File

@@ -1,30 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>10</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.0.1" />
</ItemGroup>
<ItemGroup>
<!-- Needed for NotNullableFlagAnalyzer. -->
<Compile Include="..\Robust.Shared\Analyzers\NotNullableFlagAttribute.cs" />
<Compile Include="..\Robust.Shared\Analyzers\NotNullableFlagAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<ItemGroup>
<!-- Needed for FriendAnalyzer. -->
<Compile Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" />
<Compile Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" />
<Compile Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" LinkBase="Implementations" />
<Compile Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" LinkBase="Implementations" />
</ItemGroup>
<ItemGroup>
<!-- Needed for PreferGenericVariantAnalyzer. -->
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" />
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<ItemGroup>
<!-- Needed for DataDefinitionAnalyzer. -->
<Compile Include="..\Robust.Shared\Serialization\Manager\Definition\DataDefinitionUtility.cs" LinkBase="Implementations" />
</ItemGroup>
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
<PropertyGroup>
<Nullable>disable</Nullable>
<!--
Rider seems to get really confused with hot reload if we directly compile in the above-linked classes.
As such, they have an #if to change their namespace in this project.
-->
<DefineConstants>$(DefineConstants);ROBUST_ANALYZERS_IMPL</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers
{

View File

@@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;

View File

@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.Collections;
namespace Robust.Benchmarks.Collections;
[Virtual]
public class ValueListEnumerationBenchmarks
{
[Params(4, 16, 64)]
public int N { get; set; }
private sealed class Data(int i)
{
public readonly int I = i;
}
private ValueList<Data> _valueList;
private Data[] _array = default!;
[GlobalSetup]
public void Setup()
{
var list = new List<Data>(N);
for (var i = 0; i < N; i++)
{
list.Add(new(i));
}
_array = list.ToArray();
_valueList = new(list.ToArray());
}
[Benchmark]
public int ValueList()
{
var total = 0;
foreach (var ev in _valueList)
{
total += ev.I;
}
return total;
}
[Benchmark]
public int ValueListSpan()
{
var total = 0;
foreach (var ev in _valueList.Span)
{
total += ev.I;
}
return total;
}
[Benchmark]
public int Array()
{
var total = 0;
foreach (var ev in _array)
{
total += ev.I;
}
return total;
}
[Benchmark]
public int Span()
{
var total = 0;
foreach (var ev in _array.AsSpan())
{
total += ev.I;
}
return total;
}
}

View File

@@ -1,15 +1,18 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Globalization;
using BenchmarkDotNet.Analysers;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.EventProcessors;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Filters;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Order;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Validators;
using Robust.Benchmarks.Exporters;
@@ -44,10 +47,16 @@ public sealed class DefaultSQLConfig : IConfig
public IEnumerable<BenchmarkLogicalGroupRule> GetLogicalGroupRules() => DefaultConfig.Instance.GetLogicalGroupRules();
public IEnumerable<EventProcessor> GetEventProcessors() => DefaultConfig.Instance.GetEventProcessors();
public IEnumerable<IColumnHidingRule> GetColumnHidingRules() => DefaultConfig.Instance.GetColumnHidingRules();
public IOrderer Orderer => DefaultConfig.Instance.Orderer!;
public ICategoryDiscoverer? CategoryDiscoverer => DefaultConfig.Instance.CategoryDiscoverer;
public SummaryStyle SummaryStyle => DefaultConfig.Instance.SummaryStyle;
public ConfigUnionRule UnionRule => DefaultConfig.Instance.UnionRule;
public string ArtifactsPath => DefaultConfig.Instance.ArtifactsPath;
public CultureInfo CultureInfo => DefaultConfig.Instance.CultureInfo!;
public ConfigOptions Options => DefaultConfig.Instance.Options;
public TimeSpan BuildTimeout => DefaultConfig.Instance.BuildTimeout;
public IReadOnlyList<Conclusion> ConfigAnalysisConclusion => DefaultConfig.Instance.ConfigAnalysisConclusion;
}

View File

@@ -26,9 +26,8 @@ public partial class AddRemoveComponentBenchmark
.InitializeInstance();
_entityManager = _simulation.Resolve<IEntityManager>();
var coords = new MapCoordinates(0, 0, new MapId(1));
_simulation.AddMap(coords.MapId);
var map = _simulation.CreateMap().Uid;
var coords = new EntityCoordinates(map, default);
for (var i = 0; i < N; i++)
{

View File

@@ -29,8 +29,8 @@ public partial class ComponentIteratorBenchmark
Comps = new A[N+2];
var coords = new MapCoordinates(0, 0, new MapId(1));
_simulation.AddMap(coords.MapId);
var map = _simulation.CreateMap().MapId;
var coords = new MapCoordinates(default, map);
for (var i = 0; i < N; i++)
{

View File

@@ -31,8 +31,8 @@ public partial class GetComponentBenchmark
Comps = new A[N+2];
var coords = new MapCoordinates(0, 0, new MapId(1));
_simulation.AddMap(coords.MapId);
var map = _simulation.CreateMap().Uid;
var coords = new EntityCoordinates(map, default);
for (var i = 0; i < N; i++)
{

View File

@@ -29,10 +29,9 @@ public partial class SpawnDeleteEntityBenchmark
.InitializeInstance();
_entityManager = _simulation.Resolve<IEntityManager>();
_mapCoords = new MapCoordinates(0, 0, new MapId(1));
var uid = _simulation.AddMap(_mapCoords.MapId);
_entCoords = new EntityCoordinates(uid, 0, 0);
var (map, mapId) = _simulation.CreateMap();
_mapCoords = new MapCoordinates(default, mapId);
_entCoords = new EntityCoordinates(map, 0, 0);
}
[Benchmark(Baseline = true)]

View File

@@ -13,12 +13,12 @@
<ProjectReference Include="..\Robust.UnitTesting\Robust.UnitTesting.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
<PackageReference Include="BenchmarkDotNet" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
</ItemGroup>
<Import Project="..\MSBuild\Robust.Properties.targets" />

View File

@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Robust.Server.Containers;
using Robust.Server.GameStates;
@@ -11,7 +12,9 @@ using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.UnitTesting.Server;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.UnitTesting;
namespace Robust.Benchmarks.Transform;
@@ -19,114 +22,176 @@ namespace Robust.Benchmarks.Transform;
/// This benchmark tests various transform/move related functions with an entity that has many children.
/// </summary>
[Virtual, MemoryDiagnoser]
public class RecursiveMoveBenchmark
public class RecursiveMoveBenchmark : RobustIntegrationTest
{
private ISimulation _simulation = default!;
private IEntityManager _entMan = default!;
private SharedTransformSystem _transform = default!;
private ContainerSystem _container = default!;
private PvsSystem _pvs = default!;
private EntityCoordinates _mapCoords;
private EntityCoordinates _gridCoords;
private EntityCoordinates _gridCoords2;
private EntityUid _ent;
private EntityUid _child;
private TransformComponent _childXform = default!;
private EntityQuery<TransformComponent> _query;
private ICommonSession[] _players = default!;
private PvsSession _session = default!;
[GlobalSetup]
public void GlobalSetup()
{
_simulation = RobustServerSimulation
.NewSimulation()
.InitializeInstance();
ProgramShared.PathOffset = "../../../../";
var server = StartServer();
var client = StartClient();
if (!_simulation.Resolve<IConfigurationManager>().GetCVar(CVars.NetPVS))
throw new InvalidOperationException("PVS must be enabled");
Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()).Wait();
var mapMan = server.ResolveDependency<IMapManager>();
_entMan = server.ResolveDependency<IEntityManager>();
var confMan = server.ResolveDependency<IConfigurationManager>();
var sPlayerMan = server.ResolveDependency<ISharedPlayerManager>();
_entMan = _simulation.Resolve<IEntityManager>();
_transform = _entMan.System<SharedTransformSystem>();
_container = _entMan.System<ContainerSystem>();
_pvs = _entMan.System<PvsSystem>();
_query = _entMan.GetEntityQuery<TransformComponent>();
// Create map & grid
var mapMan = _simulation.Resolve<IMapManager>();
var mapSys = _entMan.System<SharedMapSystem>();
var mapId = mapMan.CreateMap();
var map = mapMan.GetMapEntityId(mapId);
var gridComp = mapMan.CreateGrid(mapId);
var grid = gridComp.Owner;
_gridCoords = new EntityCoordinates(grid, .5f, .5f);
_mapCoords = new EntityCoordinates(map, 100, 100);
mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1));
// Next, we will spawn our test entity. This entity will have a complex transform/container hierarchy.
// This is intended to be representative of a typical SS14 player entity, with organs. clothing, and a full backpack.
_ent = _entMan.Spawn();
var netMan = client.ResolveDependency<IClientNetManager>();
client.SetConnectTarget(server);
client.Post(() => netMan.ClientConnect(null!, 0, null!));
server.Post(() => confMan.SetCVar(CVars.NetPVS, true));
// Quick check that SetCoordinates actually changes the parent as expected
// I.e., ensure that grid-traversal code doesn't just dump the entity on the map.
_transform.SetCoordinates(_ent, _gridCoords);
if (_query.GetComponent(_ent).ParentUid != _gridCoords.EntityId)
throw new Exception("Grid traversal error.");
_transform.SetCoordinates(_ent, _mapCoords);
if (_query.GetComponent(_ent).ParentUid != _mapCoords.EntityId)
throw new Exception("Grid traversal error.");
// Add 5 direct children in slots to represent clothing.
for (var i = 0; i < 5; i++)
for (int i = 0; i < 10; i++)
{
var id = $"inventory{i}";
_container.EnsureContainer<ContainerSlot>(_ent, id);
if (!_entMan.TrySpawnInContainer(null, _ent, id, out _))
throw new Exception($"Failed to setup entity");
server.WaitRunTicks(1).Wait();
client.WaitRunTicks(1).Wait();
}
// body parts
_container.EnsureContainer<Container>(_ent, "body");
for (var i = 0; i < 5; i++)
// Ensure client & server ticks are synced.
// Client runs 1 tick ahead
{
// Simple organ
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out _))
throw new Exception($"Failed to setup entity");
var sTick = (int)server.Timing.CurTick.Value;
var cTick = (int)client.Timing.CurTick.Value;
var delta = cTick - sTick;
// body part that has another body part / limb
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out var limb))
throw new Exception($"Failed to setup entity");
if (delta > 1)
server.WaitRunTicks(delta - 1).Wait();
else if (delta < 1)
client.WaitRunTicks(1 - delta).Wait();
_container.EnsureContainer<ContainerSlot>(limb.Value, "limb");
if (!_entMan.TrySpawnInContainer(null, limb.Value, "limb", out _))
throw new Exception($"Failed to setup entity");
sTick = (int)server.Timing.CurTick.Value;
cTick = (int)client.Timing.CurTick.Value;
delta = cTick - sTick;
if (delta != 1)
throw new Exception("Failed setup");
}
// Backpack
_container.EnsureContainer<ContainerSlot>(_ent, "inventory-backpack");
if (!_entMan.TrySpawnInContainer(null, _ent, "inventory-backpack", out var backpack))
throw new Exception($"Failed to setup entity");
// Misc backpack contents.
var backpackStorage = _container.EnsureContainer<Container>(backpack.Value, "storage");
for (var i = 0; i < 10; i++)
// Set up map and spawn player
server.WaitPost(() =>
{
if (!_entMan.TrySpawnInContainer(null, backpack.Value, "storage", out _))
var map = server.ResolveDependency<SharedMapSystem>().CreateMap(out var mapId);
var gridComp = mapMan.CreateGridEntity(mapId);
var grid = gridComp.Owner;
mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1));
_gridCoords = new EntityCoordinates(grid, .5f, .5f);
_gridCoords2 = new EntityCoordinates(grid, .5f, .6f);
_mapCoords = new EntityCoordinates(map, 100, 100);
var playerUid = _entMan.SpawnEntity(null, _mapCoords);
// Attach player.
var session = sPlayerMan.Sessions.First();
server.PlayerMan.SetAttachedEntity(session, playerUid);
sPlayerMan.JoinGame(session);
// Next, we will spawn our test entity. This entity will have a complex transform/container hierarchy.
// This is intended to be representative of a typical SS14 player entity, with organs. clothing, and a full backpack.
_ent = _entMan.Spawn();
// Quick check that SetCoordinates actually changes the parent as expected
// I.e., ensure that grid-traversal code doesn't just dump the entity on the map.
_transform.SetCoordinates(_ent, _gridCoords);
if (_query.GetComponent(_ent).ParentUid != _gridCoords.EntityId)
throw new Exception("Grid traversal error.");
_transform.SetCoordinates(_ent, _mapCoords);
if (_query.GetComponent(_ent).ParentUid != _mapCoords.EntityId)
throw new Exception("Grid traversal error.");
// Add 5 direct children in slots to represent clothing.
for (var i = 0; i < 5; i++)
{
var id = $"inventory{i}";
_container.EnsureContainer<ContainerSlot>(_ent, id);
if (!_entMan.TrySpawnInContainer(null, _ent, id, out _))
throw new Exception($"Failed to setup entity");
}
// body parts
_container.EnsureContainer<Container>(_ent, "body");
for (var i = 0; i < 5; i++)
{
// Simple organ
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out _))
throw new Exception($"Failed to setup entity");
// body part that has another body part / limb
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out var limb))
throw new Exception($"Failed to setup entity");
_container.EnsureContainer<ContainerSlot>(limb.Value, "limb");
if (!_entMan.TrySpawnInContainer(null, limb.Value, "limb", out _))
throw new Exception($"Failed to setup entity");
}
// Backpack
_container.EnsureContainer<ContainerSlot>(_ent, "inventory-backpack");
if (!_entMan.TrySpawnInContainer(null, _ent, "inventory-backpack", out var backpack))
throw new Exception($"Failed to setup entity");
// Misc backpack contents.
var backpackStorage = _container.EnsureContainer<Container>(backpack.Value, "storage");
for (var i = 0; i < 10; i++)
{
if (!_entMan.TrySpawnInContainer(null, backpack.Value, "storage", out _))
throw new Exception($"Failed to setup entity");
}
// Emergency box inside of the backpack
var box = backpackStorage.ContainedEntities.First();
var boxContainer = _container.EnsureContainer<Container>(box, "storage");
for (var i = 0; i < 10; i++)
{
if (!_entMan.TrySpawnInContainer(null, box, "storage", out _))
throw new Exception($"Failed to setup entity");
}
// Deepest child.
_child = boxContainer.ContainedEntities.First();
_childXform = _query.GetComponent(_child);
_players = new[] {session};
_session = _pvs.PlayerData[session];
}).Wait();
for (int i = 0; i < 10; i++)
{
server.WaitRunTicks(1).Wait();
client.WaitRunTicks(1).Wait();
}
// Emergency box inside of the backpack
var box = backpackStorage.ContainedEntities.First();
var boxContainer = _container.EnsureContainer<Container>(box, "storage");
for (var i = 0; i < 10; i++)
{
if (!_entMan.TrySpawnInContainer(null, box, "storage", out _))
throw new Exception($"Failed to setup entity");
}
PvsTick();
PvsTick();
}
// Deepest child.
_child = boxContainer.ContainedEntities.First();
_childXform = _query.GetComponent(_child);
_pvs.ProcessCollections();
private void PvsTick()
{
_session.ClearState();
_pvs.CacheSessionData(_players);
_pvs.GetVisibleChunks();
_pvs.ProcessVisibleChunksSequential();
}
/// <summary>
@@ -140,6 +205,13 @@ public class RecursiveMoveBenchmark
_transform.SetCoordinates(_ent, _mapCoords);
}
[Benchmark]
public void MoveEntityASmidge()
{
_transform.SetCoordinates(_ent, _gridCoords);
_transform.SetCoordinates(_ent, _gridCoords2);
}
/// <summary>
/// Like <see cref="MoveEntity"/>, but also processes queued PVS chunk updates.
/// </summary>
@@ -147,9 +219,18 @@ public class RecursiveMoveBenchmark
public void MoveAndUpdateChunks()
{
_transform.SetCoordinates(_ent, _gridCoords);
_pvs.ProcessCollections();
PvsTick();
_transform.SetCoordinates(_ent, _mapCoords);
_pvs.ProcessCollections();
PvsTick();
}
[Benchmark]
public void MoveASmidgeAndUpdateChunk()
{
_transform.SetCoordinates(_ent, _gridCoords);
PvsTick();
_transform.SetCoordinates(_ent, _gridCoords2);
PvsTick();
}
[Benchmark]

View File

@@ -6,8 +6,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="17.0.0" />
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
<PackageReference Include="Microsoft.Build.Framework" Version="17.8.3" />
<PackageReference Include="Mono.Cecil" Version="0.11.5" />
<PackageReference Include="Pidgin" Version="2.5.0" />
</ItemGroup>

View File

@@ -239,6 +239,7 @@ namespace Robust.Build.Tasks
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
}
res.Remove();
}
return true;
}

View File

@@ -1,18 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<Compile Link="XamlX\filename" Include="../XamlX/src/XamlX/**/*.cs" />
<Compile Remove="../XamlX/src/XamlX/**/SreTypeSystem.cs" />
<Compile Remove="../XamlX/src/XamlX/obj/**" />
<Compile Include="..\Robust.Client\UserInterface\ControlPropertyAccess.cs" />
</ItemGroup>
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
<PropertyGroup>
<!-- XamlX doesn't do NRTs. -->
<Nullable>disable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,85 @@
using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef;
/// <summary>
/// Shared functionality for all <see cref="CefClient"/> implementations.
/// </summary>
/// <remarks>
/// Locks down a bunch of CEF functionality we absolutely do not need content to do right now.
/// </remarks>
internal abstract class BaseRobustCefClient : CefClient
{
private readonly RobustCefPrintHandler _printHandler = new();
private readonly RobustCefPermissionHandler _permissionHandler = new();
private readonly RobustCefDialogHandler _dialogHandler = new();
private readonly RobustCefDragHandler _dragHandler = new();
protected override CefPrintHandler GetPrintHandler() => _printHandler;
protected override CefPermissionHandler GetPermissionHandler() => _permissionHandler;
protected override CefDialogHandler GetDialogHandler() => _dialogHandler;
protected override CefDragHandler GetDragHandler() => _dragHandler;
private sealed class RobustCefPrintHandler : CefPrintHandler
{
protected override void OnPrintSettings(CefBrowser browser, CefPrintSettings settings, bool getDefaults)
{
}
protected override bool OnPrintDialog(CefBrowser browser, bool hasSelection, CefPrintDialogCallback callback)
{
return false;
}
protected override bool OnPrintJob(CefBrowser browser, string documentName, string pdfFilePath, CefPrintJobCallback callback)
{
return false;
}
protected override void OnPrintReset(CefBrowser browser)
{
}
}
private sealed class RobustCefPermissionHandler : CefPermissionHandler
{
protected override bool OnRequestMediaAccessPermission(
CefBrowser browser,
CefFrame frame,
string requestingOrigin,
CefMediaAccessPermissionTypes requestedPermissions,
CefMediaAccessCallback callback)
{
callback.Cancel();
return true;
}
}
private sealed class RobustCefDialogHandler : CefDialogHandler
{
protected override bool OnFileDialog(
CefBrowser browser,
CefFileDialogMode mode,
string title,
string defaultFilePath,
string[] acceptFilters,
CefFileDialogCallback callback)
{
callback.Cancel();
return true;
}
}
private sealed class RobustCefDragHandler : CefDragHandler
{
protected override bool OnDragEnter(CefBrowser browser, CefDragData dragData, CefDragOperationsMask mask)
{
return true;
}
protected override void OnDraggableRegionsChanged(CefBrowser browser, CefFrame frame, CefDraggableRegion[] regions)
{
}
}
}

View File

@@ -1,4 +1,7 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef
@@ -19,6 +22,8 @@ namespace Robust.Client.WebView.Cef
var mainArgs = new CefMainArgs(argv);
StartWatchThread();
// This will block executing until the subprocess is shut down.
var code = CefRuntime.ExecuteProcess(mainArgs, new RobustCefApp(null), IntPtr.Zero);
@@ -29,5 +34,44 @@ namespace Robust.Client.WebView.Cef
return code;
}
private static void StartWatchThread()
{
//
// CEF has this nasty habit of not shutting down all its processes if the parent crashes.
// Great!
//
// We use a separate thread in each CEF child process to watch the main PID.
// If it exits, we kill ourselves after a couple seconds.
//
if (Environment.GetEnvironmentVariable("ROBUST_CEF_BROWSER_PROCESS_ID") is not { } parentIdString)
return;
if (Environment.GetEnvironmentVariable("ROBUST_CEF_BROWSER_PROCESS_MODULE") is not { } parentModuleString)
return;
if (!int.TryParse(parentIdString, CultureInfo.InvariantCulture, out var parentId))
return;
var process = Process.GetProcessById(parentId);
if ((process.MainModule?.FileName ?? "") != parentModuleString)
{
process.Dispose();
return;
}
new Thread(() => WatchThread(process)) { Name = "CEF Watch Thread", IsBackground = true }
.Start();
}
private static void WatchThread(Process p)
{
p.WaitForExit();
Thread.Sleep(3000);
Environment.Exit(1);
}
}
}

View File

@@ -76,12 +76,9 @@ namespace Robust.Client.WebView.Cef
return true;
}
protected override unsafe bool Read(IntPtr dataOut, int bytesToRead, out int bytesRead, CefResourceReadCallback callback)
protected override bool Read(Span<byte> response, out int bytesRead, CefResourceReadCallback callback)
{
var byteSpan = new Span<byte>((void*) dataOut, bytesToRead);
bytesRead = _stream.Read(byteSpan);
bytesRead = _stream.Read(response);
return bytesRead != 0;
}

View File

@@ -31,10 +31,12 @@ namespace Robust.Client.WebView.Cef
// Disable zygote on Linux.
commandLine.AppendSwitch("--no-zygote");
// Work around https://bitbucket.org/chromiumembedded/cef/issues/3213/ozone-egl-initialization-does-not-have
// Work around https://github.com/chromiumembedded/cef/issues/3213
// Desktop GL force makes Chromium not try to load its own ANGLE/Swiftshader so load paths aren't problematic.
if (OperatingSystem.IsLinux())
commandLine.AppendSwitch("--use-gl", "desktop");
// UPDATE: That bug got fixed and now this workaround breaks CEF.
// Keeping all this comment history in case I ever wanan remember what the `--use-gl` flag is.
//if (OperatingSystem.IsLinux())
// commandLine.AppendSwitch("--use-gl", "desktop");
// commandLine.AppendSwitch("--single-process");

View File

@@ -3,7 +3,7 @@ using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef
{
// Simple CEF client.
internal sealed class RobustCefClient : CefClient
internal sealed class RobustCefClient : BaseRobustCefClient
{
private readonly CefRenderHandler _renderHandler;
private readonly CefRequestHandler _requestHandler;

View File

@@ -150,7 +150,7 @@ namespace Robust.Client.WebView.Cef
}
}
private sealed class WindowCefClient : CefClient
private sealed class WindowCefClient : BaseRobustCefClient
{
private readonly CefLifeSpanHandler _lifeSpanHandler;
private readonly CefRequestHandler _requestHandler;

View File

@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
@@ -85,6 +86,10 @@ namespace Robust.Client.WebView.Cef
_app = new RobustCefApp(_sawmill);
var process = Process.GetCurrentProcess();
Environment.SetEnvironmentVariable("ROBUST_CEF_BROWSER_PROCESS_ID", process.Id.ToString());
Environment.SetEnvironmentVariable("ROBUST_CEF_BROWSER_PROCESS_MODULE", process.MainModule?.FileName ?? "");
// So these arguments look like nonsense, but it turns out CEF is just *like that*.
// The first argument is literally nonsense, but it needs to be there as otherwise the second argument doesn't apply
// The second argument turns off CEF's bullshit error handling, which breaks dotnet's error handling.

View File

@@ -8,8 +8,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
<PackageReference Include="Robust.Natives.Cef" Version="102.0.9" />
<PackageReference Include="JetBrains.Annotations" />
<PackageReference Include="Robust.Natives.Cef" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,28 @@
using System.Numerics;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Console;
namespace Robust.Client.WebView;
internal sealed class TestBrowseWindow : DefaultWindow
{
protected override Vector2 ContentsMinimumSize => new Vector2(640, 480);
public TestBrowseWindow()
{
var wv = new WebViewControl();
wv.Url = "https://spacestation14.io";
Contents.AddChild(wv);
}
}
internal sealed class TestBrowseWindowCommand : LocalizedCommands
{
public override string Command => "test_browse_window";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
new TestBrowseWindow().Open();
}
}

View File

@@ -5,6 +5,7 @@ using Robust.Client.WebView.Headless;
using Robust.Client.WebViewHook;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
[assembly: WebViewManagerImpl(typeof(WebViewManager))]
@@ -22,6 +23,9 @@ namespace Robust.Client.WebView
var cfg = dependencies.Resolve<IConfigurationManagerInternal>();
cfg.LoadCVarsFromAssembly(typeof(WebViewManager).Assembly);
var refl = dependencies.Resolve<IReflectionManager>();
refl.LoadAssemblies(typeof(WebViewManager).Assembly);
dependencies.RegisterInstance<IWebViewManager>(this);
dependencies.RegisterInstance<IWebViewManagerInternal>(this);

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using Robust.Client.Audio;
using Robust.Client.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Player;
namespace Robust.Client.Animations
@@ -37,7 +39,12 @@ namespace Robust.Client.Animations
var keyFrame = KeyFrames[keyFrameIndex];
SoundSystem.Play(keyFrame.Resource, Filter.Local(), entity, keyFrame.AudioParamsFunc.Invoke());
var audioParams = keyFrame.AudioParamsFunc.Invoke();
var audio = new SoundPathSpecifier(keyFrame.Resource)
{
Params = audioParams
};
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>().PlayEntity(audio, Filter.Local(), entity, true);
}
return (keyFrameIndex, playingTime);

View File

@@ -0,0 +1,58 @@
using System.Collections.Concurrent;
using OpenTK.Audio.OpenAL;
namespace Robust.Client.Audio;
internal partial class AudioManager
{
// Used to track audio sources that were disposed in the finalizer thread,
// so we need to properly send them off in the main thread.
private readonly ConcurrentQueue<(int sourceHandle, int filterHandle)> _sourceDisposeQueue = new();
private readonly ConcurrentQueue<(int sourceHandle, int filterHandle)> _bufferedSourceDisposeQueue = new();
private readonly ConcurrentQueue<int> _bufferDisposeQueue = new();
public void FlushALDisposeQueues()
{
// Clear out finalized audio sources.
while (_sourceDisposeQueue.TryDequeue(out var handles))
{
OpenALSawmill.Debug("Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
if (IsEfxSupported) RemoveEfx(handles);
AL.DeleteSource(handles.sourceHandle);
_checkAlError();
_audioSources.Remove(handles.sourceHandle);
}
// Clear out finalized buffered audio sources.
while (_bufferedSourceDisposeQueue.TryDequeue(out var handles))
{
OpenALSawmill.Debug("Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
if (IsEfxSupported) RemoveEfx(handles);
AL.DeleteSource(handles.sourceHandle);
_checkAlError();
_bufferedAudioSources.Remove(handles.sourceHandle);
}
// Clear out finalized audio buffers.
while (_bufferDisposeQueue.TryDequeue(out var handle))
{
AL.DeleteBuffer(handle);
_checkAlError();
}
}
internal void DeleteSourceOnMainThread(int sourceHandle, int filterHandle)
{
_sourceDisposeQueue.Enqueue((sourceHandle, filterHandle));
}
internal void DeleteBufferedSourceOnMainThread(int bufferedSourceHandle, int filterHandle)
{
_bufferedSourceDisposeQueue.Enqueue((bufferedSourceHandle, filterHandle));
}
internal void DeleteAudioBufferOnMainThread(int bufferHandle)
{
_bufferDisposeQueue.Enqueue(bufferHandle);
}
}

View File

@@ -0,0 +1,374 @@
using System;
using System.IO;
using System.Numerics;
using System.Threading;
using OpenTK.Audio.OpenAL;
using Robust.Client.Audio.Sources;
using Robust.Client.Graphics;
using Robust.Shared.Audio;
using Robust.Shared.Audio.AudioLoading;
using Robust.Shared.Audio.Sources;
using Robust.Shared.Maths;
namespace Robust.Client.Audio;
internal partial class AudioManager
{
private float _zOffset;
public void SetZOffset(float offset)
{
_zOffset = offset;
}
/// <inheritdoc />
public float GetAttenuationGain(float distance, float rolloffFactor, float referenceDistance, float maxDistance)
{
switch (_attenuation)
{
case Attenuation.LinearDistance:
return 1 - rolloffFactor * (distance - referenceDistance) / (maxDistance - referenceDistance);
case Attenuation.LinearDistanceClamped:
distance = MathF.Max(referenceDistance, MathF.Min(distance, maxDistance));
return 1 - rolloffFactor * (distance - referenceDistance) / (maxDistance - referenceDistance);
default:
// TODO: If you see this you can implement
throw new NotImplementedException();
}
}
public void InitializePostWindowing()
{
_gameThread = Thread.CurrentThread;
InitializeAudio();
}
public void Shutdown()
{
DisposeAllAudio();
if (_openALContext != ALContext.Null)
{
ALC.MakeContextCurrent(ALContext.Null);
ALC.DestroyContext(_openALContext);
}
if (_openALDevice != IntPtr.Zero)
{
ALC.CloseDevice(_openALDevice);
}
}
/// <inheritdoc/>
public void SetVelocity(Vector2 velocity)
{
AL.Listener(ALListener3f.Velocity, velocity.X, velocity.Y, 0f);
}
/// <inheritdoc/>
public void SetPosition(Vector2 position)
{
AL.Listener(ALListener3f.Position, position.X, position.Y, _zOffset);
}
/// <inheritdoc/>
public void SetRotation(Angle angle)
{
var vec = angle.ToVec();
// Default orientation: at: (0, 0, -1) up: (0, 1, 0)
var at = new OpenTK.Mathematics.Vector3(0f, 0f, -1f);
var up = new OpenTK.Mathematics.Vector3(vec.Y, vec.X, 0f);
AL.Listener(ALListenerfv.Orientation, new []{0, 0, -1, vec.X, vec.Y, 0});
AL.Listener(ALListenerfv.Orientation, ref at, ref up);
}
/// <inheritdoc/>
public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null)
{
var vorbis = AudioLoaderOgg.LoadAudioData(stream);
var buffer = AL.GenBuffer();
ALFormat format;
// NVorbis only supports loading into floats.
// If this becomes a problem due to missing extension support (doubt it but ok),
// check the git history, I originally used libvorbisfile which worked and loaded 16 bit LPCM.
if (vorbis.Channels == 1)
{
format = ALFormat.Mono16;
}
else if (vorbis.Channels == 2)
{
format = ALFormat.Stereo16;
}
else
{
throw new InvalidOperationException("Unable to load audio with more than 2 channels.");
}
unsafe
{
fixed (short* ptr = vorbis.Data.Span)
{
AL.BufferData(buffer, format, (IntPtr) ptr, vorbis.Data.Length * sizeof(short),
(int) vorbis.SampleRate);
}
}
_checkAlError();
var handle = new ClydeHandle(_audioSampleBuffers.Count);
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
var length = TimeSpan.FromSeconds(vorbis.TotalSamples / (double) vorbis.SampleRate);
return new AudioStream(handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist);
}
/// <inheritdoc/>
public AudioStream LoadAudioWav(Stream stream, string? name = null)
{
var wav = AudioLoaderWav.LoadAudioData(stream);
var buffer = AL.GenBuffer();
ALFormat format;
if (wav.BitsPerSample == 16)
{
if (wav.NumChannels == 1)
{
format = ALFormat.Mono16;
}
else if (wav.NumChannels == 2)
{
format = ALFormat.Stereo16;
}
else
{
throw new InvalidOperationException("Unable to load audio with more than 2 channels.");
}
}
else if (wav.BitsPerSample == 8)
{
if (wav.NumChannels == 1)
{
format = ALFormat.Mono8;
}
else if (wav.NumChannels == 2)
{
format = ALFormat.Stereo8;
}
else
{
throw new InvalidOperationException("Unable to load audio with more than 2 channels.");
}
}
else
{
throw new InvalidOperationException("Unable to load wav with bits per sample different from 8 or 16");
}
unsafe
{
fixed (byte* ptr = wav.Data.Span)
{
AL.BufferData(buffer, format, (IntPtr) ptr, wav.Data.Length, wav.SampleRate);
}
}
_checkAlError();
var handle = new ClydeHandle(_audioSampleBuffers.Count);
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
var length = TimeSpan.FromSeconds(wav.Data.Length / (double) wav.BlockAlign / wav.SampleRate);
return new AudioStream(handle, length, wav.NumChannels, name);
}
/// <inheritdoc/>
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
{
var fmt = channels switch
{
1 => ALFormat.Mono16,
2 => ALFormat.Stereo16,
_ => throw new ArgumentOutOfRangeException(
nameof(channels), "Only stereo and mono is currently supported")
};
var buffer = AL.GenBuffer();
_checkAlError();
unsafe
{
fixed (short* ptr = samples)
{
AL.BufferData(buffer, fmt, (IntPtr) ptr, samples.Length * sizeof(short), sampleRate);
}
}
_checkAlError();
var handle = new ClydeHandle(_audioSampleBuffers.Count);
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
return new AudioStream(handle, length, channels, name);
}
public void SetMasterGain(float newGain)
{
if (newGain < 0f)
{
OpenALSawmill.Error("Tried to set master gain below 0, clamping to 0");
AL.Listener(ALListenerf.Gain, 0f);
return;
}
#region Platform hack for MacOS
// HACK/BUG: Apple's OpenAL implementation has a bug where values of 0f for listener gain don't actually
// HACK/BUG: prevent sound playback. Workaround is to cap the minimum gain at a value just above 0.
if (OperatingSystem.IsMacOS() && newGain == 0f)
{
OpenALSawmill.Verbose("Not setting gain to 0 because Apple can't write an OpenAL implementation");
AL.Listener(ALListenerf.Gain, float.Epsilon);
return;
}
#endregion Platform hack for MacOS
AL.Listener(ALListenerf.Gain, newGain);
}
public void SetAttenuation(Attenuation attenuation)
{
switch (attenuation)
{
case Attenuation.NoAttenuation:
AL.DistanceModel(ALDistanceModel.None);
break;
case Attenuation.InverseDistance:
AL.DistanceModel(ALDistanceModel.InverseDistance);
break;
case Attenuation.InverseDistanceClamped:
AL.DistanceModel(ALDistanceModel.InverseDistanceClamped);
break;
case Attenuation.LinearDistance:
AL.DistanceModel(ALDistanceModel.LinearDistance);
break;
case Attenuation.LinearDistanceClamped:
AL.DistanceModel(ALDistanceModel.LinearDistanceClamped);
break;
case Attenuation.ExponentDistance:
AL.DistanceModel(ALDistanceModel.ExponentDistance);
break;
case Attenuation.ExponentDistanceClamped:
AL.DistanceModel(ALDistanceModel.ExponentDistanceClamped);
break;
default:
throw new ArgumentOutOfRangeException($"No implementation to set {attenuation.ToString()} for DistanceModel!");
}
_attenuation = attenuation;
OpenALSawmill.Info($"Set audio attenuation to {attenuation.ToString()}");
}
internal void RemoveAudioSource(int handle)
{
_audioSources.Remove(handle);
}
internal void RemoveBufferedAudioSource(int handle)
{
_bufferedAudioSources.Remove(handle);
}
public IAudioSource? CreateAudioSource(AudioStream stream)
{
var source = AL.GenSource();
if (!AL.IsSource(source))
{
OpenALSawmill.Error("Failed to generate source. Too many simultaneous audio streams? {0}", Environment.StackTrace);
return null;
}
// ReSharper disable once PossibleInvalidOperationException
// TODO: This really shouldn't be indexing based on the ClydeHandle...
AL.Source(source, ALSourcei.Buffer, _audioSampleBuffers[(int) stream.ClydeHandle!.Value].BufferHandle);
var audioSource = new AudioSource(this, source, stream);
_audioSources.Add(source, new WeakReference<BaseAudioSource>(audioSource));
ApplyDefaultParams(audioSource);
return audioSource;
}
/// <inheritdoc/>
IBufferedAudioSource? IAudioInternal.CreateBufferedAudioSource(int buffers, bool floatAudio=false)
{
var source = AL.GenSource();
if (!AL.IsSource(source))
{
OpenALSawmill.Error("Failed to generate source. Too many simultaneous audio streams? {0}", Environment.StackTrace);
return null;
}
// ReSharper disable once PossibleInvalidOperationException
var audioSource = new BufferedAudioSource(this, source, AL.GenBuffers(buffers), floatAudio);
_bufferedAudioSources.Add(source, new WeakReference<BufferedAudioSource>(audioSource));
ApplyDefaultParams(audioSource);
return audioSource;
}
private void ApplyDefaultParams(IAudioSource source)
{
source.MaxDistance = AudioParams.Default.MaxDistance;
source.Pitch = AudioParams.Default.Pitch;
source.ReferenceDistance = AudioParams.Default.ReferenceDistance;
source.RolloffFactor = AudioParams.Default.RolloffFactor;
}
/// <inheritdoc />
public void StopAllAudio()
{
foreach (var source in _audioSources.Values)
{
if (source.TryGetTarget(out var target))
{
target.Playing = false;
}
}
foreach (var source in _bufferedAudioSources.Values)
{
if (source.TryGetTarget(out var target))
{
target.Playing = false;
}
}
}
public void DisposeAllAudio()
{
// TODO: Do we even need to stop?
foreach (var source in _audioSources.Values)
{
if (source.TryGetTarget(out var target))
{
target.Dispose();
}
}
_audioSources.Clear();
foreach (var source in _bufferedAudioSources.Values)
{
if (source.TryGetTarget(out var target))
{
target.Dispose();
}
}
_bufferedAudioSources.Clear();
}
}

View File

@@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using OpenTK.Audio.OpenAL;
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using Robust.Client.Audio.Sources;
using Robust.Shared;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
using Robust.Shared.Log;
using Robust.Shared.Utility;
namespace Robust.Client.Audio;
internal sealed partial class AudioManager : IAudioInternal
{
[Shared.IoC.Dependency] private readonly IConfigurationManager _cfg = default!;
[Shared.IoC.Dependency] private readonly ILogManager _logMan = default!;
private Thread? _gameThread;
private ALDevice _openALDevice;
private ALContext _openALContext;
private readonly List<LoadedAudioSample> _audioSampleBuffers = new();
private readonly Dictionary<int, WeakReference<BaseAudioSource>> _audioSources =
new();
private readonly Dictionary<int, WeakReference<BufferedAudioSource>> _bufferedAudioSources =
new();
private readonly HashSet<string> _alcDeviceExtensions = new();
private readonly HashSet<string> _alContextExtensions = new();
private Attenuation _attenuation;
public bool HasAlDeviceExtension(string extension) => _alcDeviceExtensions.Contains(extension);
public bool HasAlContextExtension(string extension) => _alContextExtensions.Contains(extension);
internal bool IsEfxSupported;
internal ISawmill OpenALSawmill = default!;
private void _audioCreateContext()
{
unsafe
{
_openALContext = ALC.CreateContext(_openALDevice, (int*) 0);
}
ALC.MakeContextCurrent(_openALContext);
_checkAlcError(_openALDevice);
_checkAlError();
// Load up AL context extensions.
var s = ALC.GetString(ALDevice.Null, AlcGetString.Extensions) ?? "";
foreach (var extension in s.Split(' '))
{
_alContextExtensions.Add(extension);
}
OpenALSawmill.Debug("OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor));
OpenALSawmill.Debug("OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer));
OpenALSawmill.Debug("OpenAL Version: {0}", AL.Get(ALGetString.Version));
}
private bool _audioOpenDevice()
{
var preferredDevice = _cfg.GetCVar(CVars.AudioDevice);
// Open device.
if (!string.IsNullOrEmpty(preferredDevice))
{
_openALDevice = ALC.OpenDevice(preferredDevice);
if (_openALDevice == IntPtr.Zero)
{
OpenALSawmill.Warning("Unable to open preferred audio device '{0}': {1}. Falling back default.",
preferredDevice, ALC.GetError(ALDevice.Null));
_openALDevice = ALC.OpenDevice(null);
}
}
else
{
_openALDevice = ALC.OpenDevice(null);
}
_checkAlcError(_openALDevice);
if (_openALDevice == IntPtr.Zero)
{
OpenALSawmill.Error("Unable to open OpenAL device! {1}", ALC.GetError(ALDevice.Null));
return false;
}
// Load up ALC extensions.
var s = ALC.GetString(_openALDevice, AlcGetString.Extensions) ?? "";
foreach (var extension in s.Split(' '))
{
_alcDeviceExtensions.Add(extension);
}
return true;
}
private void InitializeAudio()
{
OpenALSawmill = _logMan.GetSawmill("clyde.oal");
if (!_audioOpenDevice())
return;
// Create OpenAL context.
_audioCreateContext();
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterGain, true);
}
internal bool IsMainThread()
{
return Thread.CurrentThread == _gameThread;
}
private static void RemoveEfx((int sourceHandle, int filterHandle) handles)
{
if (handles.filterHandle != 0)
EFX.DeleteFilter(handles.filterHandle);
}
private void _checkAlcError(ALDevice device,
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLineNumber = -1)
{
var error = ALC.GetError(device);
if (error != AlcError.NoError)
{
OpenALSawmill.Error("[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
}
}
/// <summary>
/// Like _checkAlError but allows custom data to be passed in as relevant.
/// </summary>
internal void LogALError(string message, [CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1)
{
var error = AL.GetError();
if (error != ALError.NoError)
{
OpenALSawmill.Error("[{0}:{1}] AL error: {2}, {3}. Stacktrace is {4}", callerMember, callerLineNumber, error, message, Environment.StackTrace);
}
}
public void _checkAlError([CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1)
{
var error = AL.GetError();
if (error != ALError.NoError)
{
OpenALSawmill.Error("[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
}
}
private sealed class LoadedAudioSample
{
public readonly int BufferHandle;
public LoadedAudioSample(int bufferHandle)
{
BufferHandle = bufferHandle;
}
}
}

View File

@@ -0,0 +1,91 @@
using System.Numerics;
using System.Text;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Shared.Audio;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using AudioComponent = Robust.Shared.Audio.Components.AudioComponent;
namespace Robust.Client.Audio;
/// <summary>
/// Debug overlay for audio.
/// </summary>
public sealed class AudioOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
private IEntityManager _entManager;
private IPlayerManager _playerManager;
private AudioSystem _audio;
private SharedTransformSystem _transform;
private Font _font;
public AudioOverlay(IEntityManager entManager, IPlayerManager playerManager, IResourceCache cache, AudioSystem audio, SharedTransformSystem transform)
{
_entManager = entManager;
_playerManager = playerManager;
_audio = audio;
_transform = transform;
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
}
protected internal override void Draw(in OverlayDrawArgs args)
{
var localPlayer = _playerManager.LocalEntity;
if (args.ViewportControl == null || localPlayer == null)
return;
var screenHandle = args.ScreenHandle;
var output = new StringBuilder();
var listenerPos = _entManager.GetComponent<TransformComponent>(localPlayer.Value).MapPosition;
if (listenerPos.MapId != args.MapId)
return;
var query = _entManager.AllEntityQueryEnumerator<AudioComponent>();
while (query.MoveNext(out var uid, out var comp))
{
var mapId = MapId.Nullspace;
var audioPos = Vector2.Zero;
if (_entManager.TryGetComponent<TransformComponent>(uid, out var xform))
{
mapId = xform.MapID;
audioPos = _transform.GetWorldPosition(uid);
}
if (mapId != args.MapId)
continue;
var screenPos = args.ViewportControl.WorldToScreen(audioPos);
var distance = audioPos - listenerPos.Position;
var posOcclusion = _audio.GetOcclusion(listenerPos, distance, distance.Length(), uid);
output.Clear();
output.AppendLine("Audio Source");
output.AppendLine("Runtime:");
output.AppendLine($"- Distance: {_audio.GetAudioDistance(distance.Length()):0.00}");
output.AppendLine($"- Occlusion: {posOcclusion:0.0000}");
output.AppendLine("Params:");
output.AppendLine($"- RolloffFactor: {comp.RolloffFactor:0.0000}");
output.AppendLine($"- Volume: {comp.Volume:0.0000}");
output.AppendLine($"- Reference distance: {comp.ReferenceDistance:0.00}");
output.AppendLine($"- Max distance: {comp.MaxDistance:0.00}");
var outputText = output.ToString().Trim();
var dimensions = screenHandle.GetDimensions(_font, outputText, 1f);
var buffer = new Vector2(3f, 3f);
screenHandle.DrawRect(new UIBox2(screenPos - buffer, screenPos + dimensions + buffer), new Color(39, 39, 48));
screenHandle.DrawString(_font, screenPos, outputText);
}
}
}

View File

@@ -1,18 +1,21 @@
using System;
using Robust.Client.Graphics;
using Robust.Shared.Graphics;
namespace Robust.Client.Audio;
/// <summary>
/// Has the metadata for a particular audio stream as well as the relevant internal handle to it.
/// </summary>
public sealed class AudioStream
{
public TimeSpan Length { get; }
internal ClydeHandle? ClydeHandle { get; }
internal IClydeHandle? ClydeHandle { get; }
public string? Name { get; }
public string? Title { get; }
public string? Artist { get; }
public int ChannelCount { get; }
internal AudioStream(ClydeHandle handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null)
internal AudioStream(IClydeHandle? handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null)
{
ClydeHandle = handle;
Length = length;

View File

@@ -0,0 +1,76 @@
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using Robust.Client.Audio.Effects;
using Robust.Shared.Audio.Components;
using Robust.Shared.GameObjects;
namespace Robust.Client.Audio;
public sealed partial class AudioSystem
{
protected override void InitializeEffect()
{
base.InitializeEffect();
SubscribeLocalEvent<AudioEffectComponent, ComponentAdd>(OnEffectAdd);
SubscribeLocalEvent<AudioEffectComponent, ComponentShutdown>(OnEffectShutdown);
SubscribeLocalEvent<AudioAuxiliaryComponent, ComponentAdd>(OnAuxiliaryAdd);
SubscribeLocalEvent<AudioAuxiliaryComponent, AfterAutoHandleStateEvent>(OnAuxiliaryAuto);
}
private void OnEffectAdd(EntityUid uid, AudioEffectComponent component, ComponentAdd args)
{
var effect = new AudioEffect(_audio);
component.Effect = effect;
}
private void OnEffectShutdown(EntityUid uid, AudioEffectComponent component, ComponentShutdown args)
{
if (component.Effect is AudioEffect effect)
{
effect.Dispose();
}
}
private void OnAuxiliaryAdd(EntityUid uid, AudioAuxiliaryComponent component, ComponentAdd args)
{
component.Auxiliary = new AuxiliaryAudio();
}
private void OnAuxiliaryAuto(EntityUid uid, AudioAuxiliaryComponent component, ref AfterAutoHandleStateEvent args)
{
if (TryComp<AudioEffectComponent>(component.Effect, out var effectComp))
{
component.Auxiliary.SetEffect(effectComp.Effect);
}
else
{
component.Auxiliary.SetEffect(null);
}
}
public override void SetAuxiliary(EntityUid uid, AudioComponent audio, EntityUid? auxUid)
{
base.SetAuxiliary(uid, audio, auxUid);
if (TryComp<AudioAuxiliaryComponent>(audio.Auxiliary, out var auxComp))
{
audio.Source.SetAuxiliary(auxComp.Auxiliary);
}
else
{
audio.Source.SetAuxiliary(null);
}
}
public override void SetEffect(EntityUid auxUid, AudioAuxiliaryComponent aux, EntityUid? effectUid)
{
base.SetEffect(auxUid, aux, effectUid);
if (TryComp<AudioEffectComponent>(aux.Effect, out var effectComp))
{
aux.Auxiliary.SetEffect(effectComp.Effect);
}
else
{
aux.Auxiliary.SetEffect(null);
}
}
}

View File

@@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Robust.Shared;
using Robust.Shared.GameObjects;
namespace Robust.Client.Audio;
public sealed partial class AudioSystem
{
/*
* Handles limiting concurrent sounds for audio to avoid blowing the source budget on one sound getting spammed.
*/
private readonly Dictionary<string, int> _playingCount = new();
private int _maxConcurrent;
private void InitializeLimit()
{
Subs.CVar(CfgManager, CVars.AudioDefaultConcurrent, SetConcurrentLimit, true);
}
private void SetConcurrentLimit(int obj)
{
_maxConcurrent = obj;
}
private bool TryAudioLimit(string sound)
{
if (string.IsNullOrEmpty(sound))
return true;
ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(_playingCount, sound, out _);
if (count >= _maxConcurrent)
return false;
count++;
return true;
}
private void RemoveAudioLimit(string sound)
{
if (!_playingCount.TryGetValue(sound, out var count))
return;
count--;
if (count <= 0)
{
_playingCount.Remove(sound);
return;
}
_playingCount[sound] = count;
}
}

View File

@@ -0,0 +1,744 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Shared;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Components;
using Robust.Shared.Audio.Sources;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Replays;
using Robust.Shared.Threading;
using Robust.Shared.Utility;
namespace Robust.Client.Audio;
public sealed partial class AudioSystem : SharedAudioSystem
{
/*
* There's still a lot more OpenAL can do in terms of filters, auxiliary slots, etc.
* but exposing the whole thing in an easy way is a lot of effort.
*/
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IParallelManager _parMan = default!;
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
[Dependency] private readonly IAudioInternal _audio = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
/// <summary>
/// Per-tick cache of relevant streams.
/// </summary>
private readonly List<(EntityUid Entity, AudioComponent Component, TransformComponent Xform)> _streams = new();
private EntityUid? _listenerGrid;
private UpdateAudioJob _updateAudioJob;
private EntityQuery<PhysicsComponent> _physicsQuery;
private float _maxRayLength;
public override float ZOffset
{
get => _zOffset;
protected set
{
_zOffset = value;
_audio.SetZOffset(value);
var query = AllEntityQuery<AudioComponent>();
while (query.MoveNext(out var audio))
{
// Pythagoras back to normal then adjust.
var maxDistance = GetAudioDistance(audio.Params.MaxDistance);
var refDistance = GetAudioDistance(audio.Params.ReferenceDistance);
audio.MaxDistance = maxDistance;
audio.ReferenceDistance = refDistance;
}
}
}
private float _zOffset;
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
_updateAudioJob = new UpdateAudioJob
{
System = this,
Streams = _streams,
};
UpdatesOutsidePrediction = true;
// Need to run after Eye updates so we have an accurate listener position.
UpdatesAfter.Add(typeof(EyeSystem));
_physicsQuery = GetEntityQuery<PhysicsComponent>();
SubscribeLocalEvent<AudioComponent, ComponentStartup>(OnAudioStartup);
SubscribeLocalEvent<AudioComponent, ComponentShutdown>(OnAudioShutdown);
SubscribeLocalEvent<AudioComponent, EntityPausedEvent>(OnAudioPaused);
SubscribeLocalEvent<AudioComponent, AfterAutoHandleStateEvent>(OnAudioState);
// Replay stuff
SubscribeNetworkEvent<PlayAudioGlobalMessage>(OnGlobalAudio);
SubscribeNetworkEvent<PlayAudioEntityMessage>(OnEntityAudio);
SubscribeNetworkEvent<PlayAudioPositionalMessage>(OnEntityCoordinates);
Subs.CVar(CfgManager, CVars.AudioAttenuation, OnAudioAttenuation, true);
Subs.CVar(CfgManager, CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
InitializeLimit();
}
private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
{
ApplyAudioParams(component.Params, component);
component.Source.Global = component.Global;
if (TryComp<AudioAuxiliaryComponent>(component.Auxiliary, out var auxComp))
{
component.Source.SetAuxiliary(auxComp.Auxiliary);
}
else
{
component.Source.SetAuxiliary(null);
}
switch (component.State)
{
case AudioState.Playing:
component.StartPlaying();
break;
case AudioState.Paused:
component.Pause();
break;
case AudioState.Stopped:
component.StopPlaying();
component.PlaybackPosition = 0f;
break;
}
// If playback position changed then update it.
if (!string.IsNullOrEmpty(component.FileName))
{
var position = (float) ((component.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds;
var currentPosition = component.Source.PlaybackPosition;
var diff = Math.Abs(position - currentPosition);
if (diff > 0.1f)
{
component.PlaybackPosition = position;
}
}
}
/// <summary>
/// Sets the volume for the entire game.
/// </summary>
public void SetMasterVolume(float value)
{
_audio.SetMasterGain(value);
}
private void OnAudioPaused(EntityUid uid, AudioComponent component, ref EntityPausedEvent args)
{
component.Pause();
}
protected override void OnAudioUnpaused(EntityUid uid, AudioComponent component, ref EntityUnpausedEvent args)
{
base.OnAudioUnpaused(uid, component, ref args);
component.StartPlaying();
}
private void OnAudioStartup(EntityUid uid, AudioComponent component, ComponentStartup args)
{
if (!Timing.ApplyingState && !Timing.IsFirstTimePredicted)
{
return;
}
// Source has already been set
if (component.Loaded)
{
return;
}
if (!TryGetAudio(component.FileName, out var audioResource))
{
Log.Error($"Error creating audio source for {audioResource}, can't find file {component.FileName}");
return;
}
SetupSource((uid, component), audioResource);
component.Loaded = true;
}
private void SetupSource(Entity<AudioComponent> entity, AudioResource audioResource, TimeSpan? length = null)
{
var component = entity.Comp;
if (TryAudioLimit(component.FileName))
{
var newSource = _audio.CreateAudioSource(audioResource);
if (newSource == null)
{
Log.Error($"Error creating audio source for {audioResource}");
DebugTools.Assert(false);
}
else
{
component.Source = newSource;
}
}
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
{
_metadata.SetFlag(entity.Owner, MetaDataFlags.Undetachable, true);
}
// Need to set all initial data for first frame.
ApplyAudioParams(component.Params, component);
component.Source.Global = component.Global;
// Don't play until first frame so occlusion etc. are correct.
component.Gain = 0f;
length ??= GetAudioLength(component.FileName);
// If audio came into range then start playback at the correct position.
var offset = (Timing.CurTime - component.AudioStart).TotalSeconds % length.Value.TotalSeconds;
if (offset > 0)
{
component.PlaybackPosition = (float) offset;
}
}
private void OnAudioShutdown(EntityUid uid, AudioComponent component, ComponentShutdown args)
{
// Breaks with prediction?
component.Source.Dispose();
RemoveAudioLimit(component.FileName);
}
private void OnAudioAttenuation(int obj)
{
_audio.SetAttenuation((Attenuation) obj);
}
private void OnRaycastLengthChanged(float value)
{
_maxRayLength = value;
}
public override void FrameUpdate(float frameTime)
{
var eye = _eyeManager.CurrentEye;
var localEntity = _playerManager.LocalEntity;
Vector2 listenerVelocity;
if (localEntity != null)
listenerVelocity = _physics.GetMapLinearVelocity(localEntity.Value);
else
listenerVelocity = Vector2.Zero;
_audio.SetVelocity(listenerVelocity);
_audio.SetRotation(eye.Rotation);
_audio.SetPosition(eye.Position.Position);
var ourPos = GetListenerCoordinates();
var query = AllEntityQuery<AudioComponent, TransformComponent>();
_streams.Clear();
while (query.MoveNext(out var uid, out var comp, out var xform))
{
_streams.Add((uid, comp, xform));
}
_mapManager.TryFindGridAt(ourPos, out var gridUid, out _);
_listenerGrid = gridUid == EntityUid.Invalid ? null : gridUid;
try
{
_updateAudioJob.OurPosition = ourPos;
_parMan.ProcessNow(_updateAudioJob, _streams.Count);
}
catch (Exception e)
{
Log.Error($"Caught exception while processing entity streams.");
_runtimeLog.LogException(e, $"{nameof(AudioSystem)}.{nameof(FrameUpdate)}");
}
}
public MapCoordinates GetListenerCoordinates()
{
return _eyeManager.CurrentEye.Position;
}
private void ProcessStream(EntityUid entity, AudioComponent component, TransformComponent xform, MapCoordinates listener)
{
// TODO:
// I Originally tried to be fancier here but it caused audio issues so just trying
// to replicate the old behaviour for now.
if (!component.Started)
{
component.Started = true;
component.StartPlaying();
}
// If it's global but on another map (that isn't nullspace) then stop playing it.
if (component.Global)
{
if (xform.MapID != MapId.Nullspace && listener.MapId != xform.MapID)
{
component.Gain = 0f;
return;
}
// Resume playing.
component.Volume = component.Params.Volume;
return;
}
// Non-global sounds, stop playing if on another map.
// Not relevant to us.
if (listener.MapId != xform.MapID)
{
component.Gain = 0f;
return;
}
Vector2 worldPos;
var gridUid = xform.ParentUid;
// Handle grid audio differently by using nearest-edge instead of entity centre.
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
{
// It's our grid so max volume.
if (_listenerGrid == gridUid)
{
component.Volume = component.Params.Volume;
component.Occlusion = 0f;
component.Position = listener.Position;
return;
}
// TODO: Need a grid-optimised version because this is gonna be expensive.
// Just to avoid clipping on and off grid or nearestPoint changing we'll
// always set the sound to listener's pos, we'll just manually do gain ourselves.
if (_physics.TryGetNearest(gridUid, listener, out _, out var gridDistance))
{
// Out of range
if (gridDistance > component.MaxDistance)
{
component.Gain = 0f;
return;
}
var paramsGain = VolumeToGain(component.Params.Volume);
// Thought I'd never have to manually calculate gain again but this is the least
// unpleasant audio I could get at the moment.
component.Gain = paramsGain * _audio.GetAttenuationGain(
gridDistance,
component.Params.RolloffFactor,
component.Params.ReferenceDistance,
component.Params.MaxDistance);
component.Position = listener.Position;
return;
}
// Can't get nearest point so don't play anymore.
component.Gain = 0f;
return;
}
worldPos = _xformSys.GetWorldPosition(entity);
component.Volume = component.Params.Volume;
// Max distance check
var delta = worldPos - listener.Position;
var distance = delta.Length();
// Out of range so just clip it for us.
if (GetAudioDistance(distance) > component.MaxDistance)
{
// Still keeps the source playing, just with no volume.
component.Gain = 0f;
return;
}
if (distance > 0f && distance < 0.01f)
{
worldPos = listener.Position;
delta = Vector2.Zero;
distance = 0f;
}
// Update audio occlusion
var occlusion = GetOcclusion(listener, delta, distance, entity);
component.Occlusion = occlusion;
// Update audio positions.
component.Position = worldPos;
// Make race cars go NYYEEOOOOOMMMMM
if (_physicsQuery.TryGetComponent(entity, out var physicsComp))
{
// This actually gets the tracked entity's xform & iterates up though the parents for the second time. Bit
// inefficient.
var velocity = _physics.GetMapLinearVelocity(entity, physicsComp, xform);
component.Velocity = velocity;
}
}
/// <summary>
/// Gets the audio occlusion from the target audio entity to the listener's position.
/// </summary>
public float GetOcclusion(MapCoordinates listener, Vector2 delta, float distance, EntityUid? ignoredEnt = null)
{
float occlusion = 0;
if (distance > 0.1)
{
var rayLength = MathF.Min(distance, _maxRayLength);
var ray = new CollisionRay(listener.Position, delta / distance, OcclusionCollisionMask);
occlusion = _physics.IntersectRayPenetration(listener.MapId, ray, rayLength, ignoredEnt);
}
return occlusion;
}
private bool TryGetAudio(string filename, [NotNullWhen(true)] out AudioResource? audio)
{
if (_resourceCache.TryGetResource(new ResPath(filename), out audio))
return true;
Log.Error($"Server tried to play audio file {filename} which does not exist.");
return false;
}
private bool TryGetAudio(AudioStream stream, [NotNullWhen(true)] out AudioResource? audio)
{
if (_resourceCache.TryGetResource(stream, out audio))
return true;
Log.Error($"Server failed to play audio stream {stream.Title}.");
return false;
}
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates,
AudioParams? audioParams = null)
{
return PlayStatic(filename, Filter.Local(), coordinates, true, audioParams);
}
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null)
{
return PlayEntity(filename, Filter.Local(), uid, true, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null)
{
if (Timing.IsFirstTimePredicted && sound != null)
return PlayEntity(sound, Filter.Local(), source, false, audioParams);
return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio....
}
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null)
{
if (Timing.IsFirstTimePredicted && sound != null)
return PlayStatic(sound, Filter.Local(), coordinates, false, audioParams);
return null;
}
/// <summary>
/// Play an audio file globally, without position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="audioParams"></param>
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, AudioParams? audioParams = null, bool recordReplay = true)
{
if (string.IsNullOrEmpty(filename))
return null;
if (recordReplay && _replayRecording.IsRecording)
{
_replayRecording.RecordReplayMessage(new PlayAudioGlobalMessage
{
FileName = filename,
AudioParams = audioParams ?? AudioParams.Default
});
}
return TryGetAudio(filename, out var audio) ? PlayGlobal(audio, audioParams) : default;
}
/// <summary>
/// Play an audio stream globally, without position.
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="audioParams"></param>
public (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, AudioParams? audioParams = null)
{
var (entity, component) = CreateAndStartPlayingStream(audioParams, stream);
component.Global = true;
component.Source.Global = true;
Dirty(entity, component);
return (entity, component);
}
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
private (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true)
{
if (string.IsNullOrEmpty(filename))
return null;
if (recordReplay && _replayRecording.IsRecording)
{
_replayRecording.RecordReplayMessage(new PlayAudioEntityMessage
{
FileName = filename,
NetEntity = GetNetEntity(entity),
AudioParams = audioParams ?? AudioParams.Default
});
}
return TryGetAudio(filename, out var audio) ? PlayEntity(audio, entity, audioParams) : default;
}
/// <summary>
/// Play an audio stream following an entity.
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
public (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, AudioParams? audioParams = null)
{
if (TerminatingOrDeleted(entity))
{
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(entity)}");
return null;
}
var playing = CreateAndStartPlayingStream(audioParams, stream);
_xformSys.SetCoordinates(playing.Entity, new EntityCoordinates(entity, Vector2.Zero));
return playing;
}
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
private (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true)
{
if (string.IsNullOrEmpty(filename))
return null;
if (recordReplay && _replayRecording.IsRecording)
{
_replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage
{
FileName = filename,
Coordinates = GetNetCoordinates(coordinates),
AudioParams = audioParams ?? AudioParams.Default
});
}
return TryGetAudio(filename, out var audio) ? PlayStatic(audio, coordinates, audioParams) : default;
}
/// <summary>
/// Play an audio stream at a static position.
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
public (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
if (TerminatingOrDeleted(coordinates.EntityId))
{
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}");
return null;
}
var playing = CreateAndStartPlayingStream(audioParams, stream);
_xformSys.SetCoordinates(playing.Entity, coordinates);
return playing;
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
{
return PlayGlobal(filename, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null)
{
return PlayEntity(filename, entity, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
{
return PlayStatic(filename, coordinates, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null)
{
return PlayGlobal(filename, audioParams);
}
public override void LoadStream<T>(Entity<AudioComponent> entity, T stream)
{
if (stream is AudioStream audioStream)
{
TryGetAudio(audioStream, out var audio);
SetupSource(entity, audio!, audioStream.Length);
entity.Comp.Loaded = true;
}
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null)
{
return PlayGlobal(filename, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
{
return PlayEntity(filename, uid, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
{
return PlayEntity(filename, uid, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
return PlayStatic(filename, coordinates, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
return PlayStatic(filename, coordinates, audioParams);
}
private (EntityUid Entity, AudioComponent Component) CreateAndStartPlayingStream(AudioParams? audioParams, AudioStream stream)
{
var audioP = audioParams ?? AudioParams.Default;
var entity = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace);
var comp = SetupAudio(entity, null, audioP, stream.Length);
LoadStream((entity, comp), stream);
EntityManager.InitializeAndStartEntity(entity);
var source = comp.Source;
// TODO clamp the offset inside of SetPlaybackPosition() itself.
var offset = audioP.PlayOffsetSeconds;
offset = Math.Clamp(offset, 0f, (float) stream.Length.TotalSeconds - 0.01f);
source.PlaybackPosition = offset;
// For server we will rely on the adjusted one but locally we will have to adjust it ourselves.
ApplyAudioParams(comp.Params, comp);
source.StartPlaying();
return (entity, comp);
}
/// <summary>
/// Applies the audioparams to the underlying audio source.
/// </summary>
private void ApplyAudioParams(AudioParams audioParams, IAudioSource source)
{
source.Pitch = audioParams.Pitch;
source.Volume = audioParams.Volume;
source.RolloffFactor = audioParams.RolloffFactor;
source.MaxDistance = GetAudioDistance(audioParams.MaxDistance);
source.ReferenceDistance = GetAudioDistance(audioParams.ReferenceDistance);
source.Looping = audioParams.Loop;
}
private void OnEntityCoordinates(PlayAudioPositionalMessage ev)
{
PlayStatic(ev.FileName, GetCoordinates(ev.Coordinates), ev.AudioParams, false);
}
private void OnEntityAudio(PlayAudioEntityMessage ev)
{
PlayEntity(ev.FileName, GetEntity(ev.NetEntity), ev.AudioParams, false);
}
private void OnGlobalAudio(PlayAudioGlobalMessage ev)
{
PlayGlobal(ev.FileName, ev.AudioParams, false);
}
protected override TimeSpan GetAudioLengthImpl(string filename)
{
return _resourceCache.GetResource<AudioResource>(filename).AudioStream.Length;
}
#region Jobs
private record struct UpdateAudioJob : IParallelRobustJob
{
public int BatchSize => 2;
public AudioSystem System;
public MapCoordinates OurPosition;
public List<(EntityUid Entity, AudioComponent Component, TransformComponent Xform)> Streams;
public void Execute(int index)
{
var comp = Streams[index];
System.ProcessStream(comp.Entity, comp.Component, comp.Xform, OurPosition);
}
}
#endregion
}

View File

@@ -0,0 +1,455 @@
using System;
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Effects;
using Robust.Shared.Maths;
namespace Robust.Client.Audio.Effects;
/// <inheritdoc />
internal sealed class AudioEffect : IAudioEffect
{
internal int Handle;
private readonly IAudioInternal _master;
public AudioEffect(IAudioInternal manager)
{
Handle = EFX.GenEffect();
_master = manager;
EFX.Effect(Handle, EffectInteger.EffectType, (int) EffectType.EaxReverb);
}
public void Dispose()
{
if (Handle != 0)
{
EFX.DeleteEffect(Handle);
Handle = 0;
}
}
private void _checkDisposed()
{
if (Handle == -1)
{
throw new ObjectDisposedException(nameof(AudioEffect));
}
}
/// <inheritdoc />
public float Density
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbDensity, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbDensity, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float Diffusion
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbDiffusion, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbDiffusion, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float Gain
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbGain, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbGain, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float GainHF
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbGainHF, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbGainHF, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float GainLF
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbGainLF, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbGainLF, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float DecayTime
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayTime, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbDecayTime, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float DecayHFRatio
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayHFRatio, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbDecayHFRatio, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float DecayLFRatio
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbDecayLFRatio, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbDecayLFRatio, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float ReflectionsGain
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsGain, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsGain, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float ReflectionsDelay
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbReflectionsDelay, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbReflectionsDelay, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public Vector3 ReflectionsPan
{
get
{
_checkDisposed();
var value = EFX.GetEffect(Handle, EffectVector3.EaxReverbReflectionsPan);
_master._checkAlError();
return new Vector3(value.X, value.Z, value.Y);
}
set
{
_checkDisposed();
var openVec = new OpenTK.Mathematics.Vector3(value.X, value.Y, value.Z);
EFX.Effect(Handle, EffectVector3.EaxReverbReflectionsPan, ref openVec);
_master._checkAlError();
}
}
/// <inheritdoc />
public float LateReverbGain
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbGain, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbGain, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float LateReverbDelay
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbLateReverbDelay, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbLateReverbDelay, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public Vector3 LateReverbPan
{
get
{
_checkDisposed();
var value = EFX.GetEffect(Handle, EffectVector3.EaxReverbLateReverbPan);
_master._checkAlError();
return new Vector3(value.X, value.Z, value.Y);
}
set
{
_checkDisposed();
var openVec = new OpenTK.Mathematics.Vector3(value.X, value.Y, value.Z);
EFX.Effect(Handle, EffectVector3.EaxReverbLateReverbPan, ref openVec);
_master._checkAlError();
}
}
/// <inheritdoc />
public float EchoTime
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoTime, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbEchoTime, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float EchoDepth
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbEchoDepth, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbEchoDepth, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float ModulationTime
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationTime, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbModulationTime, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float ModulationDepth
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbModulationDepth, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbModulationDepth, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float AirAbsorptionGainHF
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbAirAbsorptionGainHF, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float HFReference
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbHFReference, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbHFReference, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float LFReference
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbLFReference, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbLFReference, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public float RoomRolloffFactor
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectFloat.EaxReverbRoomRolloffFactor, value);
_master._checkAlError();
}
}
/// <inheritdoc />
public int DecayHFLimit
{
get
{
_checkDisposed();
EFX.GetEffect(Handle, EffectInteger.EaxReverbDecayHFLimit, out var value);
_master._checkAlError();
return value;
}
set
{
_checkDisposed();
EFX.Effect(Handle, EffectInteger.EaxReverbDecayHFLimit, value);
_master._checkAlError();
}
}
}

View File

@@ -0,0 +1,32 @@
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using Robust.Shared.Audio.Effects;
namespace Robust.Client.Audio.Effects;
/// <inheritdoc />
internal sealed class AuxiliaryAudio : IAuxiliaryAudio
{
internal int Handle = EFX.GenAuxiliaryEffectSlot();
public void Dispose()
{
if (Handle != -1)
{
EFX.DeleteAuxiliaryEffectSlot(Handle);
Handle = -1;
}
}
/// <inheritdoc />
public void SetEffect(IAudioEffect? effect)
{
if (effect is AudioEffect audEffect)
{
EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, audEffect.Handle);
}
else
{
EFX.AuxiliaryEffectSlot(Handle, EffectSlotInteger.Effect, 0);
}
}
}

View File

@@ -0,0 +1,111 @@
using System;
using System.IO;
using System.Numerics;
using Robust.Shared.Audio;
using Robust.Shared.Audio.AudioLoading;
using Robust.Shared.Audio.Sources;
using Robust.Shared.Maths;
namespace Robust.Client.Audio;
/// <summary>
/// Headless client audio.
/// </summary>
internal sealed class HeadlessAudioManager : IAudioInternal
{
/// <inheritdoc />
public void InitializePostWindowing()
{
}
/// <inheritdoc />
public void Shutdown()
{
}
/// <inheritdoc />
public void FlushALDisposeQueues()
{
}
/// <inheritdoc />
public IAudioSource CreateAudioSource(AudioStream stream)
{
return DummyAudioSource.Instance;
}
/// <inheritdoc />
public IBufferedAudioSource? CreateBufferedAudioSource(int buffers, bool floatAudio = false)
{
return DummyBufferedAudioSource.Instance;
}
/// <inheritdoc />
public void SetVelocity(Vector2 velocity)
{
}
/// <inheritdoc />
public void SetPosition(Vector2 position)
{
}
/// <inheritdoc />
public void SetRotation(Angle angle)
{
}
/// <inheritdoc />
public void SetMasterGain(float newGain)
{
}
/// <inheritdoc />
public void SetAttenuation(Attenuation attenuation)
{
}
/// <inheritdoc />
public void StopAllAudio()
{
}
/// <inheritdoc />
public void SetZOffset(float f)
{
}
/// <inheritdoc />
public void _checkAlError(string callerMember = "", int callerLineNumber = -1)
{
}
/// <inheritdoc />
public float GetAttenuationGain(float distance, float rolloffFactor, float referenceDistance, float maxDistance)
{
return 0f;
}
public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null)
{
var metadata = AudioLoaderOgg.LoadAudioMetadata(stream);
return AudioStreamFromMetadata(metadata, name);
}
public AudioStream LoadAudioWav(Stream stream, string? name = null)
{
var metadata = AudioLoaderWav.LoadAudioMetadata(stream);
return AudioStreamFromMetadata(metadata, name);
}
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
{
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
return new AudioStream(null, length, channels, name);
}
private static AudioStream AudioStreamFromMetadata(AudioMetadata metadata, string? name)
{
return new AudioStream(null, metadata.Length, metadata.ChannelCount, name, metadata.Title, metadata.Artist);
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Sources;
using Robust.Shared.Maths;
namespace Robust.Client.Audio;
/// <summary>
/// Handles clientside audio.
/// </summary>
internal interface IAudioInternal : IAudioManager
{
void InitializePostWindowing();
void Shutdown();
/// <summary>
/// Flushes all pending queues for disposing of AL sources.
/// </summary>
void FlushALDisposeQueues();
/// <summary>
/// Returns a buffered audio source.
/// </summary>
/// <returns>null if unable to create the source.</returns>
IBufferedAudioSource? CreateBufferedAudioSource(int buffers, bool floatAudio=false);
/// <summary>
/// Sets the velocity for the audio listener.
/// </summary>
void SetVelocity(Vector2 velocity);
/// <summary>
/// Sets position for the audio listener.
/// </summary>
void SetPosition(Vector2 position);
/// <summary>
/// Sets rotation for the audio listener.
/// </summary>
void SetRotation(Angle angle);
void SetAttenuation(Attenuation attenuation);
/// <summary>
/// Stops all audio from playing.
/// </summary>
void StopAllAudio();
/// <summary>
/// Sets the Z-offset for the audio listener.
/// </summary>
void SetZOffset(float f);
void _checkAlError([CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1);
/// <summary>
/// Manually calculates the specified gain for an attenuation source with the specified distance.
/// </summary>
float GetAttenuationGain(float distance, float rolloffFactor, float referenceDistance, float maxDistance);
}

View File

@@ -0,0 +1,22 @@
using System;
using System.IO;
using Robust.Client.Audio.Sources;
using Robust.Shared.Audio.Sources;
namespace Robust.Client.Audio;
/// <summary>
/// Public audio API for stuff that can't go through <see cref="AudioSystem"/>
/// </summary>
public interface IAudioManager
{
IAudioSource? CreateAudioSource(AudioStream stream);
AudioStream LoadAudioOggVorbis(Stream stream, string? name = null);
AudioStream LoadAudioWav(Stream stream, string? name = null);
AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null);
void SetMasterGain(float gain);
}

View File

@@ -17,11 +17,9 @@ public interface IMidiManager
bool IsAvailable { get; }
/// <summary>
/// Volume, in db.
/// Gain of audio.
/// </summary>
float Volume { get; set; }
public int OcclusionCollisionMask { get; set; }
float Gain { get; set; }
/// <summary>
/// This method tries to return a midi renderer ready to be used.

View File

@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Audio.Midi;
using Robust.Shared.Audio.Sources;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
@@ -20,7 +21,7 @@ public interface IMidiRenderer : IDisposable
/// <summary>
/// The buffered audio source of this renderer.
/// </summary>
internal IClydeBufferedAudioSource Source { get; }
internal IBufferedAudioSource Source { get; }
/// <summary>
/// Whether this renderer has been disposed or not.

View File

@@ -1,16 +1,15 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using NFluidsynth;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Audio.Midi;
using Robust.Shared.Collections;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
@@ -19,7 +18,6 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Threading;
@@ -33,25 +31,19 @@ internal sealed partial class MidiManager : IMidiManager
{
public const string SoundfontEnvironmentVariable = "ROBUST_SOUNDFONT_OVERRIDE";
private int _minRendererParallel;
private float _occlusionUpdateDelay;
private float _positionUpdateDelay;
[ViewVariables] private TimeSpan _nextOcclusionUpdate = TimeSpan.Zero;
[ViewVariables] private TimeSpan _nextPositionUpdate = TimeSpan.Zero;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IResourceCacheInternal _resourceManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
[Dependency] private readonly IAudioInternal _audio = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly ILogManager _logger = default!;
[Dependency] private readonly IParallelManager _parallel = default!;
[Dependency] private readonly IRuntimeLog _runtime = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private AudioSystem _audioSys = default!;
private SharedPhysicsSystem _broadPhaseSystem = default!;
private SharedTransformSystem _xformSystem = default!;
public IReadOnlyList<IMidiRenderer> Renderers
{
@@ -78,24 +70,32 @@ internal sealed partial class MidiManager : IMidiManager
[ViewVariables] private readonly List<IMidiRenderer> _renderers = new();
// To avoid lock contention until some kind of MIDI refactor.
private TimeSpan _nextUpdate;
private TimeSpan _updateFrequency = TimeSpan.FromSeconds(0.25f);
private SemaphoreSlim _updateSemaphore = new(1);
private bool _alive = true;
[ViewVariables] private Settings? _settings;
private Thread? _midiThread;
private ISawmill _midiSawmill = default!;
private float _volume = 0f;
private float _gain = 0f;
private bool _volumeDirty = true;
// Not reliable until Fluidsynth is initialized!
[ViewVariables(VVAccess.ReadWrite)]
public float Volume
public float Gain
{
get => _volume;
get => _gain;
set
{
if (MathHelper.CloseToPercent(_volume, value))
var clamped = Math.Clamp(value, 0f, 1f);
if (MathHelper.CloseToPercent(_gain, clamped))
return;
_cfgMan.SetCVar(CVars.MidiVolume, value);
_cfgMan.SetCVar(CVars.MidiVolume, clamped);
_volumeDirty = true;
}
}
@@ -114,7 +114,7 @@ internal sealed partial class MidiManager : IMidiManager
"/usr/share/sounds/sf2/TimGM6mb.sf2",
};
private const string WindowsSoundfont = @"C:\WINDOWS\system32\drivers\gm.dls";
private static readonly string WindowsSoundfont = $@"{Environment.GetEnvironmentVariable("SystemRoot")}\system32\drivers\gm.dls";
private const string OsxSoundfont =
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
@@ -132,10 +132,9 @@ internal sealed partial class MidiManager : IMidiManager
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
private ISawmill _fluidsynthSawmill = default!;
private float _maxCastLength;
[ViewVariables(VVAccess.ReadWrite)]
public int OcclusionCollisionMask { get; set; }
private MidiUpdateJob _updateJob;
public MidiManager()
{
@@ -148,19 +147,10 @@ internal sealed partial class MidiManager : IMidiManager
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
{
_volume = value;
_gain = value;
_volumeDirty = true;
}, true);
_cfgMan.OnValueChanged(CVars.MidiMinRendererParallel,
value => _minRendererParallel = value, true);
_cfgMan.OnValueChanged(CVars.MidiOcclusionUpdateDelay,
value => _occlusionUpdateDelay = value, true);
_cfgMan.OnValueChanged(CVars.MidiPositionUpdateDelay,
value => _positionUpdateDelay = value, true);
_midiSawmill = _logger.GetSawmill("midi");
#if DEBUG
_midiSawmill.Level = LogLevel.Debug;
@@ -202,7 +192,12 @@ internal sealed partial class MidiManager : IMidiManager
_settings["synth.midi-bank-select"].StringValue = "gm";
//_settings["synth.verbose"].IntValue = 1; // Useful for debugging.
_parallel.AddAndInvokeParallelCountChanged(UpdateParallelCount);
var midiParallel = _cfgMan.GetCVar(CVars.MidiParallelism);
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int)(Math.Log2(midiParallel) * 2048), 1, 65535);
_settings["synth.cpu-cores"].IntValue = Math.Clamp(midiParallel, 1, 256);
_midiSawmill.Debug($"Synth Cores: {_settings["synth.cpu-cores"].IntValue}");
_midiSawmill.Debug($"Synth Polyphony: {_settings["synth.polyphony"].IntValue}");
}
catch (Exception e)
{
@@ -211,32 +206,27 @@ internal sealed partial class MidiManager : IMidiManager
return;
}
_midiThread = new Thread(ThreadUpdate);
_midiThread = new Thread(ThreadUpdate)
{
Name = "RobustToolbox MIDI Thread"
};
_midiThread.Start();
_updateJob = new MidiUpdateJob()
{
Manager = this,
Renderers = _renderers,
};
_audioSys = _entityManager.EntitySysManager.GetEntitySystem<AudioSystem>();
_broadPhaseSystem = _entityManager.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>();
_cfgMan.OnValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
_xformSystem = _entityManager.System<SharedTransformSystem>();
_entityManager.GetEntityQuery<PhysicsComponent>();
_entityManager.GetEntityQuery<TransformComponent>();
FluidsynthInitialized = true;
}
private void UpdateParallelCount()
{
if (_settings == null)
return;
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int)(Math.Log2(_parallel.ParallelProcessCount) * 2048), 1, 65535);
_settings["synth.cpu-cores"].IntValue = Math.Clamp(_parallel.ParallelProcessCount, 1, 256);
_midiSawmill.Debug($"Synth Cores: {_settings["synth.cpu-cores"].IntValue}");
_midiSawmill.Debug($"Synth Polyphony: {_settings["synth.polyphony"].IntValue}");
}
private void OnRaycastLengthChanged(float value)
{
_maxCastLength = value;
}
private void LoggerDelegate(NFluidsynth.Logger.LogLevel level, string message, IntPtr data)
{
var rLevel = level switch
@@ -273,7 +263,7 @@ internal sealed partial class MidiManager : IMidiManager
{
soundfontLoader.SetCallbacks(_soundfontLoaderCallbacks);
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _clydeAudio, _taskManager, _midiSawmill);
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _audio, _taskManager, _midiSawmill);
_midiSawmill.Debug($"Loading fallback soundfont {FallbackSoundfont}");
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
@@ -351,7 +341,7 @@ internal sealed partial class MidiManager : IMidiManager
renderer.LoadSoundfont(file.ToString());
}
renderer.Source.SetVolume(Volume);
renderer.Source.Gain = _gain;
lock (_renderers)
{
@@ -372,112 +362,125 @@ internal sealed partial class MidiManager : IMidiManager
return;
}
// Update positions of streams every frame.
if (_nextUpdate > _timing.RealTime)
return;
_nextUpdate = _timing.RealTime + _updateFrequency;
// Update positions of streams occasionally.
// This has a lot of code duplication with AudioSystem.FrameUpdate(), and they should probably be combined somehow.
// so TRUE
lock (_renderers)
{
if (_renderers.Count == 0)
return;
_updateJob.OurPosition = _audioSys.GetListenerCoordinates();
var transQuery = _entityManager.GetEntityQuery<TransformComponent>();
var physicsQuery = _entityManager.GetEntityQuery<PhysicsComponent>();
var opts = new ParallelOptions { MaxDegreeOfParallelism = _parallel.ParallelProcessCount };
// This semaphore is here to avoid lock contention as much as possible.
_updateSemaphore.Wait();
if (_renderers.Count > _minRendererParallel)
{
Parallel.ForEach(_renderers, opts, renderer => UpdateRenderer(renderer, transQuery, physicsQuery));
}
else
{
foreach (var renderer in _renderers)
{
UpdateRenderer(renderer, transQuery, physicsQuery);
}
}
// The ONLY time this should be contested is with ThreadUpdate.
// If that becomes NOT the case then just lock this, remove the semaphore, and drop the update frequency even harder.
// ReSharper disable once InconsistentlySynchronizedField
_parallel.ProcessNow(_updateJob, _renderers.Count);
}
if (_nextOcclusionUpdate < _timing.RealTime)
_nextOcclusionUpdate = _timing.RealTime.Add(TimeSpan.FromSeconds(_occlusionUpdateDelay));
if (_nextPositionUpdate < _timing.RealTime)
_nextPositionUpdate = _timing.RealTime.Add(TimeSpan.FromSeconds(_positionUpdateDelay));
_updateSemaphore.Release();
_volumeDirty = false;
}
private void UpdateRenderer(IMidiRenderer renderer, EntityQuery<TransformComponent> transQuery,
EntityQuery<PhysicsComponent> physicsQuery)
private void UpdateRenderer(IMidiRenderer renderer, MapCoordinates listener)
{
// TODO: This should be sharing more code with AudioSystem.
try
{
if (renderer.Disposed)
return;
if (_volumeDirty)
renderer.Source.SetVolume(Volume);
{
renderer.Source.Gain = Gain;
}
if (!renderer.Mono)
{
renderer.Source.SetGlobal();
renderer.Source.Global = true;
return;
}
if (_nextPositionUpdate < _timing.RealTime)
MapCoordinates mapPos;
if (renderer.TrackingEntity is {} trackedEntity && !_entityManager.Deleted(trackedEntity))
{
if (renderer.TrackingEntity is {} trackedEntity && !_entityManager.Deleted(trackedEntity))
{
renderer.TrackingCoordinates = transQuery.GetComponent(renderer.TrackingEntity!.Value).MapPosition;
}
else if (renderer.TrackingCoordinates == null)
renderer.TrackingCoordinates = _xformSystem.GetMapCoordinates(renderer.TrackingEntity.Value);
// Pause it if the attached entity is paused.
if (_entityManager.IsPaused(renderer.TrackingEntity))
{
renderer.Source.Pause();
return;
}
if (!renderer.Source.SetPosition(renderer.TrackingCoordinates.Value.Position))
{
return;
}
var vel = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity!.Value,
xformQuery: transQuery, physicsQuery: physicsQuery);
renderer.Source.SetVelocity(vel);
}
else if (renderer.TrackingCoordinates == null)
{
renderer.Source.Pause();
return;
}
if (renderer.TrackingCoordinates != null && renderer.TrackingCoordinates.Value.MapId == _eyeManager.CurrentMap)
mapPos = renderer.TrackingCoordinates.Value;
// If it's on a different map then just mute it, not pause.
if (mapPos.MapId == MapId.Nullspace || mapPos.MapId != listener.MapId)
{
if (_nextOcclusionUpdate >= _timing.RealTime)
return;
renderer.Source.Gain = 0f;
return;
}
var pos = renderer.TrackingCoordinates.Value;
// Was previously muted maybe so try unmuting it?
if (renderer.Source.Gain == 0f)
{
renderer.Source.Gain = Gain;
}
var sourceRelative = pos.Position - _eyeManager.CurrentEye.Position.Position;
var occlusion = 0f;
if (sourceRelative.Length() > 0)
{
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
_eyeManager.CurrentEye.Position.Position,
sourceRelative.Normalized(),
OcclusionCollisionMask),
MathF.Min(sourceRelative.Length(), _maxCastLength),
renderer.TrackingEntity);
}
var worldPos = mapPos.Position;
var delta = worldPos - listener.Position;
var distance = delta.Length();
renderer.Source.SetOcclusion(occlusion);
// Update position
// Out of range so just clip it for us.
if (distance > renderer.Source.MaxDistance)
{
// Still keeps the source playing, just with no volume.
renderer.Source.Gain = 0f;
return;
}
// Same imprecision suppression as audiosystem.
if (distance > 0f && distance < 0.01f)
{
worldPos = listener.Position;
delta = Vector2.Zero;
distance = 0f;
}
renderer.Source.Position = worldPos;
// Update velocity (doppler).
if (!_entityManager.Deleted(renderer.TrackingEntity))
{
var velocity = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity.Value);
renderer.Source.Velocity = velocity;
}
else
{
renderer.Source.SetOcclusion(float.MaxValue);
renderer.Source.Velocity = Vector2.Zero;
}
// Update occlusion
var occlusion = _audioSys.GetOcclusion(listener, delta, distance, renderer.TrackingEntity);
renderer.Source.Occlusion = occlusion;
}
catch (Exception ex)
{
_runtime.LogException(ex, _midiSawmill.Name);
}
}
/// <summary>
@@ -489,21 +492,39 @@ internal sealed partial class MidiManager : IMidiManager
{
lock (_renderers)
{
var toRemove = new ValueList<IMidiRenderer>();
for (var i = 0; i < _renderers.Count; i++)
{
var renderer = _renderers[i];
if (!renderer.Disposed)
{
if (renderer.Master is { Disposed: true })
renderer.Master = null;
renderer.Render();
lock (renderer)
{
if (!renderer.Disposed)
{
if (renderer.Master is { Disposed: true })
renderer.Master = null;
renderer.Render();
}
else
{
toRemove.Add(renderer);
}
}
else
}
if (toRemove.Count > 0)
{
_updateSemaphore.Wait();
foreach (var renderer in toRemove)
{
renderer.InternalDispose();
_renderers.Remove(renderer);
}
_updateSemaphore.Release();
}
}
@@ -674,4 +695,31 @@ internal sealed partial class MidiManager : IMidiManager
}
}
#region Jobs
private record struct MidiUpdateJob : IParallelRobustJob
{
public int MinimumBatchParallel => 2;
public int BatchSize => 1;
public MidiManager Manager;
public MapCoordinates OurPosition;
public List<IMidiRenderer> Renderers;
public void Execute(int index)
{
// The indices shouldn't be able to be touched while this job is running, just the renderer itself getting locked.
var renderer = Renderers[index];
lock (renderer)
{
Manager.UpdateRenderer(renderer, OurPosition);
}
}
}
#endregion
}

View File

@@ -4,7 +4,9 @@ using JetBrains.Annotations;
using NFluidsynth;
using Robust.Client.Graphics;
using Robust.Shared.Asynchronous;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Midi;
using Robust.Shared.Audio.Sources;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Map;
@@ -36,6 +38,7 @@ internal sealed class MidiRenderer : IMidiRenderer
private readonly Synth _synth;
private readonly Sequencer _sequencer;
private NFluidsynth.Player? _player;
private int _playerTotalTicks;
private MidiDriver? _driver;
private byte _midiProgram = 1;
private byte _midiBank = 1;
@@ -52,8 +55,8 @@ internal sealed class MidiRenderer : IMidiRenderer
private IMidiRenderer? _master;
public MidiRendererState RendererState => _rendererState;
public IClydeBufferedAudioSource Source { get; set; }
IClydeBufferedAudioSource IMidiRenderer.Source => Source;
public IBufferedAudioSource Source { get; set; }
IBufferedAudioSource IMidiRenderer.Source => Source;
[ViewVariables]
public bool Disposed { get; private set; } = false;
@@ -142,7 +145,21 @@ internal sealed class MidiRenderer : IMidiRenderer
public bool DisableProgramChangeEvent { get; set; } = true;
[ViewVariables(VVAccess.ReadWrite)]
public int PlayerTotalTick => _player?.GetTotalTicks ?? 0;
public int PlayerTotalTick
{
get
{
// GetTotalTicks is really expensive (has to iterate the entire file, not cached).
// Slight problem with caching it ourselves: the value only becomes available when the player loads the MIDI file.
// And that only happens after playback really starts, with the timer and synth and all that stuff.
// So we cache it "as soon as it's available", i.e. not 0.
// We don't care about playlists and such, so it shouldn't change anymore after.
if (_playerTotalTicks != 0)
return _playerTotalTicks;
return _playerTotalTicks = _player?.GetTotalTicks ?? 0;
}
}
[ViewVariables(VVAccess.ReadWrite)]
public int PlayerTick
@@ -247,13 +264,13 @@ internal sealed class MidiRenderer : IMidiRenderer
public event Action? OnMidiPlayerFinished;
internal MidiRenderer(Settings settings, SoundFontLoader soundFontLoader, bool mono,
IMidiManager midiManager, IClydeAudio clydeAudio, ITaskManager taskManager, ISawmill midiSawmill)
IMidiManager midiManager, IAudioInternal clydeAudio, ITaskManager taskManager, ISawmill midiSawmill)
{
_midiManager = midiManager;
_taskManager = taskManager;
_midiSawmill = midiSawmill;
Source = clydeAudio.CreateBufferedAudioSource(Buffers, true);
Source = clydeAudio.CreateBufferedAudioSource(Buffers, true) ?? DummyBufferedAudioSource.Instance;
Source.SampleRate = SampleRate;
_settings = settings;
_soundFontLoader = soundFontLoader;
@@ -337,6 +354,7 @@ internal sealed class MidiRenderer : IMidiRenderer
return false;
}
_playerTotalTicks = 0;
_player?.Dispose();
_player = new NFluidsynth.Player(_synth);
_player.SetPlaybackCallback(MidiPlayerEventHandler);
@@ -375,6 +393,7 @@ internal sealed class MidiRenderer : IMidiRenderer
_player?.Join();
_player?.Dispose();
_player = null;
_playerTotalTicks = 0;
}
StopAllNotes();
@@ -488,7 +507,7 @@ internal sealed class MidiRenderer : IMidiRenderer
}
}
if (!Source.IsPlaying) Source.StartPlaying();
Source.StartPlaying();
}
public void ApplyState(MidiRendererState state, bool filterChannels = false)

View File

@@ -0,0 +1,34 @@
using Robust.Client.Audio;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Client.Commands;
/// <summary>
/// Shows a debug overlay for audio sources.
/// </summary>
public sealed class ShowAudioCommand : LocalizedCommands
{
[Dependency] private readonly IResourceCache _client = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _playerMgr = default!;
public override string Command => "showaudio";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (_overlayManager.HasOverlay<AudioOverlay>())
_overlayManager.RemoveOverlay<AudioOverlay>();
else
_overlayManager.AddOverlay(new AudioOverlay(
_entManager,
_playerMgr,
_client,
_entManager.System<AudioSystem>(),
_entManager.System<SharedTransformSystem>()));
}
}

View File

@@ -0,0 +1,90 @@
using System;
using System.Numerics;
using OpenTK.Audio.OpenAL;
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using Robust.Shared.Audio;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Client.Audio.Sources;
internal sealed class AudioSource : BaseAudioSource
{
/// <summary>
/// Underlying stream to the audio.
/// </summary>
private readonly AudioStream _sourceStream;
#if DEBUG
private bool _didPositionWarning;
#endif
public AudioSource(AudioManager master, int sourceHandle, AudioStream sourceStream) : base(master, sourceHandle)
{
_sourceStream = sourceStream;
}
/// <inheritdoc />
public override Vector2 Position
{
get
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSource3f.Position, out var x, out var y, out _);
Master._checkAlError();
return new Vector2(x, y);
}
set
{
_checkDisposed();
var (x, y) = value;
if (!AreFinite(x, y))
{
return;
}
#if DEBUG
// OpenAL doesn't seem to want to play stereo positionally.
// Log a warning if people try to.
if (_sourceStream.ChannelCount > 1 && !_didPositionWarning)
{
_didPositionWarning = true;
Master.OpenALSawmill.Warning("Attempting to set position on audio source with multiple audio channels! Stream: '{0}'. Make sure the audio is MONO, not stereo.",
_sourceStream.Name);
// warning isn't enough, people just ignore it :(
DebugTools.Assert(false, $"Attempting to set position on audio source with multiple audio channels! Stream: '{_sourceStream.Name}'. Make sure the audio is MONO, not stereo.");
}
#endif
AL.Source(SourceHandle, ALSource3f.Position, x, y, 0);
Master._checkAlError();
}
}
~AudioSource()
{
Dispose(false);
}
protected override void Dispose(bool disposing)
{
if (!disposing)
{
// We can't run this code inside the finalizer thread so tell Clyde to clear it up later.
Master.DeleteSourceOnMainThread(SourceHandle, FilterHandle);
}
else
{
if (FilterHandle != 0)
EFX.DeleteFilter(FilterHandle);
AL.DeleteSource(SourceHandle);
Master.RemoveAudioSource(SourceHandle);
Master._checkAlError();
}
FilterHandle = 0;
SourceHandle = -1;
}
}

View File

@@ -0,0 +1,417 @@
using System;
using System.Numerics;
using OpenTK.Audio.OpenAL;
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using Robust.Client.Audio.Effects;
using Robust.Shared.Audio.Effects;
using Robust.Shared.Audio.Sources;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Maths;
namespace Robust.Client.Audio.Sources;
public abstract class BaseAudioSource : IAudioSource
{
/*
* This may look weird having all these methods here however
* we need to handle disposing plus checking for errors hence we get this.
*/
/// <summary>
/// Handle to the AL source.
/// </summary>
protected int SourceHandle;
/// <summary>
/// Source to the EFX filter if applicable.
/// </summary>
protected int FilterHandle;
internal readonly AudioManager Master;
/// <summary>
/// Prior gain that was set.
/// </summary>
private float _gain;
private float _occlusion;
private bool IsEfxSupported => Master.IsEfxSupported;
internal BaseAudioSource(AudioManager master, int sourceHandle)
{
Master = master;
SourceHandle = sourceHandle;
AL.GetSource(SourceHandle, ALSourcef.Gain, out _gain);
}
/// <inheritdoc />
public void Pause()
{
AL.SourcePause(SourceHandle);
}
/// <inheritdoc />
public void StartPlaying()
{
if (Playing)
return;
Playing = true;
}
/// <inheritdoc />
public void StopPlaying()
{
if (!Playing)
return;
Playing = false;
}
/// <inheritdoc />
public void Restart()
{
AL.SourceRewind(SourceHandle);
StartPlaying();
}
/// <inheritdoc />
public virtual bool Playing
{
get
{
_checkDisposed();
var state = AL.GetSourceState(SourceHandle);
Master._checkAlError();
return state == ALSourceState.Playing;
}
set
{
_checkDisposed();
if (value)
{
AL.SourcePlay(SourceHandle);
}
else
{
AL.SourceStop(SourceHandle);
}
Master._checkAlError();
}
}
/// <inheritdoc />
public bool Looping
{
get
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSourceb.Looping, out var ret);
Master._checkAlError();
return ret;
}
set
{
_checkDisposed();
AL.Source(SourceHandle, ALSourceb.Looping, value);
Master._checkAlError();
}
}
/// <inheritdoc />
public bool Global
{
get
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSourceb.SourceRelative, out var value);
Master._checkAlError();
return value;
}
set
{
_checkDisposed();
AL.Source(SourceHandle, ALSourceb.SourceRelative, value);
Master._checkAlError();
}
}
/// <inheritdoc />
public virtual Vector2 Position
{
get
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSource3f.Position, out var x, out var y, out _);
Master._checkAlError();
return new Vector2(x, y);
}
set
{
_checkDisposed();
var (x, y) = value;
if (!AreFinite(x, y))
{
return;
}
AL.Source(SourceHandle, ALSource3f.Position, x, y, 0);
Master._checkAlError();
}
}
/// <inheritdoc />
public float Pitch
{
get
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSourcef.Pitch, out var value);
Master._checkAlError();
return value;
}
set
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.Pitch, value);
Master._checkAlError();
}
}
/// <inheritdoc />
public float Volume
{
get
{
var gain = Gain;
var volume = SharedAudioSystem.GainToVolume(gain);
return volume;
}
set => Gain = SharedAudioSystem.VolumeToGain(value);
}
/// <inheritdoc />
public float Gain
{
get
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSourcef.Gain, out var gain);
Master._checkAlError();
return gain;
}
set
{
_checkDisposed();
var priorOcclusion = 1f;
if (!IsEfxSupported)
{
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
// Default to 0 to avoid spiking audio, just means it will be muted for a frame in this case.
priorOcclusion = _gain == 0 ? 1f : priorGain / _gain;
}
_gain = value;
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
Master.LogALError($"Gain is {_gain:0.00} and priorOcclusion is {priorOcclusion:0.00}. EFX supported: {IsEfxSupported}");
}
}
/// <inheritdoc />
public float MaxDistance
{
get
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSourcef.MaxDistance, out var value);
Master._checkAlError();
return value;
}
set
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.MaxDistance, value);
Master.LogALError($"MaxDistance is {value:0.00}");
}
}
/// <inheritdoc />
public float RolloffFactor
{
get
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSourcef.RolloffFactor, out var value);
Master._checkAlError();
return value;
}
set
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.RolloffFactor, value);
Master.LogALError($"RolloffFactor is {value:0.00}");
}
}
/// <inheritdoc />
public float ReferenceDistance
{
get
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSourcef.ReferenceDistance, out var value);
Master._checkAlError();
return value;
}
set
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.ReferenceDistance, value);
Master.LogALError($"ReferenceDistance is {value:0.00}");
}
}
/// <inheritdoc />
public float Occlusion
{
get => _occlusion;
set
{
_checkDisposed();
var cutoff = MathF.Exp(-value * 1);
var gain = MathF.Pow(cutoff, 0.1f);
if (IsEfxSupported)
{
SetOcclusionEfx(gain, cutoff);
}
else
{
gain *= gain * gain;
AL.Source(SourceHandle, ALSourcef.Gain, _gain * gain);
}
_occlusion = value;
Master._checkAlError();
}
}
/// <inheritdoc />
public float PlaybackPosition
{
get
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSourcef.SecOffset, out var value);
Master._checkAlError();
return value;
}
set
{
_checkDisposed();
value = MathF.Max(value, 0f);
AL.Source(SourceHandle, ALSourcef.SecOffset, value);
Master._checkAlError($"Tried to set invalid playback position of {value:0.00}");
}
}
/// <inheritdoc />
public Vector2 Velocity
{
get
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSource3f.Velocity, out var x, out var y, out _);
Master._checkAlError();
return new Vector2(x, y);
}
set
{
_checkDisposed();
var (x, y) = value;
if (!AreFinite(x, y))
{
return;
}
AL.Source(SourceHandle, ALSource3f.Velocity, x, y, 0);
Master._checkAlError();
}
}
void IAudioSource.SetAuxiliary(IAuxiliaryAudio? audio)
{
_checkDisposed();
if (!IsEfxSupported)
return;
if (audio is AuxiliaryAudio impAudio)
{
EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, impAudio.Handle, 0, 0);
}
else
{
EFX.Source(SourceHandle, EFXSourceInteger3.AuxiliarySendFilter, 0, 0, 0);
}
Master._checkAlError();
}
private void SetOcclusionEfx(float gain, float cutoff)
{
if (FilterHandle == 0)
{
FilterHandle = EFX.GenFilter();
EFX.Filter(FilterHandle, FilterInteger.FilterType, (int) FilterType.Lowpass);
}
EFX.Filter(FilterHandle, FilterFloat.LowpassGain, gain);
EFX.Filter(FilterHandle, FilterFloat.LowpassGainHF, cutoff);
AL.Source(SourceHandle, ALSourcei.EfxDirectFilter, FilterHandle);
}
protected static bool AreFinite(float x, float y)
{
if (float.IsFinite(x) && float.IsFinite(y))
{
return true;
}
return false;
}
~BaseAudioSource()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected abstract void Dispose(bool disposing);
protected bool _isDisposed()
{
return SourceHandle == -1;
}
protected void _checkDisposed()
{
if (SourceHandle == -1)
{
throw new ObjectDisposedException(nameof(BaseAudioSource));
}
}
}

View File

@@ -0,0 +1,222 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using OpenTK.Audio.OpenAL;
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using Robust.Client.Graphics;
using Robust.Shared.Audio.Sources;
using Robust.Shared.Maths;
namespace Robust.Client.Audio.Sources;
internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSource
{
private int? SourceHandle = null;
private int[] BufferHandles;
private Dictionary<int, int> BufferMap = new();
private readonly AudioManager _master;
private bool _mono = true;
private bool _float = false;
private int FilterHandle;
public int SampleRate { get; set; } = 44100;
private bool IsEfxSupported => _master.IsEfxSupported;
public BufferedAudioSource(AudioManager master, int sourceHandle, int[] bufferHandles, bool floatAudio = false) : base(master, sourceHandle)
{
_master = master;
SourceHandle = sourceHandle;
BufferHandles = bufferHandles;
for (int i = 0; i < BufferHandles.Length; i++)
{
var bufferHandle = BufferHandles[i];
BufferMap[bufferHandle] = i;
}
_float = floatAudio;
}
/// <inheritdoc />
public override bool Playing
{
get
{
_checkDisposed();
var state = AL.GetSourceState(SourceHandle!.Value);
_master._checkAlError();
return state == ALSourceState.Playing;
}
set
{
if (value)
{
_checkDisposed();
// IDK why this stackallocs but gonna leave it for now.
AL.SourcePlay(stackalloc int[] {SourceHandle!.Value});
_master._checkAlError();
}
else
{
if (_isDisposed())
return;
AL.SourceStop(SourceHandle!.Value);
_master._checkAlError();
}
}
}
~BufferedAudioSource()
{
Dispose(false);
}
protected override void Dispose(bool disposing)
{
if (SourceHandle == null)
return;
if (!_master.IsMainThread())
{
// We can't run this code inside another thread so tell Clyde to clear it up later.
_master.DeleteBufferedSourceOnMainThread(SourceHandle.Value, FilterHandle);
foreach (var handle in BufferHandles)
{
_master.DeleteAudioBufferOnMainThread(handle);
}
}
else
{
if (FilterHandle != 0)
EFX.DeleteFilter(FilterHandle);
AL.DeleteSource(SourceHandle.Value);
AL.DeleteBuffers(BufferHandles);
_master.RemoveBufferedAudioSource(SourceHandle.Value);
_master._checkAlError();
}
FilterHandle = 0;
SourceHandle = null;
}
public int GetNumberOfBuffersProcessed()
{
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.GetSource(SourceHandle!.Value, ALGetSourcei.BuffersProcessed, out var buffersProcessed);
return buffersProcessed;
}
public unsafe void GetBuffersProcessed(Span<int> handles)
{
_checkDisposed();
var entries = Math.Min(Math.Min(handles.Length, BufferHandles.Length), GetNumberOfBuffersProcessed());
fixed (int* ptr = handles)
{
AL.SourceUnqueueBuffers(SourceHandle!.Value, entries, ptr);
}
for (var i = 0; i < entries; i++)
{
handles[i] = BufferMap[handles[i]];
}
}
public unsafe void WriteBuffer(int handle, ReadOnlySpan<ushort> data)
{
_checkDisposed();
if(_float)
throw new InvalidOperationException("Can't write ushort numbers to buffers when buffer type is float!");
if (handle >= BufferHandles.Length)
{
throw new ArgumentOutOfRangeException(nameof(handle),
$"Got {handle}. Expected less than {BufferHandles.Length}");
}
fixed (ushort* ptr = data)
{
AL.BufferData(BufferHandles[handle], _mono ? ALFormat.Mono16 : ALFormat.Stereo16, (IntPtr) ptr,
_mono ? data.Length / 2 * sizeof(ushort) : data.Length * sizeof(ushort), SampleRate);
}
}
public unsafe void WriteBuffer(int handle, ReadOnlySpan<float> data)
{
_checkDisposed();
if(!_float)
throw new InvalidOperationException("Can't write float numbers to buffers when buffer type is ushort!");
if (handle >= BufferHandles.Length)
{
throw new ArgumentOutOfRangeException(nameof(handle),
$"Got {handle}. Expected less than {BufferHandles.Length}");
}
fixed (float* ptr = data)
{
AL.BufferData(BufferHandles[handle], _mono ? ALFormat.MonoFloat32Ext : ALFormat.StereoFloat32Ext, (IntPtr) ptr,
_mono ? data.Length / 2 * sizeof(float) : data.Length * sizeof(float), SampleRate);
}
}
public unsafe void QueueBuffers(ReadOnlySpan<int> handles)
{
_checkDisposed();
Span<int> realHandles = stackalloc int[handles.Length];
handles.CopyTo(realHandles);
for (var i = 0; i < realHandles.Length; i++)
{
var handle = realHandles[i];
if (handle >= BufferHandles.Length)
throw new ArgumentOutOfRangeException(nameof(handles), $"Invalid handle with index {i}!");
realHandles[i] = BufferHandles[handle];
}
fixed (int* ptr = realHandles)
// ReSharper disable once PossibleInvalidOperationException
{
AL.SourceQueueBuffers(SourceHandle!.Value, handles.Length, ptr);
}
}
public unsafe void EmptyBuffers()
{
_checkDisposed();
var length = SampleRate / BufferHandles.Length * (_mono ? 1 : 2);
Span<int> handles = stackalloc int[BufferHandles.Length];
if (_float)
{
var empty = new float[length];
var span = (Span<float>) empty;
for (var i = 0; i < BufferHandles.Length; i++)
{
WriteBuffer(BufferMap[BufferHandles[i]], span);
handles[i] = BufferMap[BufferHandles[i]];
}
}
else
{
var empty = new ushort[length];
var span = (Span<ushort>) empty;
for (var i = 0; i < BufferHandles.Length; i++)
{
WriteBuffer(BufferMap[BufferHandles[i]], span);
handles[i] = BufferMap[BufferHandles[i]];
}
}
QueueBuffers(handles);
}
}

View File

@@ -1,8 +1,6 @@
using System;
using System.Linq;
using System.Net;
using Robust.Client.Configuration;
using Robust.Client.Debugging;
using Robust.Client.GameObjects;
using Robust.Client.GameStates;
using Robust.Client.Player;
@@ -10,13 +8,12 @@ using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -65,12 +62,12 @@ namespace Robust.Client
_configManager.OnValueChanged(CVars.NetTickrate, TickRateChanged, invokeImmediately: true);
_playMan.Initialize();
_playMan.Initialize(0);
_playMan.PlayerListUpdated += OnPlayerListUpdated;
Reset();
}
private void OnPlayerListUpdated(object? sender, EventArgs e)
private void OnPlayerListUpdated()
{
var serverPlayers = _playMan.PlayerCount;
if (_net.ServerChannel != null && GameInfo != null && _net.IsConnected)
@@ -130,9 +127,10 @@ namespace Robust.Client
{
DebugTools.Assert(RunLevel < ClientRunLevel.Connecting);
DebugTools.Assert(!_net.IsConnected);
_playMan.Startup();
_playMan.LocalPlayer!.Name = PlayerNameOverride ?? _configManager.GetCVar(CVars.PlayerName);
var name = PlayerNameOverride ?? _configManager.GetCVar(CVars.PlayerName);
_playMan.SetupSinglePlayer(name);
OnRunLevelChanged(ClientRunLevel.SinglePlayerGame);
_playMan.JoinGame(_playMan.LocalSession!);
GameStartedSetup();
}
@@ -173,22 +171,14 @@ namespace Robust.Client
info.ServerName = serverName;
}
var maxPlayers = _configManager.GetCVar<int>("game.maxplayers");
info.ServerMaxPlayers = maxPlayers;
var userName = _net.ServerChannel!.UserName;
var userId = _net.ServerChannel.UserId;
var channel = _net.ServerChannel!;
// start up player management
_playMan.Startup();
_playMan.LocalPlayer!.UserId = userId;
_playMan.LocalPlayer.Name = userName;
_playMan.LocalPlayer.StatusChanged += OnLocalStatusChanged;
_playMan.SetupMultiplayer(channel);
_playMan.PlayerStatusChanged += OnStatusChanged;
var serverPlayers = _playMan.PlayerCount;
_discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString(), serverPlayers.ToString());
_discord.Update(info.ServerName, channel.UserName, info.ServerMaxPlayers.ToString(), serverPlayers.ToString());
}
@@ -221,6 +211,8 @@ namespace Robust.Client
private void Reset()
{
_configManager.ReceivedInitialNwVars -= OnReceivedClientData;
_playMan.PlayerStatusChanged -= OnStatusChanged;
_configManager.ClearReceivedInitialNwVars();
OnRunLevelChanged(ClientRunLevel.Initialize);
}
@@ -237,7 +229,7 @@ namespace Robust.Client
// Don't invoke PlayerLeaveServer if PlayerJoinedServer & GameStartedSetup hasn't been called yet.
if (RunLevel > ClientRunLevel.Connecting)
PlayerLeaveServer?.Invoke(this, new PlayerEventArgs(_playMan.LocalPlayer?.Session));
PlayerLeaveServer?.Invoke(this, new PlayerEventArgs(_playMan.LocalSession));
LastDisconnectReason = args.Reason;
GameStoppedReset();
@@ -263,19 +255,17 @@ namespace Robust.Client
Reset();
}
private void OnLocalStatusChanged(object? obj, StatusEventArgs eventArgs)
private void OnStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.Session != _playMan.LocalSession)
return;
// player finished fully connecting to the server.
// OldStatus is used here because it can go from connecting-> connected or connecting-> ingame
if (eventArgs.OldStatus == SessionStatus.Connecting)
{
OnPlayerJoinedServer(_playMan.LocalPlayer!.Session);
}
if (eventArgs.NewStatus == SessionStatus.InGame)
{
OnPlayerJoinedGame(_playMan.LocalPlayer!.Session);
}
if (e.OldStatus == SessionStatus.Connecting)
OnPlayerJoinedServer(e.Session);
else if (e.NewStatus == SessionStatus.InGame)
OnPlayerJoinedGame(e.Session);
}
private void OnRunLevelChanged(ClientRunLevel newRunLevel)
@@ -295,6 +285,7 @@ namespace Robust.Client
/// <summary>
/// Enumeration of the run levels of the BaseClient.
/// </summary>
/// <seealso cref="ClientRunLevelExt"/>
public enum ClientRunLevel : byte
{
Error = 0,
@@ -325,6 +316,21 @@ namespace Robust.Client
SinglePlayerGame,
}
/// <summary>
/// Helper functions for working with <see cref="ClientRunLevel"/>.
/// </summary>
public static class ClientRunLevelExt
{
/// <summary>
/// Check if a <see cref="ClientRunLevel"/> is <see cref="ClientRunLevel.InGame"/>
/// or <see cref="ClientRunLevel.SinglePlayerGame"/>.
/// </summary>
public static bool IsInGameLike(this ClientRunLevel runLevel)
{
return runLevel is ClientRunLevel.InGame or ClientRunLevel.SinglePlayerGame;
}
}
/// <summary>
/// Event arguments for when something changed with the player.
/// </summary>

View File

@@ -1,4 +1,5 @@
using System;
using Robust.Client.Audio;
using Robust.Client.Audio.Midi;
using Robust.Client.Configuration;
using Robust.Client.Console;
@@ -6,7 +7,6 @@ using Robust.Client.Debugging;
using Robust.Client.GameObjects;
using Robust.Client.GameStates;
using Robust.Client.Graphics;
using Robust.Client.Graphics.Audio;
using Robust.Client.Graphics.Clyde;
using Robust.Client.Input;
using Robust.Client.Map;
@@ -37,7 +37,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Replays;
@@ -107,8 +107,8 @@ namespace Robust.Client
deps.Register<IClyde, ClydeHeadless>();
deps.Register<IClipboardManager, ClydeHeadless>();
deps.Register<IClydeInternal, ClydeHeadless>();
deps.Register<IClydeAudio, ClydeAudioHeadless>();
deps.Register<IClydeAudioInternal, ClydeAudioHeadless>();
deps.Register<IAudioManager, HeadlessAudioManager>();
deps.Register<IAudioInternal, HeadlessAudioManager>();
deps.Register<IInputManager, InputManager>();
deps.Register<IFileDialogManager, DummyFileDialogManager>();
deps.Register<IUriOpener, UriOpenerDummy>();
@@ -117,8 +117,8 @@ namespace Robust.Client
deps.Register<IClyde, Clyde>();
deps.Register<IClipboardManager, Clyde>();
deps.Register<IClydeInternal, Clyde>();
deps.Register<IClydeAudio, FallbackProxyClydeAudio>();
deps.Register<IClydeAudioInternal, FallbackProxyClydeAudio>();
deps.Register<IAudioManager, AudioManager>();
deps.Register<IAudioInternal, AudioManager>();
deps.Register<IInputManager, ClydeInputManager>();
deps.Register<IFileDialogManager, FileDialogManager>();
deps.Register<IUriOpener, UriOpener>();

View File

@@ -8,7 +8,7 @@ using Robust.Shared.Network.Messages;
namespace Robust.Client.Console;
internal sealed partial class ClientConsoleHost
internal partial class ClientConsoleHost
{
private readonly Dictionary<int, PendingCompletion> _completionsPending = new();
private int _completionSeq;

View File

@@ -13,7 +13,7 @@ using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
using Robust.Shared.Player;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -47,7 +47,8 @@ namespace Robust.Client.Console
}
/// <inheritdoc cref="IClientConsoleHost" />
internal sealed partial class ClientConsoleHost : ConsoleHost, IClientConsoleHost, IConsoleHostInternal, IPostInjectInit
[Virtual]
internal partial class ClientConsoleHost : ConsoleHost, IClientConsoleHost, IConsoleHostInternal, IPostInjectInit
{
[Dependency] private readonly IClientConGroupController _conGroup = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -187,7 +188,7 @@ namespace Robust.Client.Console
}
args.RemoveAt(0);
var shell = new ConsoleShell(this, session ?? _player.LocalPlayer?.Session, session == null);
var shell = new ConsoleShell(this, session ?? _player.LocalSession, session == null);
var cmdArgs = args.ToArray();
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
@@ -199,8 +200,7 @@ namespace Robust.Client.Console
// When not connected to a server, you can run all local commands.
// When connected to a server, you can only run commands according to the con group controller.
return _player.LocalPlayer == null
|| _player.LocalPlayer.Session.Status <= SessionStatus.Connecting
return _player.LocalSession is not { Status: > SessionStatus.Connecting }
|| _conGroup.CanCommand(cmdName);
}

View File

@@ -26,10 +26,7 @@ namespace Robust.Client.Console.Commands
var entity = _entityManager.GetEntity(netEntity);
var componentName = args[1];
var component = (Component) _componentFactory.GetComponent(componentName);
component.Owner = entity;
var component = _componentFactory.GetComponent(componentName);
_entityManager.AddComponent(entity, component);
}
}

View File

@@ -16,8 +16,7 @@ namespace Robust.Client.Console.Commands
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var controlled = _playerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
if (controlled == EntityUid.Invalid)
if (_playerManager.LocalEntity is not { } controlled)
{
shell.WriteLine("You don't have an attached entity.");
return;

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime;
using System.Text;
using System.Text.RegularExpressions;
@@ -15,6 +16,7 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Asynchronous;
using Robust.Shared.Audio;
using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
@@ -197,6 +199,7 @@ namespace Robust.Client.Console.Commands
}
}
#if DEBUG
internal sealed class ShowRayCommand : LocalizedCommands
{
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
@@ -223,6 +226,7 @@ namespace Robust.Client.Console.Commands
mgr.DebugRayLifetime = TimeSpan.FromSeconds(duration);
}
}
#endif
internal sealed class DisconnectCommand : LocalizedCommands
{
@@ -290,7 +294,6 @@ namespace Robust.Client.Console.Commands
internal sealed class SnapGridGetCell : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
public override string Command => "sggcell";
@@ -316,7 +319,7 @@ namespace Robust.Client.Console.Commands
return;
}
if (_map.TryGetGrid(_entManager.GetEntity(gridNet), out var grid))
if (_entManager.TryGetComponent<MapGridComponent>(_entManager.GetEntity(gridNet), out var grid))
{
foreach (var entity in grid.GetAnchoredEntities(new Vector2i(
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
@@ -425,7 +428,6 @@ namespace Robust.Client.Console.Commands
internal sealed class GridTileCount : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
public override string Command => "gridtc";
@@ -444,7 +446,7 @@ namespace Robust.Client.Console.Commands
return;
}
if (_map.TryGetGrid(gridUid, out var grid))
if (_entManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
{
shell.WriteLine(grid.GetAllTiles().Count().ToString());
}
@@ -458,13 +460,13 @@ namespace Robust.Client.Console.Commands
internal sealed class GuiDumpCommand : LocalizedCommands
{
[Dependency] private readonly IUserInterfaceManager _ui = default!;
[Dependency] private readonly IResourceCache _res = default!;
[Dependency] private readonly IResourceManager _resManager = default!;
public override string Command => "guidump";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
using var writer = _res.UserData.OpenWriteText(new ResPath("/guidump.txt"));
using var writer = _resManager.UserData.OpenWriteText(new ResPath("/guidump.txt"));
foreach (var root in _ui.AllRoots)
{
@@ -491,9 +493,9 @@ namespace Robust.Client.Console.Commands
}
}
internal static List<(string, string)> PropertyValuesFor(Control control)
internal static List<MemberInfo> GetAllMembers(Control control)
{
var members = new List<(string, string)>();
var members = new List<MemberInfo>();
var type = control.GetType();
foreach (var fieldInfo in type.GetAllFields())
@@ -503,7 +505,7 @@ namespace Robust.Client.Console.Commands
continue;
}
members.Add((fieldInfo.Name, fieldInfo.GetValue(control)?.ToString() ?? "null"));
members.Add(fieldInfo);
}
foreach (var propertyInfo in type.GetAllProperties())
@@ -513,7 +515,19 @@ namespace Robust.Client.Console.Commands
continue;
}
members.Add((propertyInfo.Name, propertyInfo.GetValue(control)?.ToString() ?? "null"));
members.Add(propertyInfo);
}
return members;
}
internal static List<(string, string)> PropertyValuesFor(Control control)
{
var members = new List<(string, string)>();
foreach (var fieldInfo in GetAllMembers(control))
{
members.Add((fieldInfo.Name, fieldInfo.GetValue(control)?.ToString() ?? "null"));
}
foreach (var (attachedProperty, value) in control.AllAttachedProperties)
@@ -525,6 +539,57 @@ namespace Robust.Client.Console.Commands
members.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.Ordinal));
return members;
}
internal static Dictionary<string, List<(string, string)>> PropertyValuesForInheritance(Control control)
{
var returnVal = new Dictionary<string, List<(string, string)>>();
var engine = typeof(Control).Assembly;
foreach (var member in GetAllMembers(control))
{
var type = member.DeclaringType!;
var cname = type.Assembly == engine ? type.Name : type.ToString();
if (type != typeof(Control))
cname = $"Control > {cname}";
returnVal.GetOrNew(cname).Add((member.Name, GetMemberValue(member, control, ", ")));
}
foreach (var (attachedProperty, value) in control.AllAttachedProperties)
{
var cname = $"Attached > {attachedProperty.OwningType.Name}";
returnVal.GetOrNew(cname).Add((attachedProperty.Name, value?.ToString() ?? "null"));
}
foreach (var v in returnVal.Values)
{
v.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.Ordinal));
}
return returnVal;
}
internal static string PropertyValuesString(Control control, string key)
{
var member = GetAllMembers(control).Find(m => m.Name == key);
return GetMemberValue(member, control, "\n", "\"{0}\"");
}
private static string GetMemberValue(MemberInfo? member, Control control, string separator, string
wrap = "{0}")
{
var value = member?.GetValue(control);
var o = value switch
{
ICollection<Control> controls => string.Join(separator,
controls.Select(ctrl => $"{ctrl.Name}({ctrl.GetType()})")),
ICollection<string> list => string.Join(separator, list),
null => null,
_ => value.ToString()
};
// Convert to quote surrounded string or null with no quotes
return o is not null ? string.Format(wrap, o) : "null";
}
}
internal sealed class SetClipboardCommand : LocalizedCommands
@@ -644,7 +709,8 @@ namespace Robust.Client.Console.Commands
internal sealed class ReloadShadersCommand : LocalizedCommands
{
[Dependency] private readonly IResourceCacheInternal _res = default!;
[Dependency] private readonly IResourceCache _cache = default!;
[Dependency] private readonly IResourceManagerInternal _resManager = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
public override string Command => "rldshader";
@@ -655,7 +721,7 @@ namespace Robust.Client.Console.Commands
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var resC = _res;
var resC = _resManager;
if (args.Length == 1)
{
if (args[0] == "+watch")
@@ -679,9 +745,9 @@ namespace Robust.Client.Console.Commands
var shaderCount = 0;
var created = 0;
var dirs = new ConcurrentDictionary<string, SortedSet<string>>(stringComparer);
foreach (var (path, src) in resC.GetAllResources<ShaderSourceResource>())
foreach (var (path, src) in _cache.GetAllResources<ShaderSourceResource>())
{
if (!resC.TryGetDiskFilePath(path, out var fullPath))
if (!_resManager.TryGetDiskFilePath(path, out var fullPath))
{
throw new NotImplementedException();
}
@@ -730,7 +796,7 @@ namespace Robust.Client.Console.Commands
{
try
{
resC.ReloadResource<ShaderSourceResource>(resPath);
_cache.ReloadResource<ShaderSourceResource>(resPath);
shell.WriteLine($"Reloaded shader: {resPath}");
}
catch (Exception)
@@ -791,11 +857,11 @@ namespace Robust.Client.Console.Commands
shell.WriteLine("Reloading content shader resources...");
foreach (var (path, _) in resC.GetAllResources<ShaderSourceResource>())
foreach (var (path, _) in _cache.GetAllResources<ShaderSourceResource>())
{
try
{
resC.ReloadResource<ShaderSourceResource>(path);
_cache.ReloadResource<ShaderSourceResource>(path);
}
catch (Exception)
{

View File

@@ -43,6 +43,7 @@ public sealed class ProfileEntitySpawningCommand : IConsoleCommand
GC.Collect();
Span<EntityUid> ents = stackalloc EntityUid[amount];
var stopwatch = new Stopwatch();
stopwatch.Start();
@@ -50,12 +51,17 @@ public sealed class ProfileEntitySpawningCommand : IConsoleCommand
for (var i = 0; i < amount; i++)
{
_entities.SpawnEntity(prototype, MapCoordinates.Nullspace);
ents[i] = _entities.SpawnEntity(prototype, MapCoordinates.Nullspace);
}
MeasureProfiler.SaveData();
shell.WriteLine($"Client: Profiled spawning {amount} entities in {stopwatch.Elapsed.TotalMilliseconds:N3} ms");
foreach (var ent in ents)
{
_entities.DeleteEntity(ent);
}
}
}
#endif

View File

@@ -165,7 +165,7 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
{
var textEdit = new TextEdit
{
Placeholder = new Rope.Leaf("You deleted the lipsum OwO")
Placeholder = new Rope.Leaf("You deleted the lipsum\nOwO")
};
TabContainer.SetTabTitle(textEdit, "TextEdit");

View File

@@ -1,6 +1,7 @@
#if DEBUG
using System.Numerics;
using System.Text;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
@@ -8,7 +9,6 @@ using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Client.Debugging
@@ -19,6 +19,7 @@ namespace Robust.Client.Debugging
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterface = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
private Label? _label;
@@ -70,7 +71,7 @@ namespace Robust.Client.Debugging
return;
}
var tile = grid.GetTileRef(spot);
var tile = _mapSystem.GetTileRef(gridUid, grid, spot);
_label.Position = mouseSpot.Position + new Vector2(32, 0);
if (_hovered?.GridId == gridUid && _hovered?.Tile == tile) return;
@@ -79,7 +80,7 @@ namespace Robust.Client.Debugging
var text = new StringBuilder();
foreach (var ent in grid.GetAnchoredEntities(spot))
foreach (var ent in _mapSystem.GetAnchoredEntities(gridUid, grid, spot))
{
if (EntityManager.TryGetComponent<MetaDataComponent>(ent, out var meta))
{

View File

@@ -46,7 +46,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.Input;
@@ -207,6 +206,7 @@ namespace Robust.Client.Debugging
private readonly Font _font;
private HashSet<Joint> _drawnJoints = new();
private List<Entity<MapGridComponent>> _grids = new();
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IMapManager mapManager, IPlayerManager playerManager, IResourceCache cache, DebugPhysicsSystem system, EntityLookupSystem lookup, SharedPhysicsSystem physicsSystem)
{
@@ -225,38 +225,39 @@ namespace Robust.Client.Debugging
{
var viewBounds = args.WorldBounds;
var viewAABB = args.WorldAABB;
var mapId = _eyeManager.CurrentMap;
var mapId = args.MapId;
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Shapes) != 0)
{
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
{
if (_entityManager.HasComponent<MapGridComponent>(physBody.Owner)) continue;
if (_entityManager.HasComponent<MapGridComponent>(physBody)) continue;
var xform = _physicsSystem.GetPhysicsTransform(physBody.Owner);
var xform = _physicsSystem.GetPhysicsTransform(physBody);
var comp = physBody.Comp;
const float AlphaModifier = 0.2f;
foreach (var fixture in _entityManager.GetComponent<FixturesComponent>(physBody.Owner).Fixtures.Values)
foreach (var fixture in _entityManager.GetComponent<FixturesComponent>(physBody).Fixtures.Values)
{
// Invalid shape - Box2D doesn't check for IsSensor but we will for sanity.
if (physBody.BodyType == BodyType.Dynamic && fixture.Density == 0f && fixture.Hard)
if (comp.BodyType == BodyType.Dynamic && fixture.Density == 0f && fixture.Hard)
{
DrawShape(worldHandle, fixture, xform, Color.Red.WithAlpha(AlphaModifier));
}
else if (!physBody.CanCollide)
else if (!comp.CanCollide)
{
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.5f, 0.3f).WithAlpha(AlphaModifier));
}
else if (physBody.BodyType == BodyType.Static)
else if (comp.BodyType == BodyType.Static)
{
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.9f, 0.5f).WithAlpha(AlphaModifier));
}
else if ((physBody.BodyType & (BodyType.Kinematic | BodyType.KinematicController)) != 0x0)
else if ((comp.BodyType & (BodyType.Kinematic | BodyType.KinematicController)) != 0x0)
{
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.5f, 0.9f).WithAlpha(AlphaModifier));
}
else if (!physBody.Awake)
else if (!comp.Awake)
{
DrawShape(worldHandle, fixture, xform, new Color(0.6f, 0.6f, 0.6f).WithAlpha(AlphaModifier));
}
@@ -275,15 +276,18 @@ namespace Robust.Client.Debugging
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
{
var color = Color.Purple.WithAlpha(Alpha);
var transform = _physicsSystem.GetPhysicsTransform(physBody.Owner);
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), 0.2f, color);
var transform = _physicsSystem.GetPhysicsTransform(physBody);
worldHandle.DrawCircle(Transform.Mul(transform, physBody.Comp.LocalCenter), 0.2f, color);
}
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, viewBounds))
_grids.Clear();
_mapManager.FindGridsIntersecting(mapId, viewBounds, ref _grids);
foreach (var grid in _grids)
{
var physBody = _entityManager.GetComponent<PhysicsComponent>(grid.Owner);
var physBody = _entityManager.GetComponent<PhysicsComponent>(grid);
var color = Color.Orange.WithAlpha(Alpha);
var transform = _physicsSystem.GetPhysicsTransform(grid.Owner);
var transform = _physicsSystem.GetPhysicsTransform(grid);
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), 1f, color);
}
}
@@ -292,14 +296,14 @@ namespace Robust.Client.Debugging
{
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
{
if (_entityManager.HasComponent<MapGridComponent>(physBody.Owner)) continue;
if (_entityManager.HasComponent<MapGridComponent>(physBody)) continue;
var xform = _physicsSystem.GetPhysicsTransform(physBody.Owner);
var xform = _physicsSystem.GetPhysicsTransform(physBody);
const float AlphaModifier = 0.2f;
Box2? aabb = null;
foreach (var fixture in _entityManager.GetComponent<FixturesComponent>(physBody.Owner).Fixtures.Values)
foreach (var fixture in _entityManager.GetComponent<FixturesComponent>(physBody).Fixtures.Values)
{
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
@@ -318,10 +322,11 @@ namespace Robust.Client.Debugging
{
_drawnJoints.Clear();
foreach (var jointComponent in _entityManager.EntityQuery<JointComponent>(true))
var query = _entityManager.AllEntityQueryEnumerator<JointComponent>();
while (query.MoveNext(out var uid, out var jointComponent))
{
if (jointComponent.JointCount == 0 ||
!_entityManager.TryGetComponent(jointComponent.Owner, out TransformComponent? xf1) ||
!_entityManager.TryGetComponent(uid, out TransformComponent? xf1) ||
!viewAABB.Contains(xf1.WorldPosition)) continue;
foreach (var (_, joint) in jointComponent.Joints)
@@ -361,37 +366,43 @@ namespace Robust.Client.Debugging
_debugPhysicsSystem.PointCount = 0;
}
worldHandle.UseShader(null);
worldHandle.SetTransform(Matrix3.Identity);
}
private void DrawScreen(DrawingHandleScreen screenHandle, OverlayDrawArgs args)
{
var mapId = _eyeManager.CurrentMap;
var mapId = args.MapId;
var mousePos = _inputManager.MouseScreenPosition;
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0)
{
var hoverBodies = new List<PhysicsComponent>();
var hoverBodies = new List<Entity<PhysicsComponent>>();
var bounds = Box2.UnitCentered.Translated(_eyeManager.PixelToMap(mousePos.Position).Position);
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, bounds))
{
if (_entityManager.HasComponent<MapGridComponent>(physBody.Owner)) continue;
hoverBodies.Add(physBody);
var uid = physBody.Owner;
if (_entityManager.HasComponent<MapGridComponent>(uid)) continue;
hoverBodies.Add((uid, physBody));
}
var lineHeight = _font.GetLineHeight(1f);
var drawPos = mousePos.Position + new Vector2(20, 0) + new Vector2(0, -(hoverBodies.Count * 4 * lineHeight / 2f));
int row = 0;
foreach (var body in hoverBodies)
foreach (var bodyEnt in hoverBodies)
{
if (body != hoverBodies[0])
if (bodyEnt != hoverBodies[0])
{
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), "------");
row++;
}
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
var body = bodyEnt.Comp;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {bodyEnt.Owner}");
row++;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
row++;
@@ -409,7 +420,7 @@ namespace Robust.Client.Debugging
if (mapPos.MapId != args.MapId)
return;
var player = _playerManager.LocalPlayer?.ControlledEntity;
var player = _playerManager.LocalEntity;
if (!_entityManager.TryGetComponent<TransformComponent>(player, out var playerXform) ||
playerXform.MapID != args.MapId)
@@ -430,6 +441,9 @@ namespace Robust.Client.Debugging
}
}
}
screenHandle.UseShader(null);
screenHandle.SetTransform(Matrix3.Identity);
}
protected internal override void Draw(in OverlayDrawArgs args)
@@ -451,11 +465,26 @@ namespace Robust.Client.Debugging
{
switch (fixture.Shape)
{
case ChainShape cShape:
{
var count = cShape.Count;
var vertices = cShape.Vertices;
var v1 = Transform.Mul(xform, vertices[0]);
for (var i = 1; i < count; ++i)
{
var v2 = Transform.Mul(xform, vertices[i]);
worldHandle.DrawLine(v1, v2, color);
v1 = v2;
}
}
break;
case PhysShapeCircle circle:
var center = Transform.Mul(xform, circle.Position);
worldHandle.DrawCircle(center, circle.Radius, color);
break;
case EdgeShape edge:
{
var v1 = Transform.Mul(xform, edge.Vertex1);
var v2 = Transform.Mul(xform, edge.Vertex2);
worldHandle.DrawLine(v1, v2, color);
@@ -465,6 +494,7 @@ namespace Robust.Client.Debugging
worldHandle.DrawCircle(v1, 0.1f, color);
worldHandle.DrawCircle(v2, 0.1f, color);
}
}
break;
case PolygonShape poly:

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