Compare commits

...

154 Commits

Author SHA1 Message Date
metalgearsloth
f659b2b58c Version: 232.0.0 2024-08-29 12:55:51 +10:00
metalgearsloth
b1e13f5b13 Fix BUI interfaces not deep copying (#5410)
* Fix BUI interfaces not deep copying

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

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

* V2 Commit, ObjectSerializer

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

* Added AppearanceDataInit

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

* RELEASE-NOTES.md

* Fix release notes

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

* Fix EmplaceCommand

* Fix sort commands

* Fix JoinCommand

* changelog

---------

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

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

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

* fix merges

* Revert some stuff

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

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

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

* Tests and confidence

* code

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

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

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

* Re-namespace packages

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

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

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

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

* Hot reload: watcher

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

* Put everything dangerous behind conditional comp

* Code cleanup, docs

* Fix a bad comment

* Deal a little better with crashes in the watcher

* Make reload failures Info, since they're expected

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

* Fix an unnecessary null check

I removed the ability for CompileCore to return null.

* injectors: null! strings, default primitives

* Tidy documentation (thanks, PJB!)

* Reinstate netstandard2.0, abolish Pidgin

* Internal-ize all of Robust.Xaml

* Add a cautionary note to Sandbox.yml

* Shuffle around where conditional compilation occurs

* Privatize fields in XamlImplementationStorage

* Internalize XamlJitDelegate

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

* Use file-scoped namespaces

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

* Bump language level for R.Xaml, file namespaces

* Force hot reloading off for integration tests

* Fix bizarre comment/behavior in XamlImplementationStorage

* Consistently use interfaces, even in generated code

* Update Robust.Client/ClientIoC.cs

---------

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

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

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

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

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

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

* Move to system

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

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

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

Need this stuff for AI.

* Fix dumb

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

* better setUniform

* less unsafe

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

Not used.

* release notes

---------

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

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

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

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

* Update test

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

Reported by @NarryG and @nyeogmi

* Sandbox updates

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

* ButtonFlag and better formatting.

* Localization and style cleanup
2024-08-08 18:56:32 +02:00
metalgearsloth
49c831b48d Version: 229.1.2 2024-08-08 12:15:46 +10:00
Leon Friedrich
60a29933d8 Try fix broadphase bug (#5342)
* Try fix broadphase

* I love initialization pasta
2024-08-07 20:07:57 +10:00
metalgearsloth
5729e8eb19 Version: 229.1.1 2024-08-07 10:56:07 +10:00
Leon Friedrich
42da4b1287 Fix replay teleportation command exception (#5337) 2024-08-06 20:56:20 +10:00
metalgearsloth
3342e1272f Add audio filename to entity name (#5338)
Will make debugging tests easier.
2024-08-06 20:54:17 +10:00
metalgearsloth
5c0ce43e6c Version: 229.1.0 2024-08-05 14:54:28 +10:00
metalgearsloth
0717b1fced Avoid resolve in VV prop editor (#5335) 2024-08-05 14:53:55 +10:00
Pieter-Jan Briers
68c03196e6 Fix IPv6-only hosts in HappyEyeballsHttp
Copy paste xd
2024-08-05 00:32:33 +02:00
Pieter-Jan Briers
31292fe4b8 Do network message encryption concurrently. (#5328)
In profiles of RMC-14, encrypting network messages accounted for ~8% of main thread time. That's a lot.

Each NetChannel has an "encryption channel" which gets processed on the thread pool.
2024-08-03 15:21:54 +02:00
Pieter-Jan Briers
865348550f Fix warnings in ClientOccluderSystem.cs
PROJECT ZERO WARNINGS
2024-08-02 15:48:38 +02:00
Pieter-Jan Briers
7372233782 Fix client crash if networking handshake fails
MapManager would get an NRE access its sawmill during client reset because it depends on the uninitialized entity system.
2024-08-02 15:18:50 +02:00
Vasilis
7ebfc82dd6 Reduce the default TPS (#5326)
It is our suggestion for a long while to keep the TPS at 30 for servers. However the default was always 60.

I believe its better to have it as a default.
2024-08-02 00:27:46 +10:00
metalgearsloth
807e7e888a Fix chunkenumerator allocs (#5325)
* Fix chunkenumerator allocs

This was number 2 to pathfinding sitting afk on a server. I thought the property would cache it but apparently not. Ref struct is just nicety and it's internal and not exposed to content anyway so.

* also dis
2024-08-01 14:06:22 +02:00
Stalen
39fefcb9c8 Fixed data race in ParallelTracker (#5311)
* Fixed data race in ParallelTracker

* Added ParallelTracker fix release note
2024-07-30 15:08:58 +10:00
Tayrtahn
b6548c870c Add analyzer/fixer for replacing ProtoId<EntityPrototype> with EntProtoId (#5312)
* Add PreferOtherTypeAttribute, analyzer, and test.

* nullable enable

* Add nuget package for CodeFix verifier

* Add fixer for PreferOtherType

* Rename arguments

* Adjust diagnostic message

* Move attribute lookup
2024-07-23 19:01:43 +02:00
Pieter-Jan Briers
cf230b3454 Warning fixes centered around Clyde
Pulling entity systems into direct fields in Clyde to make it now painful. This required adding an event to ClientEntityManager when these become available, as they are only available when the client is in a server/single player.
2024-07-23 17:49:56 +02:00
Pieter-Jan Briers
16a93e86f6 Add obsoletion warning on control dispose
this shouldn't be used anymore
2024-07-21 01:45:24 +02:00
metalgearsloth
2e4275a7f3 Version: 229.0.0 2024-07-20 15:38:53 +10:00
metalgearsloth
176ca6c578 Add window helper for BUIs (#5183)
* Add window helper for BUIs

Automatically does OnClose and just makes content slightly nicer.

* more

* Add prototype reload helper

* Add Box2i Center

* weh
2024-07-20 14:50:15 +10:00
metalgearsloth
2664061993 Make PhysicsHull a ref struct (#5297)
* Make PhysicsHull a ref struct

First time I've used it but seemed like a good candidate considering it's temporary.

* weh

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-07-15 17:43:33 +02:00
Pieter-Jan Briers
033699d7d6 Fix spawn menu breaking on larger entities
Fixes #5304
2024-07-15 16:23:25 +02:00
metalgearsloth
f696edaa0c Clamp audio tickrate (#5296)
* Clamp audio tickrate

I am reasonably sure I saw a recommended 30TPS figure somewhere but I cannot find it again. At any rate I can't notice this but imagine it provides significant benefits for people on 144hz+ monitors.

* rn

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-07-13 20:09:49 +02:00
SlamBamActionman
4920ecaa64 Add AppearanceSystem Data dictionaries and RemoveData (#5288)
* Improved Appearance

* PR changes
2024-07-13 15:33:05 +10:00
Pieter-Jan Briers
b8924a04cf Fix RaisePredictiveEvent prediction checks (#5294)
RaisePredictiveEvent was made to not check whether prediction is enabled in #3534. This doesn't make much sense to me and is causing various SS14 game logic to erroneously run when prediction is disabled.

Here's the fix PR. Also fixes the assert to actually work (checking Connected is wrong, it should've been InGame) and makes the new check also account for SinglePlayerGame.
2024-07-13 15:30:20 +10:00
Plykiya
be11cb4bca Update GridSplit_Tests.cs to not use Component.Owner (#5300)
Co-authored-by: plykiya <plykiya@protonmail.com>
2024-07-13 15:28:47 +10:00
Plykiya
eafe395273 Update PlayerManager.cs to not use Component.Owner (#5299)
Co-authored-by: plykiya <plykiya@protonmail.com>
2024-07-13 15:28:02 +10:00
Pieter-Jan Briers
05cdb99252 Warning fixes around IMapManager.GetMapEntityId (#5298)
* Add MapSystem.GetMapOrInvalid

This is effectively the same exact behavior as IMapManager.GetMapEntityId. Adding this so I don't have to consider whether warning fixes using MapSystem.GetMap() instead would change behavior.

* Warning fixes around IMapManager.GetMapEntityId

* Fix tests
2024-07-13 15:27:32 +10:00
Plykiya
d4c6b4a828 Update MapGrid_Tests to use MapSystem functions (#5301)
* Update MapGrid_Tests.cs

* missed one

* remove unused entman

---------

Co-authored-by: plykiya <plykiya@protonmail.com>
2024-07-13 14:30:19 +10:00
metalgearsloth
fc1cca4f48 Version: 228.0.0 2024-07-12 21:50:15 +10:00
Pieter-Jan Briers
3657b0a424 Strongly order network prototypes and resources. (#5293)
* Strongly order network prototypes and resources.

When a new client connects, both the uploaded prototypes and resources get sent at once. There was no ordering here, which means that prototypes could easily load before resources. This would then obviously give load errors at runtime. In practice though this seemed fine because the RSI or something would just load fine after when spawned or something.

This was then broken by ae1051e813, which made ResourceCache start caching "that RSI doesn't exist" so it never really tried again.

I originally tried to fix this by adding an API to IResourceManager that allows content to invalidate the aforementioned cache (commit 316a7e4ac10100593202ff7f53dc2992611bbd1e, for however GitHub will track that) but then realized resource uploading isn't part of content like I first thought. Lol whoops. That API might still be useful for other dynamic content use cases, but I'm not committing it for now. That fix still caused errors to be spammed if the prototype was loaded before the resources were ready.

The new fix is to just load resources before prototypes. This is done by making them both ordered relative to each other, and running resources first.

Fixes #5291

* Release notes
2024-07-12 09:12:58 +02:00
Tayrtahn
c3d8080a8e Add PreferNonGenericVariantFor attribute and analyzer (#5190)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-07-10 12:33:56 +02:00
Tayrtahn
8e50924607 Add analyzer/fixer for DataFields with ReadWrite VV (#5164)
* Add analyzer/fixer for datafields with ReadWrite VV

* Nothing to see here
2024-07-10 02:04:55 +02:00
Leon Friedrich
7fdd5c9d1c Make color equality exact (#5253) 2024-07-10 01:43:12 +02:00
Pieter-Jan Briers
7fbcfeaa8f Warning fixes (#5275)
* Warning fixes in Robust.Shared

* Robust.Client warning fixes

* Fix test failure

Test failures were due to broken system registrations for the client RobustUnitTest. It was accidentally registering some server systems, which means DebugPhysicsSystem wasn't gettings its dependencies properly.

Fixing this meant pulling half a dozen extra dependencies that client ContainerSystem and TransformSystem are supposed to have, but didn't.
2024-07-10 01:38:32 +02:00
eoineoineoin
b82bc258db Add styleclass to OptionsButton popup background widget (#5290)
* Add styleclass to OptionsButton popup background widget

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

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>

---------

Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
2024-07-09 23:33:16 +02:00
metalgearsloth
7ad2925f2c Add TryQueueDeleteEntity (#5281)
Having to check deleted by hand up front is annoying.
2024-07-09 23:32:17 +02:00
CaasGit
4091ad4837 Fixes two moderate security issues in ImageSharp (#5257)
Two moderate issues were fixed in ImageSharp 3.1.4:
* https://github.com/SixLabors/ImageSharp/security/advisories/GHSA-g85r-6x2q-45w7
* https://github.com/SixLabors/ImageSharp/security/advisories/GHSA-5x7m-6737-26cr
2024-07-09 23:31:49 +02:00
metalgearsloth
35881d7a6a Add SpriteSystem.IsVisible (#5283)
So content doesn't need to manually check in the rare case we update this.
2024-07-09 22:37:35 +02:00
Pieter-Jan Briers
2d28ac35d8 Interpolated string handler for AudioManager.LogALError
This reduces a decent chunk of useless log allocations.
2024-07-09 17:12:06 +02:00
Pieter-Jan Briers
8b5ad938d5 Fix a closure allocation in physics
Makes InternalParallel a static function. This makes the sort delegate on line 609 statically cacheable by the compiler as it has no state.
2024-07-09 17:00:17 +02:00
Pieter-Jan Briers
723f936a33 Add full caps doc comment about VisibilityComponent
Brought to you by "why does disabling PVS make ghosts visible in SS14"
2024-07-07 20:37:12 +02:00
ShadowCommander
2636879860 ViewVariables UI for Flags Enum and fixes enums with duplicate values (#5287)
* Add editor dropdown for large enums

* Add enum flag selection buttons

* Cleanup
2024-07-07 16:27:17 +10:00
ShadowCommander
dad1da507c Toolshed command help usage (#5274)
* Add usage to toolshed help

* Add name to toolshed usage help

* Better formatting

* Localize toolshed command usage

* Remove unnecessary call

* Cleanup

* Add release notes

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-07-07 16:19:56 +10:00
DrSmugleaf
145c190800 Add cvar to limit nearby command range (#5282)
* Add cvar to limit nearby command range

* Release notes

* More comment

* Fix release note

* Update Robust.Shared/CVars.cs
2024-07-07 13:54:34 +10:00
TemporalOroboros
b7cc0ec629 Adds event for mass and angular inertia changes. (#5286)
* Adds MassDataChangedEvent to physics
This event is raised in response to changes to an entities innate mass/angular inertia/center of mass

* Use properties to fetch data from component

* Comp1

* Vector2

* I sure love an analyzer that doesn't work half the time
2024-07-06 17:34:50 +10:00
Amy
ad329a6b58 whitelist (#5285)
Co-authored-by: amylizzle <amylizzle@users.noreply.github.com>
2024-07-04 15:29:03 +02:00
Guillaume E
4deba4b866 Darken SnapgridCenter placement grid (#5279)
In SS14, the bright blue placement grid was making other game objects
difficult to see in low lighting conditions.
2024-07-04 09:55:46 +10:00
deathride58
4c31083186 Replaces the entity spawn window's bespoke method of object icon rendering with entityprototypeview (#5277) 2024-07-03 21:37:18 +10:00
DrSmugleaf
d31e7ccb55 Add Text property to RichTextLabel (#5280) 2024-07-03 13:02:41 +02:00
metalgearsloth
a9aea7027f Version: 227.0.0 2024-07-01 15:54:45 +10:00
metalgearsloth
2a49c2d9b8 Add loop support for SpriteSystem.GetFrame (#5265)
For Ftl I just want it played once.
2024-07-01 15:50:49 +10:00
metalgearsloth
a0c069f1ea Add LocalTilesIntersecting for circles (#5262)
* Add LocalTilesIntersecting for circles

* Update Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs

Co-authored-by: Tayrtahn <tayrtahn@gmail.com>

---------

Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
2024-06-29 14:57:06 +10:00
Pieter-Jan Briers
2c6fb95e53 Add EntityManager dependency to base LocalizedEntityCommands 2024-06-28 17:00:14 +02:00
Nemanja
afe337644e Make spin box controls disable buttons that can't be pressed (#5221)
* spin box changes

* make SpinboxButton private

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-06-28 17:53:10 +10:00
Plykiya
b8924f3ddf Removes obsolete visibility system functions (#5209)
* Removes obsolete visibility system functions

* guh, forgot to add the test

---------

Co-authored-by: plykiya <plykiya@protonmail.com>
2024-06-28 17:30:02 +10:00
Pieter-Jan Briers
08970e745b Entity console commands system. (#5267)
* Entity console commands system.

This adds a new base type, LocalizedEntityCommands, which is able to import entity systems as dependencies. This is done by only registering these while the entity system is active.

Handling registration separately like this required a bit of changes around ConsoleHost to make it more suitable for this purpose:

You can now directly register command instances, and also have a system to suppress `UpdateAvailableCommands` on the client so there's no bad O(N*M) behavior.

* Convert TeleportCommands.cs to new entity commands.

Removes some obsoletion warnings without pain from having to manually import transform system.

* Fix RobustServerSimulation dependency issue.

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-06-28 17:29:24 +10:00
Leon Friedrich
0ba4a66787 Always process networked events via the priority queue (#5205) 2024-06-28 17:02:14 +10:00
Pieter-Jan Briers
75b3431ee6 New "must call base" analyzer. (#5266)
* New "must call base" analyzer.

This enforces that you actually call base when overriding stuff. This is intended for base methods like entity system's, where server/client systems overriding shared ones SHOULD call Initialize() and such.

* Add MustCallBase to entity system methods
2024-06-28 14:44:49 +10:00
geraeumig
c0ef976588 Make PvsSystem consider offset and zoom from EyeComponent (#5228)
* Make PvsSystem consider offset and zoom from EyeComponent

* Just use PvsScale float

* float.IsFinite

---------

Co-authored-by: geraeumig <alfenos@proton.me>
Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2024-06-28 14:24:22 +10:00
Pieter-Jan Briers
fe5cdf9e3c Fix loading of replays if string package is compressed in zip.
This happened when I had to re-compress a recovered replay from a server crash, and then loaded it up in a dev environment.
2024-06-27 16:07:49 +02:00
Morb
450349188b Dispose memory stream after deserialization exception (#4840)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-06-26 23:16:56 +02:00
metalgearsloth
897ad998d9 Add option for left or right-aligned checkboxes (#4739)
* Add option for left or right-aligned checkboxes

I think right-aligned is better but this is less of a breaking change.

* Cleanup
2024-06-26 23:11:17 +02:00
metalgearsloth
635ae3c353 Remove IGameTiming from TransformComponent (#5273) 2024-06-26 17:10:34 +02:00
Pieter-Jan Briers
a4ea5a4620 Add AnimationCompletedEvent "Finished" boolean.
Content has multiple cases where AnimationCompletedEvent is used to loop an animation. #5238 broke some of these by making this event raised even when manually removed.

Luckily most cases in content tie the animation looping to the presence of a component, so the component getting removed means there's nothing to refresh the loop. LightBehavior is not as fortunate however, causing bugs like https://github.com/space-wizards/space-station-14/issues/29144

This boolean allows looping code to properly distinguish the event, so it won't try to restart an animation after removing it directly.
2024-06-25 15:35:53 +02:00
Pieter-Jan Briers
90e87526d0 Quote tab completions containing spaces. 2024-06-24 16:05:00 +02:00
metalgearsloth
cd6576ddf9 Mark EntityCoordinates.Offset as pure (#5264)
Doesn't do anything just being called and sometimes I forget.
2024-06-24 10:53:41 +02:00
Leon Friedrich
e2cf4ee3db SIMD Colour multiplication (#5251)
* color simd

* removed wrong one

* A

* Use Unsafe.BitCast

* Color4 -> Color

* remove constructor

* remove `in`
2024-06-22 16:42:40 +02:00
metalgearsloth
860c9af2bf Version: 226.3.0 2024-06-22 14:10:43 +10:00
Pieter-Jan Briers
87bb29408a Try to report method source of sandboxing issues. 2024-06-21 00:31:47 +02:00
Pieter-Jan Briers
738cfbe992 Add non-generic IList and ICollection to sandbox.
Used by collection expressions in some cases.
2024-06-21 00:31:47 +02:00
wixoa
90edc02259 Add style property overrides to ContainerButton and TabContainer (#5222)
* Add style box override properties to ContainerButton and TabContainer

* Add background panel to TabContainer, and add text color overrides

* Undo background panel
You can achieve the same by instead putting the TabContainer in a PanelContainer

* Add BackgroundColor property to StyleBoxTexture

* Remove BackgroundColor from StyleBoxTexture
2024-06-20 20:50:51 +02:00
metalgearsloth
da5416a2da Version: 226.2.0 2024-06-20 17:28:11 +10:00
metalgearsloth
021845d956 Add some System.Random methods (#5177)
* Add some System.Random methods

* weh
2024-06-20 17:23:47 +10:00
Leon Friedrich
7fab9f3b8d Fix ContainerSystem debug assert (#5254) 2024-06-20 17:23:38 +10:00
Pieter-Jan Briers
69c1161562 FormattedMessage/DebugConsole performance improvements (#5244)
* Add VisibilityChanged virtual to Control

* Defer updating invisible OutputPanels on UIScale change

DebugConsole falls under this when not hidden, and it significantly improves perf of e.g. resizing the window when there's a lot of stuff in there.

* Avoid redundant UI Scale updates on window resize.

Window resizing can change the UI scale, due to the auto-scaling system. This system had multiple perf issues:

UI scale was set and propagated even if it didn't change (system disabled, not effective, etc). This was just wasted processing.

UI scale was updated for every window resize event. When the game is lagging (due to the aforementioned UI scale updates being expensive...) this means multiple window resize events in a single frame ALL cause a UI scale update, which is useless.

UI scale updates from resizing now avoid doing *nothing* and are deferred until later in the frame for natural batching.

* Reduce allocations/memory usage of various rich-text related things

Just allocate a buncha dictionaries what could possibly go wrong.

I kept to non-breaking-changes which means this couldn't as effective as it should be.

There's some truly repulsive stuff here. Ugh.

* Cap debug console content size.

It's a CVar.

OutputPanel has been switched to use a new RingBufferList datastructure to make removal of the oldest entry efficient.

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-06-20 17:22:12 +10:00
metalgearsloth
095fe9d60f Turn broadphase contacts into a job (#5245)
Okay when I said no more physics this was a low-hanging fruit as we can get rid of the mapmanager getmapentityid for every contact so.
2024-06-20 17:19:26 +10:00
Leon Friedrich
14138fbcc2 Separate PVS serialization from compression & sending (#5246) 2024-06-20 17:18:51 +10:00
Pieter-Jan Briers
48ce24e98b Remove race condition invoking ThreadPool.SetMinThreads call
For some reason we call ThreadPool.SetMinThreads on startup of the game server. Calling this function this early seems to put us at high risk of triggering the following deadlock bug in the .NET runtime: https://github.com/dotnet/runtime/issues/93175

Given I have zero trust in whether this manual ThreadPool fuckery is even helpful, I'm just gonna nuke it and call it a day.
2024-06-20 03:12:01 +02:00
Pieter-Jan Briers
9cde21a7b3 Lower default MTU again.
Yet more reports of people running into issues with the current default.
2024-06-20 00:15:27 +02:00
Pieter-Jan Briers
ae1051e813 Cache non-existence of ResourceCache TryGetResource.
Many patterns (both in engine and content) make use of regular TryGetResource returning null. The problem is that if the resource doesn't exist, it won't be cached and the code attempts to load it from disk *every single time*.

For example, opening an inventory in SS14 would hang the client for ages on some UI themes due to the UITheme texture fallback system constantly trying to load a texture that doesn't exist.
2024-06-19 22:50:09 +02:00
Leon Friedrich
a3f80ac7dd Increase default value of res.rsi_atlas_size (#5250) 2024-06-19 22:09:39 +02:00
CaasGit
f98ef78a21 Update LoaderApi to the latest commit. (#5256) 2024-06-19 19:52:49 +02:00
metalgearsloth
bf8054b181 Version: 226.1.0 2024-06-18 21:50:18 +10:00
metalgearsloth
6b875e6676 Add local entities APIs (#5178)
Need for some vgroid stuff
2024-06-18 21:41:38 +10:00
Vasilis
a687c0a6c0 Change "to" to "from" on advert error (#5247)
It's a message FROM the hub

Currently, if you get "You are banned from the hub, if you believe this is an error contact us" it may confuse someone that they have to visit the hub URL where they will be met with a 404 because it's not an actual website. Seems it looks like "contact us to website"

Similarly, with "Failed to contact status address" makes it look like it's an error message coming from robust failing to connect to the hub server. When it's actually coming from the hub, telling you probably don't have your ports open.

I believe changing it to "from" will get the message acros that this is a message from the HUB and not robust.
2024-06-17 15:15:30 +02:00
Pieter-Jan Briers
0580cf3ff7 Drop SQL exporter in Robust.Benchmarks to fix compilation.
It was using an old Npgsql version, which broke compilation. Updating it breaks some of the custom JSON mapping code.

Comment out the entire thing, it's not being used anymore anyways.
2024-06-17 02:00:11 +02:00
Pieter-Jan Briers
590964d5bf Update SpaceWizards.HttpListener to 0.1.1
This fixes an EXTREMELY RARE crash on server startup due to a race condition. Yes, it did cause a crash in practice that's how I noticed it.
2024-06-16 21:34:15 +02:00
Pieter-Jan Briers
ceda39813d Fix MsgPlayerList being capped to 255
WHY WAS THIS A BYTE.

This prevented having more than 255 people on a server, beyond that the game might get stuck as people's player states wouldn't necessarily get sent.
2024-06-16 21:31:57 +02:00
metalgearsloth
a3a8912f42 Version: 226.0.0 2024-06-17 01:50:04 +10:00
metalgearsloth
b40973157d Animation player fixes (#5238)
Ensures the event always goes out even if the animation is stopped.
2024-06-17 01:47:31 +10:00
Leon Friedrich
1de8731465 Reduce uses of IComponentFactory.GetIndex(Type) (#5242)
* Update `RaiseComponentEvent` & component lifestatge methods

* Fix ComponentNetworkGenerator

* a

* A
2024-06-17 01:46:51 +10:00
Leon Friedrich
3a479cb5f4 Add ComponentEventAttribute to AfterAutoHandleStateEvent (#5243) 2024-06-15 17:32:19 +02:00
slarticodefast
76eeebf439 Allow RequestScreenTexture to be set in overlays (#5234) 2024-06-15 23:35:10 +10:00
310 changed files with 9135 additions and 2088 deletions

View File

@@ -19,6 +19,7 @@
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit" Version="1.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit" Version="1.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.8.0" />
@@ -43,7 +44,7 @@
<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="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<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" />
@@ -55,8 +56,8 @@
<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="SixLabors.ImageSharp" Version="3.1.5" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
@@ -71,4 +72,4 @@
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
<PackageVersion Include="PolySharp" Version="1.14.1" />
</ItemGroup>
</Project>
</Project>

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

@@ -54,6 +54,299 @@ END TEMPLATE-->
*None yet*
## 232.0.0
### Breaking changes
* Obsolete method `AppearanceComponent.TryGetData` is now access-restricted to `SharedAppearanceSystem`; use `SharedAppearanceSystem.TryGetData` instead.
### New features
* Added `SharedAppearanceSystem.AppendData`, which appends non-existing `AppearanceData` from one `AppearanceComponent` to another.
* Added `AppearanceComponent.AppearanceDataInit`, which can be used to set initial `AppearanceData` entries in .yaml.
### Bugfixes
* Fix BUI interfaces not deep-copying in state handling.
* Add Robust.Xaml.csproj to the solution to fix some XAML issues.
### Other
* Serialization will now add type tags (`!type:<T>`) for necessary `NodeData` when writing (currently only for `object` nodes).
### Internal
* Added `ObjectSerializer`, which handles serialization of the generic `object` type.
## 231.1.1
### Bugfixes
* Fixed a bug where the client might not add entities to the broadphase/lookup components.
* Fixed various toolshed commands not working, including `sort`, `sortdown` `join` (for strings), and `emplace`
### Other
* Toolshed command blocks now stop executing if previous errors were not handled / cleared.
## 231.1.0
### New features
* Network `InterfaceData` on `UserInterfaceComponent`.
* Added `System.Decimal` to sandbox.
* Added XAML hot reloading.
* Added API for content to write custom files into replay through `IReplayFileWriter`.
### Other
* Optimized `EntityLookup` and other physics systems.
### Internal
* Added more tests related to physics.
## 231.0.1
### Other
* Add better logging to failed PVS sends.
## 231.0.0
### Breaking changes
* ViewSubscriber has been moved to shared; it doesn't actually do anything on the client but makes shared code easier.
### New features
* ContactEnumreator exists to iterate the contacts of a particular entity.
* Add FixturesChangeComponent as a generic way to add and remove fixtures easily.
* PointLightComponent enabling / disabling now has an attempt event if you wish to block it on content side.
* There's an OpenScreenAt overload for screen-relative coordinates.
* SpriteSystem has methods to get an entity's position in sprite terms.
* EntityManager and ComponentFactory now have additional methods that interact with ComponentRegistry and ComponentRegistryEntry.
### Bugfixes
* Fix PrototypeFlags Add not actually working.
* Fix BUIs going BRRT opening and closing repeatedly upon prediction. The closing now gets deferred to the update loop if it's still closed at the end of prediction.
## 230.2.0
### New features
* Add ProcessNow for IRobustJob as a convenience method where you may not want to run a job in the background sometimes.
* Add Vector2i helpers to all 8 neighbouring directions.
### Other
* Remove IThreadPoolWorkItem interface from IRobustJob.
## 230.1.0
### New features
* You can now pass `bool[]` parameters to shaders.
* Added `toolshed.nearby_entities_limit` CVar.
* Fix `RichTextLabel.Text` to clear and reset the message properly in all cases.
* `scene` command has tab completion now.
* `devwindow` UI inspector property catches exceptions for read properties.
* `SplitContainer.Flip()`
### Bugfixes
* Fix tile enlargement not being applied for some EntityLookup queries.
* `LocalizedEntityCommands` are now not initialized inside `RobustUnitTest`, fixing guaranteed test failures.
* Fixed issues with broadphase init breaking replays frequently.
* Fix uploaded prototypes and resources for clients connecting to a server.
### Other
* Improved error reporting for DataField analyzer.
## 230.0.1
## 230.0.0
### New features
* Added `InterpolatedStringHandlerArgumentAttribute` to the sandbox whitelist.
* `IUserInterfaceManager.Popup()` popups now have a copy to clipboard button.
### Bugfixes
* Security fixes
* Fix exception in `TimedDespawnComponent` spawning another `TimedDespawnComponent`.
* Fixed pool memory leak in physics `SolveIsland`.
## 229.1.2
### Bugfixes
* Fixed a bug where the client might not add entities to the broadphase/lookup components.
## 229.1.1
### Bugfixes
* Fix some teleportation commands not working in singleplayer or replays
### Other
* Audio entity names now include the filepath of the audio being played if relevant for debugging.
## 229.1.0
### Bugfixes
* Fix multithreading bug in ParallelTracker that caused the game to crash randomly.
* Fixed IPv6-only hosts not working properly with built-in HTTP clients.
### Other
* Added obsoletion warning for `Control.Dispose()`. New code should not rely on it.
* Reduced the default tickrate to 30 ticks.
* Encryption of network messages is now done concurrently to avoid spending main thread time. In profiles, this added up to ~8% of main thread time on RMC-14.
## 229.0.0
### Breaking changes
* Fixes large entities causing entity spawn menu to break.
* Made PhysicsHull an internal ref struct for some PolygonShape speedup.
### New features
* Audio ticks-per-second is now capped at 30 by default and controlled via `audio.tick_rate` cvar.
* Add CreateWindow and CreateDisposableControl helpers for BUIs.
* Add OnProtoReload virtual method to BUIs that gets called on prototype reloads.
* Add RemoveData to AppearanceSystem data.
## 228.0.0
### Breaking changes
* The `Color` struct's equality methods now check for exact equality. Use `MathHelper.CloseToPercent(Color, Color)` for the previous functionality.
* Added a toolshed.nearby_limit cvar to limit the maximum range of the nearby command. Defaults to 200.
### New features
* Added command usage with types to Toolshed command help.
* Add Text property to RichTextLabel.
* Whitelist System.Net.IPEndPoint.
* Add event for mass & angular inertia changes.
* Add SpriteSystem.IsVisible for layers.
* Add TryQueueDeleteEntity that checks if the entity is already deleted / queuedeleted first.
### Bugfixes
* Clients connecting to a server now always load prototype uploads after resource uploads, fixing ordering bugs that could cause various errors.
## 227.0.0
### Breaking changes
* Add a `loop` arg to SpriteSystem.GetFrame in case you don't want to get a looping animation.
* Remove obsolete VisibileSystem methods.
### New features
* Added `LocalizedEntityCommands`, which are console commands that have the ability to take entity system dependencies.
* Added `BeginRegistrationRegion` to `IConsoleHost` to allow efficient bulk-registration of console commands.
* Added `IConsoleHost.RegisterCommand` overload that takes an `IConsoleCommand`.
* Added a `Finished` boolean to `AnimationCompletedEvent` which allows distinguishing if an animation was removed prematurely or completed naturally.
* Add GetLocalTilesIntersecting for MapSystem.
* Add an analyzer for methods that should call the base implementation and use it for EntitySystems.
### Bugfixes
* Fix loading replays if string package is compressed inside a zip.
### Other
* Tab completions containing spaces are now properly quoted, so the command will actually work properly once entered.
* Mark EntityCoordinates.Offset as Pure so it shows as warnings if the variable is unused.
* Networked events will always be processed in order even if late.
## 226.3.0
### New features
* `System.Collections.IList` and `System.Collections.ICollection` are now sandbox safe, this fixes some collection expression cases.
* The sandboxing system will now report the methods responsible for references to illegal items.
## 226.2.0
### New features
* `Control.VisibilityChanged()` virtual function.
* Add some System.Random methods for NextFloat and NextPolarVector2.
### Bugfixes
* Fixes ContainerSystem failing client-side debug asserts when an entity gets unanchored & inserted into a container on the same tick.
* Remove potential race condition on server startup from invoking ThreadPool.SetMinThreads.
### Other
* Increase default value of res.rsi_atlas_size.
* Fix internal networking logic.
* Updates of `OutputPanel` contents caused by change in UI scale are now deferred until visible. Especially important to avoid updates from debug console.
* Debug console is now limited to only keep `con.max_entries` entries.
* Non-existent resources are cached by `IResourceCache.TryGetResource`. This avoids the game constantly trying to re-load non-existent resources in common patterns such as UI theme texture fallbacks.
* Default IPv4 MTU has been lowered to 700.
* Update Robust.LoaderApi.
### Internal
* Split out PVS serialization from compression and sending game states.
* Turn broadphase contacts into an IParallelRobustJob and remove unnecessary GetMapEntityIds for every contact.
## 226.1.0
### New features
* Add some GetLocalEntitiesIntersecting methods for `Entity<T>`.
### Other
* Fix internal networking logic
## 226.0.0
### Breaking changes
* `IEventBus.RaiseComponentEvent` now requires an EntityUid argument.
* The `AddedComponentEventArgs` and `RemovedComponentEventArgs` constructors are now internal
### New features
* Allow RequestScreenTexture to be set in overlays.
### Bugfixes
* Fix AnimationCompletedEvent not always going out.
## 225.0.0
### Breaking changes

View File

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

View File

@@ -0,0 +1,91 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.DataDefinitionAnalyzer>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
public sealed class DataDefinitionAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<DataDefinitionAnalyzer, 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;
using Robust.Shared.ViewVariables;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.ViewVariables
{
public sealed class ViewVariablesAttribute : Attribute
{
public readonly VVAccess Access = VVAccess.ReadOnly;
public ViewVariablesAttribute() { }
public ViewVariablesAttribute(VVAccess access)
{
Access = access;
}
}
public enum VVAccess : byte
{
ReadOnly = 0,
ReadWrite = 1,
}
}
namespace Robust.Shared.Serialization.Manager.Attributes
{
public class DataFieldBaseAttribute : Attribute;
public class DataFieldAttribute : DataFieldBaseAttribute;
public sealed class DataDefinitionAttribute : Attribute;
}
[DataDefinition]
public sealed partial class Foo
{
[DataField, ViewVariables(VVAccess.ReadWrite)]
public int Bad;
[DataField]
public int Good;
[DataField, ViewVariables]
public int Good2;
[DataField, ViewVariables(VVAccess.ReadOnly)]
public int Good3;
[ViewVariables]
public int Good4;
}
""";
await Verifier(code,
// /0/Test0.cs(35,17): info RA0028: Data field Bad in data definition Foo has ViewVariables attribute with ReadWrite access, which is redundant
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldNoVVReadWriteRule).WithSpan(35, 17, 35, 50).WithArguments("Bad", "Foo")
);
}
}

View File

@@ -0,0 +1,92 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.MustCallBaseAnalyzer>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
public sealed class MustCallBaseAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<MustCallBaseAnalyzer, NUnitVerifier>()
{
TestState =
{
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.IoC.MustCallBaseAttribute.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.Analyzers;
public class Foo
{
[MustCallBase]
public virtual void Function()
{
}
[MustCallBase(true)]
public virtual void Function2()
{
}
}
public class Bar : Foo
{
public override void Function()
{
}
public override void Function2()
{
}
}
public class Baz : Foo
{
public override void Function()
{
base.Function();
}
}
public class Bal : Bar
{
public override void Function2()
{
}
}
""";
await Verifier(code,
// /0/Test0.cs(20,26): warning RA0028: Overriders of this function must always call the base function
VerifyCS.Diagnostic().WithSpan(20, 26, 20, 34),
// /0/Test0.cs(41,26): warning RA0028: Overriders of this function must always call the base function
VerifyCS.Diagnostic().WithSpan(41, 26, 41, 35));
}
}

View File

@@ -0,0 +1,71 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.PreferNonGenericVariantForAnalyzer>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
public sealed class PreferNonGenericVariantForTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<PreferNonGenericVariantForAnalyzer, NUnitVerifier>()
{
TestState =
{
Sources = { code },
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute.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.Analyzers;
public class Bar { };
public class Baz { };
public class Okay { };
public static class Foo
{
[PreferNonGenericVariantFor(typeof(Bar), typeof(Baz))]
public static void DoFoo<T>() { }
}
public class Test
{
public void DoBad()
{
Foo.DoFoo<Bar>();
}
public void DoGood()
{
Foo.DoFoo<Okay>();
}
}
""";
await Verifier(code,
// /0/Test0.cs(17,9): warning RA0029: Use the non-generic variant of this method for type Bar
VerifyCS.Diagnostic().WithSpan(17, 9, 17, 25).WithArguments("Bar")
);
}
}

View File

@@ -0,0 +1,62 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.PreferOtherTypeAnalyzer>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
public sealed class PreferOtherTypeAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<PreferOtherTypeAnalyzer, NUnitVerifier>()
{
TestState =
{
Sources = { code },
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.Analyzers.PreferOtherTypeAttribute.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.Analyzers;
public class EntityPrototype { };
public class EntProtoId { };
public class ReagentPrototype { };
[PreferOtherType(typeof(EntityPrototype), typeof(EntProtoId))]
public class ProtoId<T> { };
public class Test
{
public ProtoId<EntityPrototype> Bad = new();
public ProtoId<ReagentPrototype> Good = new();
}
""";
await Verifier(code,
// /0/Test0.cs(12,12): warning RA0031: Use the specific type EntProtoId instead of ProtoId when the type argument is EntityPrototype
VerifyCS.Diagnostic().WithSpan(12, 12, 12, 48).WithArguments("EntProtoId", "ProtoId", "EntityPrototype")
);
}
}

View File

@@ -0,0 +1,81 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.PreferOtherTypeAnalyzer>;
namespace Robust.Analyzers.Tests;
public sealed class PreferOtherTypeFixerTest
{
private static Task Verifier(string code, string fixedCode, params DiagnosticResult[] expected)
{
var test = new CSharpCodeFixTest<PreferOtherTypeAnalyzer, PreferOtherTypeFixer, NUnitVerifier>()
{
TestState =
{
Sources = { code },
},
FixedState =
{
Sources = { fixedCode },
}
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs"
);
TestHelper.AddEmbeddedSources(
test.FixedState,
"Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs"
);
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task Test()
{
const string code = """
using Robust.Shared.Analyzers;
public class EntityPrototype { };
public class EntProtoId { };
public class ReagentPrototype { };
[PreferOtherType(typeof(EntityPrototype), typeof(EntProtoId))]
public class ProtoId<T> { };
public class Test
{
public ProtoId<EntityPrototype> Foo = new();
}
""";
const string fixedCode = """
using Robust.Shared.Analyzers;
public class EntityPrototype { };
public class EntProtoId { };
public class ReagentPrototype { };
[PreferOtherType(typeof(EntityPrototype), typeof(EntProtoId))]
public class ProtoId<T> { };
public class Test
{
public EntProtoId Foo = new();
}
""";
await Verifier(code, fixedCode,
// /0/Test0.cs(12,12): error RA0031: Use the specific type EntProtoId instead of ProtoId when the type argument is EntityPrototype
VerifyCS.Diagnostic().WithSpan(12, 12, 12, 48).WithArguments("EntProtoId", "ProtoId", "EntityPrototype"));
}
}

View File

@@ -10,6 +10,9 @@
<ItemGroup>
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" LogicalName="Robust.Shared.Analyzers.AccessAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" LogicalName="Robust.Shared.Analyzers.AccessPermissions.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\MustCallBaseAttribute.cs" LogicalName="Robust.Shared.IoC.MustCallBaseAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferNonGenericVariantForAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferOtherTypeAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
@@ -25,6 +28,7 @@
<PackageReference Include="Microsoft.CodeAnalysis.Analyzer.Testing"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces"/>
<PackageReference Include="NUnit"/>
<PackageReference Include="NUnit3TestAdapter"/>

View File

@@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
using Robust.Shared.Serialization.Manager.Definition;
using Robust.Shared.ViewVariables;
namespace Robust.Analyzers;
@@ -16,6 +17,9 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private const string DataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataDefinitionAttribute";
private const string ImplicitDataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.ImplicitDataDefinitionForInheritorsAttribute";
private const string DataFieldBaseNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataFieldBaseAttribute";
private const string ViewVariablesNamespace = "Robust.Shared.ViewVariables.ViewVariablesAttribute";
private const string DataFieldAttributeName = "DataField";
private const string ViewVariablesAttributeName = "ViewVariables";
private static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
Diagnostics.IdDataDefinitionPartial,
@@ -66,9 +70,20 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
true,
"Make sure to remove the tag string from the data field attribute."
);
public static readonly DiagnosticDescriptor DataFieldNoVVReadWriteRule = new(
Diagnostics.IdDataFieldNoVVReadWrite,
"Data field has VV ReadWrite",
"Data field {0} in data definition {1} has ViewVariables attribute with ReadWrite access, which is redundant",
"Usage",
DiagnosticSeverity.Info,
true,
"Make sure to remove the ViewVariables attribute."
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule,
DataFieldRedundantTagRule
DataFieldRedundantTagRule, DataFieldNoVVReadWriteRule
);
public override void Initialize(AnalysisContext context)
@@ -139,7 +154,14 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (HasRedundantTag(fieldSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
TryGetAttributeLocation(field, DataFieldAttributeName, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, location, fieldSymbol.Name, type.Name));
}
if (HasVVReadWrite(fieldSymbol))
{
TryGetAttributeLocation(field, ViewVariablesAttributeName, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldNoVVReadWriteRule, location, fieldSymbol.Name, type.Name));
}
}
}
@@ -168,7 +190,14 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (HasRedundantTag(propertySymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
TryGetAttributeLocation(property, DataFieldAttributeName, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, location, propertySymbol.Name, type.Name));
}
if (HasVVReadWrite(propertySymbol))
{
TryGetAttributeLocation(property, ViewVariablesAttributeName, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldNoVVReadWriteRule, location, propertySymbol.Name, type.Name));
}
}
@@ -238,6 +267,24 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
return false;
}
private static bool TryGetAttributeLocation(MemberDeclarationSyntax syntax, string attributeName, out Location location)
{
foreach (var attributeList in syntax.AttributeLists)
{
foreach (var attribute in attributeList.Attributes)
{
if (attribute.Name.ToString() != attributeName)
continue;
location = attribute.GetLocation();
return true;
}
}
// Default to the declaration syntax's location
location = syntax.GetLocation();
return false;
}
private static bool IsReadOnlyMember(ITypeSymbol type, ISymbol member)
{
if (member is IFieldSymbol field)
@@ -292,6 +339,34 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
return explicitName == automaticName;
}
private static bool HasVVReadWrite(ISymbol symbol)
{
if (!IsDataField(symbol, out _, out _))
return false;
// Make sure it has ViewVariablesAttribute
AttributeData? viewVariablesAttribute = null;
foreach (var attr in symbol.GetAttributes())
{
if (attr.AttributeClass?.ToDisplayString() == ViewVariablesNamespace)
{
viewVariablesAttribute = attr;
}
}
if (viewVariablesAttribute == null)
return false;
// Default is ReadOnly, which is fine
if (viewVariablesAttribute.ConstructorArguments.Length == 0)
return false;
var accessArgument = viewVariablesAttribute.ConstructorArguments[0];
if (accessArgument.Value is not byte accessByte)
return false;
return (VVAccess)accessByte == VVAccess.ReadWrite;
}
private static bool IsImplicitDataDefinition(ITypeSymbol type)
{
if (HasAttribute(type, ImplicitDataDefinitionNamespace))

View File

@@ -15,9 +15,11 @@ public sealed class DefinitionFixer : CodeFixProvider
{
private const string DataFieldAttributeName = "DataField";
private const string ViewVariablesAttributeName = "ViewVariables";
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable,
IdDataFieldRedundantTag
IdDataFieldRedundantTag, IdDataFieldNoVVReadWrite
);
public override Task RegisterCodeFixesAsync(CodeFixContext context)
@@ -36,6 +38,8 @@ public sealed class DefinitionFixer : CodeFixProvider
return RegisterDataFieldPropertyFix(context, diagnostic);
case IdDataFieldRedundantTag:
return RegisterRedundantTagFix(context, diagnostic);
case IdDataFieldNoVVReadWrite:
return RegisterVVReadWriteFix(context, diagnostic);
}
}
@@ -136,6 +140,48 @@ public sealed class DefinitionFixer : CodeFixProvider
return document.WithSyntaxRoot(root);
}
private static async Task RegisterVVReadWriteFix(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;
context.RegisterCodeFix(CodeAction.Create(
"Remove ViewVariables attribute",
c => RemoveVVAttribute(context.Document, token, c),
"Remove ViewVariables attribute"
), diagnostic);
}
private static async Task<Document> RemoveVVAttribute(Document document, MemberDeclarationSyntax syntax, CancellationToken cancellation)
{
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
var newLists = new SyntaxList<AttributeListSyntax>();
foreach (var attributeList in syntax.AttributeLists)
{
var attributes = new SeparatedSyntaxList<AttributeSyntax>();
foreach (var attribute in attributeList.Attributes)
{
if (attribute.Name.ToString() != ViewVariablesAttributeName)
{
attributes = attributes.Add(attribute);
}
}
// Don't add empty lists []
if (attributes.Count > 0)
newLists = newLists.Add(attributeList.WithAttributes(attributes));
}
var newSyntax = syntax.WithAttributeLists(newLists);
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,111 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
#nullable enable
/// <summary>
/// Enforces <c>MustCallBaseAttribute</c>.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class MustCallBaseAnalyzer : DiagnosticAnalyzer
{
private const string Attribute = "Robust.Shared.Analyzers.MustCallBaseAttribute";
private static readonly DiagnosticDescriptor Rule = new(
Diagnostics.IdMustCallBase,
"No base call in overriden function",
"Overriders of this function must always call the base function",
"Usage",
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Method);
}
private static void AnalyzeSymbol(SymbolAnalysisContext context)
{
if (context.Symbol is not IMethodSymbol { IsOverride: true } method)
return;
var attrSymbol = context.Compilation.GetTypeByMetadataName(Attribute);
if (attrSymbol == null)
return;
if (DoesMethodOverriderHaveAttribute(method, attrSymbol) is not { } data)
return;
if (data is { onlyOverrides: true, depth: < 2 })
return;
var syntax = (MethodDeclarationSyntax) method.DeclaringSyntaxReferences[0].GetSyntax();
if (HasBaseCall(syntax))
return;
var diag = Diagnostic.Create(Rule, syntax.Identifier.GetLocation());
context.ReportDiagnostic(diag);
}
private static (int depth, bool onlyOverrides)? DoesMethodOverriderHaveAttribute(
IMethodSymbol method,
INamedTypeSymbol attributeSymbol)
{
var depth = 0;
while (method.OverriddenMethod != null)
{
depth += 1;
method = method.OverriddenMethod;
if (GetAttribute(method, attributeSymbol) is not { } attribute)
continue;
var onlyOverrides = attribute.ConstructorArguments is [{Kind: TypedConstantKind.Primitive, Value: true}];
return (depth, onlyOverrides);
}
return null;
}
private static bool HasBaseCall(MethodDeclarationSyntax syntax)
{
return syntax.Accept(new BaseCallLocator());
}
private static AttributeData? GetAttribute(ISymbol namedTypeSymbol, INamedTypeSymbol attrSymbol)
{
return namedTypeSymbol.GetAttributes()
.SingleOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol));
}
private sealed class BaseCallLocator : CSharpSyntaxVisitor<bool>
{
public override bool VisitBaseExpression(BaseExpressionSyntax node)
{
return true;
}
public override bool DefaultVisit(SyntaxNode node)
{
foreach (var childNode in node.ChildNodes())
{
if (childNode is not CSharpSyntaxNode cSharpSyntax)
continue;
if (cSharpSyntax.Accept(this))
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,65 @@
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 PreferNonGenericVariantForAnalyzer : DiagnosticAnalyzer
{
private const string AttributeType = "Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute";
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
UseNonGenericVariantDescriptor
);
private static readonly DiagnosticDescriptor UseNonGenericVariantDescriptor = new(
Diagnostics.IdUseNonGenericVariant,
"Consider using the non-generic variant of this method",
"Use the non-generic variant of this method for type {0}",
"Usage",
DiagnosticSeverity.Warning,
true,
"Use the generic variant of this method.");
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics | GeneratedCodeAnalysisFlags.Analyze);
context.EnableConcurrentExecution();
context.RegisterOperationAction(CheckForNonGenericVariant, OperationKind.Invocation);
}
private void CheckForNonGenericVariant(OperationAnalysisContext obj)
{
if (obj.Operation is not IInvocationOperation invocationOperation) return;
var preferNonGenericAttribute = obj.Compilation.GetTypeByMetadataName(AttributeType);
HashSet<ITypeSymbol> forTypes = [];
foreach (var attribute in invocationOperation.TargetMethod.GetAttributes())
{
if (!SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, preferNonGenericAttribute))
continue;
foreach (var type in attribute.ConstructorArguments[0].Values)
forTypes.Add((ITypeSymbol)type.Value);
break;
}
if (forTypes == null)
return;
foreach (var typeArg in invocationOperation.TargetMethod.TypeArguments)
{
if (forTypes.Contains(typeArg))
{
obj.ReportDiagnostic(
Diagnostic.Create(UseNonGenericVariantDescriptor,
invocationOperation.Syntax.GetLocation(), typeArg.Name));
}
}
}
}

View File

@@ -0,0 +1,75 @@
#nullable enable
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class PreferOtherTypeAnalyzer : DiagnosticAnalyzer
{
private const string AttributeType = "Robust.Shared.Analyzers.PreferOtherTypeAttribute";
private static readonly DiagnosticDescriptor PreferOtherTypeDescriptor = new(
Diagnostics.IdPreferOtherType,
"Use the specific type",
"Use the specific type {0} instead of {1} when the type argument is {2}",
"Usage",
DiagnosticSeverity.Error,
true,
"Use the specific type.");
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
PreferOtherTypeDescriptor
);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics | GeneratedCodeAnalysisFlags.Analyze);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeField, SyntaxKind.VariableDeclaration);
}
private void AnalyzeField(SyntaxNodeAnalysisContext context)
{
if (context.Node is not VariableDeclarationSyntax node)
return;
// Get the type of the generic being used
if (node.Type is not GenericNameSyntax genericName)
return;
var genericSyntax = genericName.TypeArgumentList.Arguments[0];
if (context.SemanticModel.GetSymbolInfo(genericSyntax).Symbol is not { } genericType)
return;
// Look for the PreferOtherTypeAttribute
var symbolInfo = context.SemanticModel.GetSymbolInfo(node.Type);
if (symbolInfo.Symbol?.GetAttributes() is not { } attributes)
return;
var preferOtherTypeAttribute = context.Compilation.GetTypeByMetadataName(AttributeType);
foreach (var attribute in attributes)
{
if (!SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, preferOtherTypeAttribute))
continue;
// See if the generic type argument matches the type the attribute specifies
if (attribute.ConstructorArguments[0].Value is not ITypeSymbol checkedType)
return;
if (!SymbolEqualityComparer.Default.Equals(checkedType, genericType))
continue;
if (attribute.ConstructorArguments[1].Value is not ITypeSymbol replacementType)
continue;
context.ReportDiagnostic(Diagnostic.Create(PreferOtherTypeDescriptor,
context.Node.GetLocation(),
replacementType.Name,
symbolInfo.Symbol.Name,
genericType.Name));
}
}
}

View File

@@ -0,0 +1,97 @@
#nullable enable
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Robust.Roslyn.Shared.Diagnostics;
namespace Robust.Analyzers;
[ExportCodeFixProvider(LanguageNames.CSharp)]
public sealed class PreferOtherTypeFixer : CodeFixProvider
{
private const string PreferOtherTypeAttributeName = "PreferOtherTypeAttribute";
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
IdPreferOtherType
);
public override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
foreach (var diagnostic in context.Diagnostics)
{
switch (diagnostic.Id)
{
case IdPreferOtherType:
return RegisterReplaceType(context, diagnostic);
}
}
return Task.CompletedTask;
}
private static async Task RegisterReplaceType(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<VariableDeclarationSyntax>().First();
if (token == null)
return;
context.RegisterCodeFix(CodeAction.Create(
"Replace type",
c => ReplaceType(context.Document, token, c),
"Replace type"
), diagnostic);
}
private static async Task<Document> ReplaceType(Document document, VariableDeclarationSyntax syntax, CancellationToken cancellation)
{
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
var model = await document.GetSemanticModelAsync(cancellation);
if (model == null)
return document;
if (syntax.Type is not GenericNameSyntax genericNameSyntax)
return document;
var genericTypeSyntax = genericNameSyntax.TypeArgumentList.Arguments[0];
if (model.GetSymbolInfo(genericTypeSyntax).Symbol is not {} genericTypeSymbol)
return document;
var symbolInfo = model.GetSymbolInfo(syntax.Type);
if (symbolInfo.Symbol?.GetAttributes() is not { } attributes)
return document;
foreach (var attribute in attributes)
{
if (attribute.AttributeClass?.Name != PreferOtherTypeAttributeName)
continue;
if (attribute.ConstructorArguments[0].Value is not ITypeSymbol checkedTypeSymbol)
continue;
if (!SymbolEqualityComparer.Default.Equals(checkedTypeSymbol, genericTypeSymbol))
continue;
if (attribute.ConstructorArguments[1].Value is not ITypeSymbol replacementTypeSymbol)
continue;
var replacementIdentifier = SyntaxFactory.IdentifierName(replacementTypeSymbol.Name);
var replacementSyntax = syntax.WithType(replacementIdentifier);
root = root!.ReplaceNode(syntax, replacementSyntax);
return document.WithSyntaxRoot(root);
}
return document;
}
}

View File

@@ -16,9 +16,21 @@
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<ItemGroup>
<!-- Needed for PreferNonGenericVariantAnalyzer. -->
<Compile Include="..\Robust.Shared\Analyzers\PreferNonGenericVariantForAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<ItemGroup>
<!-- Needed for PreferOtherTypeAnalyzer. -->
<Compile Include="..\Robust.Shared\Analyzers\PreferOtherTypeAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<ItemGroup>
<!-- Needed for DataDefinitionAnalyzer. -->
<Compile Include="..\Robust.Shared\Serialization\Manager\Definition\DataDefinitionUtility.cs" LinkBase="Implementations" />
<Compile Include="..\Robust.Shared\ViewVariables\ViewVariablesAttribute.cs" LinkBase="Implementations" />
<Compile Include="..\Robust.Shared\Serialization\NetSerializableAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />

View File

@@ -26,7 +26,8 @@ public sealed class DefaultSQLConfig : IConfig
public IEnumerable<IExporter> GetExporters()
{
yield return SQLExporter.Default;
//yield return SQLExporter.Default;
yield break;
}
public IEnumerable<IColumnProvider> GetColumnProviders() => DefaultConfig.Instance.GetColumnProviders();

View File

@@ -15,11 +15,10 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Npgsql;
using Npgsql.Internal;
using Npgsql.Internal.TypeHandlers;
using Npgsql.Internal.TypeHandling;
namespace Robust.Benchmarks.Exporters;
/*
public sealed class SQLExporter : IExporter
{
private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions
@@ -98,7 +97,9 @@ public sealed class SQLExporter : IExporter
public string Name => "sql";
}
*/
/*
// https://github.com/npgsql/efcore.pg/issues/1107#issuecomment-945126627
class JsonOverrideTypeHandlerResolverFactory : TypeHandlerResolverFactory
{
@@ -138,6 +139,7 @@ class JsonOverrideTypeHandlerResolverFactory : TypeHandlerResolverFactory
=> null; // Let the built-in resolver do this
}
}
*/
public sealed class DesignTimeContextFactoryPostgres : IDesignTimeDbContextFactory<BenchmarkContext>
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -302,7 +302,7 @@ internal partial class AudioManager
}
/// <inheritdoc/>
IBufferedAudioSource? IAudioInternal.CreateBufferedAudioSource(int buffers, bool floatAudio=false)
IBufferedAudioSource? IAudioInternal.CreateBufferedAudioSource(int buffers, bool floatAudio)
{
var source = AL.GenSource();

View File

@@ -143,12 +143,11 @@ internal sealed partial class AudioManager : IAudioInternal
/// <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)
internal void LogALError(ALErrorInterpolatedStringHandler message, [CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1)
{
var error = AL.GetError();
if (error != ALError.NoError)
if (message.Error != ALError.NoError)
{
OpenALSawmill.Error("[{0}:{1}] AL error: {2}, {3}. Stacktrace is {4}", callerMember, callerLineNumber, error, message, Environment.StackTrace);
OpenALSawmill.Error("[{0}:{1}] AL error: {2}, {3}. Stacktrace is {4}", callerMember, callerLineNumber, message.Error, message.ToStringAndClear(), Environment.StackTrace);
}
}
@@ -170,4 +169,32 @@ internal sealed partial class AudioManager : IAudioInternal
BufferHandle = bufferHandle;
}
}
[InterpolatedStringHandler]
internal ref struct ALErrorInterpolatedStringHandler
{
private DefaultInterpolatedStringHandler _handler;
public ALError Error;
public ALErrorInterpolatedStringHandler(int literalLength, int formattedCount, out bool shouldAppend)
{
Error = AL.GetError();
if (Error == ALError.NoError)
{
shouldAppend = false;
_handler = default;
}
else
{
shouldAppend = true;
_handler = new DefaultInterpolatedStringHandler(literalLength, formattedCount);
}
}
public string ToStringAndClear() => _handler.ToStringAndClear();
public override string ToString() => _handler.ToString();
public void AppendLiteral(string value) => _handler.AppendLiteral(value);
public void AppendFormatted<T>(T value) => _handler.AppendFormatted(value);
public void AppendFormatted<T>(T value, string? format) => _handler.AppendFormatted(value, format);
}
}

View File

@@ -46,7 +46,7 @@ public sealed class AudioOverlay : Overlay
var screenHandle = args.ScreenHandle;
var output = new StringBuilder();
var listenerPos = _entManager.GetComponent<TransformComponent>(localPlayer.Value).MapPosition;
var listenerPos = _transform.GetMapCoordinates(_entManager.GetComponent<TransformComponent>(localPlayer.Value));
if (listenerPos.MapId != args.MapId)
return;

View File

@@ -37,7 +37,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
[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!;
@@ -49,9 +48,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
/// 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 float _audioFrameTime;
private float _audioFrameTimeRemaining;
private EntityQuery<PhysicsComponent> _physicsQuery;
@@ -110,9 +110,16 @@ public sealed partial class AudioSystem : SharedAudioSystem
Subs.CVar(CfgManager, CVars.AudioAttenuation, OnAudioAttenuation, true);
Subs.CVar(CfgManager, CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
Subs.CVar(CfgManager, CVars.AudioTickRate, OnAudioTickRate, true);
InitializeLimit();
}
private void OnAudioTickRate(int obj)
{
_audioFrameTime = 1f / obj;
_audioFrameTimeRemaining = MathF.Min(_audioFrameTimeRemaining, _audioFrameTime);
}
private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
{
ApplyAudioParams(component.Params, component);
@@ -254,6 +261,13 @@ public sealed partial class AudioSystem : SharedAudioSystem
public override void FrameUpdate(float frameTime)
{
_audioFrameTimeRemaining -= frameTime;
if (_audioFrameTimeRemaining > 0f)
return;
// Clamp to 0 in case we have a really long frame.
_audioFrameTimeRemaining = MathF.Max(0f, _audioFrameTime + _audioFrameTimeRemaining);
var eye = _eyeManager.CurrentEye;
var localEntity = _playerManager.LocalEntity;
Vector2 listenerVelocity;
@@ -277,9 +291,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
_streams.Add((uid, comp, xform));
}
_mapManager.TryFindGridAt(ourPos, out var gridUid, out _);
_listenerGrid = gridUid == EntityUid.Invalid ? null : gridUid;
try
{
_updateAudioJob.OurPosition = ourPos;
@@ -332,7 +343,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
Vector2 worldPos;
component.Volume = component.Params.Volume;
Vector2 delta;
// Handle grid audio differently by using grid position.
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
@@ -346,7 +356,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
// Max distance check
delta = worldPos - listener.Position;
var delta = worldPos - listener.Position;
var distance = delta.Length();
// Out of range so just clip it for us.

View File

@@ -1,24 +1,18 @@
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;
@@ -43,7 +37,7 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
get
{
_checkDisposed();
var state = AL.GetSourceState(SourceHandle!.Value);
var state = AL.GetSourceState(SourceHandle);
_master._checkAlError();
return state == ALSourceState.Playing;
}
@@ -53,7 +47,7 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
{
_checkDisposed();
// IDK why this stackallocs but gonna leave it for now.
AL.SourcePlay(stackalloc int[] {SourceHandle!.Value});
AL.SourcePlay(stackalloc int[] {SourceHandle});
_master._checkAlError();
}
else
@@ -61,7 +55,7 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
if (_isDisposed())
return;
AL.SourceStop(SourceHandle!.Value);
AL.SourceStop(SourceHandle);
_master._checkAlError();
}
}
@@ -74,13 +68,13 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
protected override void Dispose(bool disposing)
{
if (SourceHandle == null)
if (SourceHandle == -1)
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);
_master.DeleteBufferedSourceOnMainThread(SourceHandle, FilterHandle);
foreach (var handle in BufferHandles)
{
@@ -92,21 +86,21 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
if (FilterHandle != 0)
EFX.DeleteFilter(FilterHandle);
AL.DeleteSource(SourceHandle.Value);
AL.DeleteSource(SourceHandle);
AL.DeleteBuffers(BufferHandles);
_master.RemoveBufferedAudioSource(SourceHandle.Value);
_master.RemoveBufferedAudioSource(SourceHandle);
_master._checkAlError();
}
FilterHandle = 0;
SourceHandle = null;
SourceHandle = -1;
}
public int GetNumberOfBuffersProcessed()
{
_checkDisposed();
// ReSharper disable once PossibleInvalidOperationException
AL.GetSource(SourceHandle!.Value, ALGetSourcei.BuffersProcessed, out var buffersProcessed);
AL.GetSource(SourceHandle, ALGetSourcei.BuffersProcessed, out var buffersProcessed);
return buffersProcessed;
}
@@ -116,7 +110,7 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
var entries = Math.Min(Math.Min(handles.Length, BufferHandles.Length), GetNumberOfBuffersProcessed());
fixed (int* ptr = handles)
{
AL.SourceUnqueueBuffers(SourceHandle!.Value, entries, ptr);
AL.SourceUnqueueBuffers(SourceHandle, entries, ptr);
}
for (var i = 0; i < entries; i++)
@@ -183,7 +177,7 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
fixed (int* ptr = realHandles)
// ReSharper disable once PossibleInvalidOperationException
{
AL.SourceQueueBuffers(SourceHandle!.Value, handles.Length, ptr);
AL.SourceQueueBuffers(SourceHandle, handles.Length, ptr);
}
}

View File

@@ -26,6 +26,7 @@ using Robust.Client.Upload;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.RichText;
using Robust.Client.UserInterface.Themes;
using Robust.Client.UserInterface.XAML.Proxy;
using Robust.Client.Utility;
using Robust.Client.ViewVariables;
using Robust.Shared;
@@ -146,6 +147,16 @@ namespace Robust.Client
deps.Register<IConfigurationManagerInternal, ClientNetConfigurationManager>();
deps.Register<IClientNetConfigurationManager, ClientNetConfigurationManager>();
deps.Register<INetConfigurationManagerInternal, ClientNetConfigurationManager>();
#if TOOLS
deps.Register<IXamlProxyManager, XamlProxyManager>();
deps.Register<IXamlHotReloadManager, XamlHotReloadManager>();
#else
deps.Register<IXamlProxyManager, XamlProxyManagerStub>();
deps.Register<IXamlHotReloadManager, XamlHotReloadManagerStub>();
#endif
deps.Register<IXamlProxyHelper, XamlProxyHelper>();
deps.Register<MarkupTagManager>();
}
}

View File

@@ -291,9 +291,9 @@ namespace Robust.Client.Console.Commands
}
}
internal sealed class SnapGridGetCell : LocalizedCommands
internal sealed class SnapGridGetCell : LocalizedEntityCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
public override string Command => "sggcell";
@@ -319,9 +319,10 @@ namespace Robust.Client.Console.Commands
return;
}
if (_entManager.TryGetComponent<MapGridComponent>(_entManager.GetEntity(gridNet), out var grid))
var gridEnt = EntityManager.GetEntity(gridNet);
if (EntityManager.TryGetComponent<MapGridComponent>(gridEnt, out var grid))
{
foreach (var entity in grid.GetAnchoredEntities(new Vector2i(
foreach (var entity in _map.GetAnchoredEntities(gridEnt, grid, new Vector2i(
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
int.Parse(indices.Split(',')[1], CultureInfo.InvariantCulture))))
{
@@ -425,9 +426,9 @@ namespace Robust.Client.Console.Commands
}
}
internal sealed class GridTileCount : LocalizedCommands
internal sealed class GridTileCount : LocalizedEntityCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
public override string Command => "gridtc";
@@ -440,15 +441,15 @@ namespace Robust.Client.Console.Commands
}
if (!NetEntity.TryParse(args[0], out var gridUidNet) ||
!_entManager.TryGetEntity(gridUidNet, out var gridUid))
!EntityManager.TryGetEntity(gridUidNet, out var gridUid))
{
shell.WriteLine($"{args[0]} is not a valid entity UID.");
return;
}
if (_entManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
if (EntityManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
{
shell.WriteLine(grid.GetAllTiles().Count().ToString());
shell.WriteLine(_map.GetAllTiles(gridUid.Value, grid).Count().ToString());
}
else
{
@@ -578,7 +579,20 @@ namespace Robust.Client.Console.Commands
private static string GetMemberValue(MemberInfo? member, Control control, string separator, string
wrap = "{0}")
{
var value = member?.GetValue(control);
object? value = null;
try
{
value = member?.GetValue(control);
}
catch (TargetInvocationException exception)
{
var exceptionToPrint = exception.InnerException ?? exception;
value = $"{exceptionToPrint.GetType()}: {exceptionToPrint.Message}";
}
catch (Exception exception)
{
value = $"{exception.GetType()}: {exception.Message}";
}
var o = value switch
{
ICollection<Control> controls => string.Join(separator,
@@ -680,12 +694,12 @@ namespace Robust.Client.Console.Commands
}
}
internal sealed class ChunkInfoCommand : LocalizedCommands
internal sealed class ChunkInfoCommand : LocalizedEntityCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IEyeManager _eye = default!;
[Dependency] private readonly IInputManager _input = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
public override string Command => "chunkinfo";
@@ -699,8 +713,8 @@ namespace Robust.Client.Console.Commands
return;
}
var mapSystem = _entManager.System<SharedMapSystem>();
var chunkIndex = mapSystem.LocalToChunkIndices(gridUid, grid, grid.MapToGrid(mousePos));
var mapSystem = EntityManager.System<SharedMapSystem>();
var chunkIndex = mapSystem.LocalToChunkIndices(gridUid, grid, _mapSystem.MapToGrid(gridUid, mousePos));
var chunk = mapSystem.GetOrAddChunk(gridUid, grid, chunkIndex);
shell.WriteLine($"worldBounds: {mapSystem.CalcWorldAABB(gridUid, grid, chunk)} localBounds: {chunk.CachedBounds}");

View File

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

View File

@@ -1,16 +1,19 @@
using Robust.Client.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Client.Console.Commands
{
public sealed class GridChunkBBCommand : LocalizedCommands
public sealed class GridChunkBBCommand : LocalizedEntityCommands
{
[Dependency] private readonly GridChunkBoundsDebugSystem _system = default!;
public override string Command => "showchunkbb";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<GridChunkBoundsDebugSystem>().Enabled ^= true;
_system.Enabled ^= true;
}
}
}

View File

@@ -204,7 +204,7 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
private Control TabRichText()
{
var label = new RichTextLabel();
label.SetMessage(FormattedMessage.FromMarkup(Lipsum));
label.SetMessage(FormattedMessage.FromMarkupOrThrow(Lipsum));
TabContainer.SetTabTitle(label, "RichText");
return label;

View File

@@ -1,4 +1,5 @@
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
@@ -14,6 +15,8 @@ namespace Robust.Client.Debugging
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly TransformSystem _transform = default!;
private bool _debugPositions;
private bool _debugRotations;
@@ -35,7 +38,7 @@ namespace Robust.Client.Debugging
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
{
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, EntityManager));
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, EntityManager, _transform));
}
else
{
@@ -74,13 +77,15 @@ namespace Robust.Client.Debugging
{
private readonly EntityLookupSystem _lookup;
private readonly IEntityManager _entityManager;
private readonly SharedTransformSystem _transform;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(EntityLookupSystem lookup, IEntityManager entityManager)
public EntityPositionOverlay(EntityLookupSystem lookup, IEntityManager entityManager, SharedTransformSystem transform)
{
_lookup = lookup;
_entityManager = entityManager;
_transform = transform;
}
protected internal override void Draw(in OverlayDrawArgs args)
@@ -88,11 +93,10 @@ namespace Robust.Client.Debugging
const float stubLength = 0.25f;
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
foreach (var entity in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
{
var (center, worldRotation) = xformQuery.GetComponent(entity).GetWorldPositionRotation();
var (center, worldRotation) = _transform.GetWorldPositionRotation(entity);
var xLine = worldRotation.RotateVec(Vector2.UnitX);
var yLine = worldRotation.RotateVec(Vector2.UnitY);

View File

@@ -47,6 +47,7 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
@@ -78,6 +79,14 @@ namespace Robust.Client.Debugging
internal int PointCount;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly IOverlayManager _overlay = default!;
[Dependency] private readonly IEyeManager _eye = default!;
[Dependency] private readonly IInputManager _input = default!;
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
internal ContactPoint[] Points = new ContactPoint[MaxContactPoints];
@@ -89,20 +98,21 @@ namespace Robust.Client.Debugging
if (value == _flags) return;
if (_flags == PhysicsDebugFlags.None)
IoCManager.Resolve<IOverlayManager>().AddOverlay(
_overlay.AddOverlay(
new PhysicsDebugOverlay(
EntityManager,
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IInputManager>(),
IoCManager.Resolve<IMapManager>(),
IoCManager.Resolve<IPlayerManager>(),
IoCManager.Resolve<IResourceCache>(),
_eye,
_input,
_map,
_player,
_resourceCache,
this,
Get<EntityLookupSystem>(),
Get<SharedPhysicsSystem>()));
_entityLookup,
_physics,
_transform));
if (value == PhysicsDebugFlags.None)
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsDebugOverlay));
_overlay.RemoveOverlay(typeof(PhysicsDebugOverlay));
_flags = value;
}
@@ -198,6 +208,7 @@ namespace Robust.Client.Debugging
private readonly DebugPhysicsSystem _debugPhysicsSystem;
private readonly EntityLookupSystem _lookup;
private readonly SharedPhysicsSystem _physicsSystem;
private readonly SharedTransformSystem _transformSystem;
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
@@ -208,7 +219,7 @@ namespace Robust.Client.Debugging
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)
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IMapManager mapManager, IPlayerManager playerManager, IResourceCache cache, DebugPhysicsSystem system, EntityLookupSystem lookup, SharedPhysicsSystem physicsSystem, SharedTransformSystem transformSystem)
{
_entityManager = entityManager;
_eyeManager = eyeManager;
@@ -218,6 +229,7 @@ namespace Robust.Client.Debugging
_debugPhysicsSystem = system;
_lookup = lookup;
_physicsSystem = physicsSystem;
_transformSystem = transformSystem;
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
}
@@ -327,7 +339,7 @@ namespace Robust.Client.Debugging
{
if (jointComponent.JointCount == 0 ||
!_entityManager.TryGetComponent(uid, out TransformComponent? xf1) ||
!viewAABB.Contains(xf1.WorldPosition)) continue;
!viewAABB.Contains(_transformSystem.GetWorldPosition(xf1))) continue;
foreach (var (_, joint) in jointComponent.Joints)
{
@@ -517,8 +529,8 @@ namespace Robust.Client.Debugging
if (!_entityManager.TryGetComponent(joint.BodyAUid, out TransformComponent? xform1) ||
!_entityManager.TryGetComponent(joint.BodyBUid, out TransformComponent? xform2)) return;
var matrix1 = xform1.WorldMatrix;
var matrix2 = xform2.WorldMatrix;
var matrix1 = _transformSystem.GetWorldMatrix(xform1);
var matrix2 = _transformSystem.GetWorldMatrix(xform2);
var xf1 = new Vector2(matrix1.M31, matrix1.M32);
var xf2 = new Vector2(matrix2.M31, matrix2.M32);
@@ -526,8 +538,8 @@ namespace Robust.Client.Debugging
var p1 = Vector2.Transform(joint.LocalAnchorA, matrix1);
var p2 = Vector2.Transform(joint.LocalAnchorB, matrix2);
var xfa = new Transform(xf1, xform1.WorldRotation);
var xfb = new Transform(xf2, xform2.WorldRotation);
var xfa = new Transform(xf1, _transformSystem.GetWorldRotation(xform1));
var xfb = new Transform(xf2, _transformSystem.GetWorldRotation(xform2));
switch (joint)
{

View File

@@ -19,6 +19,7 @@ using Robust.Client.State;
using Robust.Client.Upload;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.RichText;
using Robust.Client.UserInterface.XAML.Proxy;
using Robust.Client.Utility;
using Robust.Client.ViewVariables;
using Robust.Client.WebViewHook;
@@ -53,6 +54,8 @@ namespace Robust.Client
[Dependency] private readonly IResourceCacheInternal _resourceCache = default!;
[Dependency] private readonly IResourceManagerInternal _resManager = default!;
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly IXamlProxyManager _xamlProxyManager = default!;
[Dependency] private readonly IXamlHotReloadManager _xamlHotReloadManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IClientNetManager _networkManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
@@ -171,6 +174,8 @@ namespace Robust.Client
_reflectionManager.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDefaultPrototypes();
_xamlProxyManager.Initialize();
_xamlHotReloadManager.Initialize();
_userInterfaceManager.Initialize();
_eyeManager.Initialize();
_entityManager.Initialize();

View File

@@ -26,6 +26,9 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
internal event Action? AfterStartup;
internal event Action? AfterShutdown;
public override void Initialize()
{
SetupNetworking();
@@ -34,6 +37,20 @@ namespace Robust.Client.GameObjects
base.Initialize();
}
public override void Startup()
{
base.Startup();
AfterStartup?.Invoke();
}
public override void Shutdown()
{
base.Shutdown();
AfterShutdown?.Invoke();
}
public override void FlushEntities()
{
// Server doesn't network deletions on client shutdown so we need to
@@ -48,16 +65,6 @@ namespace Robust.Client.GameObjects
return base.CreateEntity(prototypeName, out metadata);
}
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity, MetaDataComponent? meta)
{
base.InitializeEntity(entity, meta);
}
void IClientEntityManagerInternal.StartEntity(EntityUid entity)
{
base.StartEntity(entity);
}
/// <inheritdoc />
public override void DirtyEntity(EntityUid uid, MetaDataComponent? meta = null)
{
@@ -128,7 +135,10 @@ namespace Robust.Client.GameObjects
var sequence = _stateMan.SystemMessageDispatched(msg);
EntityNetManager?.SendSystemNetworkMessage(msg, sequence);
DebugTools.Assert(!_stateMan.IsPredictionEnabled || _gameTiming.InPrediction && _gameTiming.IsFirstTimePredicted || _client.RunLevel != ClientRunLevel.Connected);
if (!_stateMan.IsPredictionEnabled && _client.RunLevel != ClientRunLevel.SinglePlayerGame)
return;
DebugTools.Assert(_gameTiming.InPrediction && _gameTiming.IsFirstTimePredicted || _client.RunLevel == ClientRunLevel.SinglePlayerGame);
var eventArgs = new EntitySessionEventArgs(session!);
EventBus.RaiseEvent(EventSource.Local, msg);

View File

@@ -1379,7 +1379,7 @@ namespace Robust.Client.GameObjects
// TODO whenever sprite comp gets ECS'd , just make this a direct method call.
var ev = new QueueSpriteTreeUpdateEvent(entities.GetComponent<TransformComponent>(Owner));
entities.EventBus.RaiseComponentEvent(this, ref ev);
entities.EventBus.RaiseComponentEvent(Owner, this, ref ev);
}
private void QueueUpdateIsInert()
@@ -1389,7 +1389,7 @@ namespace Robust.Client.GameObjects
// TODO whenever sprite comp gets ECS'd , just make this a direct method call.
var ev = new SpriteUpdateInertEvent();
entities.EventBus.RaiseComponentEvent(this, ref ev);
entities.EventBus.RaiseComponentEvent(Owner, this, ref ev);
}
[Obsolete("Use SpriteSystem instead.")]

View File

@@ -11,6 +11,7 @@ namespace Robust.Client.GameObjects
{
private readonly List<Entity<AnimationPlayerComponent>> _activeAnimations = new();
private EntityQuery<AnimationPlayerComponent> _playerQuery;
private EntityQuery<MetaDataComponent> _metaQuery;
[Dependency] private readonly IComponentFactory _compFact = default!;
@@ -18,6 +19,7 @@ namespace Robust.Client.GameObjects
public override void Initialize()
{
base.Initialize();
_playerQuery = GetEntityQuery<AnimationPlayerComponent>();
_metaQuery = GetEntityQuery<MetaDataComponent>();
}
@@ -74,7 +76,8 @@ namespace Robust.Client.GameObjects
foreach (var key in remie)
{
component.PlayingAnimations.Remove(key);
EntityManager.EventBus.RaiseLocalEvent(uid, new AnimationCompletedEvent {Uid = uid, Key = key}, true);
var completedEvent = new AnimationCompletedEvent {Uid = uid, Key = key, Finished = true};
EntityManager.EventBus.RaiseLocalEvent(uid, completedEvent, true);
}
return false;
@@ -171,31 +174,42 @@ namespace Robust.Client.GameObjects
return component.PlayingAnimations.ContainsKey(key);
}
[Obsolete]
public void Stop(AnimationPlayerComponent component, string key)
{
component.PlayingAnimations.Remove(key);
Stop((component.Owner, component), key);
}
public void Stop(EntityUid uid, string key)
public void Stop(Entity<AnimationPlayerComponent?> entity, string key)
{
if (!TryComp<AnimationPlayerComponent>(uid, out var player))
if (!_playerQuery.Resolve(entity.Owner, ref entity.Comp, false) ||
!entity.Comp.PlayingAnimations.Remove(key))
{
return;
}
player.PlayingAnimations.Remove(key);
var completedEvent = new AnimationCompletedEvent {Uid = entity.Owner, Key = key, Finished = false};
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, completedEvent, true);
}
public void Stop(EntityUid uid, AnimationPlayerComponent? component, string key)
{
if (!Resolve(uid, ref component, false))
return;
component.PlayingAnimations.Remove(key);
Stop((uid, component), key);
}
}
/// <summary>
/// Raised whenever an animation stops, either due to running its course or being stopped manually.
/// </summary>
public sealed class AnimationCompletedEvent : EntityEventArgs
{
public EntityUid Uid { get; init; }
public string Key { get; init; } = string.Empty;
/// <summary>
/// If true, the animation finished by getting to its natural end.
/// If false, it was removed prematurely via <see cref="AnimationPlayerSystem.Stop(Robust.Client.GameObjects.AnimationPlayerComponent,string)"/> or similar overloads.
/// </summary>
public bool Finished { get; init; }
}
}

View File

@@ -2,8 +2,11 @@ using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.GameStates;
using Robust.Shared.Utility;
using Robust.Shared.Serialization.Manager;
namespace Robust.Client.GameObjects
{
@@ -11,6 +14,7 @@ namespace Robust.Client.GameObjects
public sealed class AppearanceSystem : SharedAppearanceSystem
{
private readonly Queue<(EntityUid uid, AppearanceComponent)> _queuedUpdates = new();
[Dependency] private readonly ISerializationManager _serialization = default!;
public override void Initialize()
{
@@ -74,10 +78,13 @@ namespace Robust.Client.GameObjects
foreach (var (key, value) in data)
{
object? serializationObject;
if (value.GetType().IsValueType)
newDict[key] = value;
else if (value is ICloneable cloneable)
newDict[key] = cloneable.Clone();
else if ((serializationObject = _serialization.CreateCopy(value)) != null)
newDict[key] = serializationObject;
else
throw new NotSupportedException("Invalid object in appearance data dictionary. Appearance data must be cloneable");
}

View File

@@ -6,6 +6,7 @@ using Robust.Shared.Maths;
using Robust.Shared.Utility;
using System;
using System.Collections.Generic;
using Robust.Shared.IoC;
using static Robust.Shared.GameObjects.OccluderComponent;
namespace Robust.Client.GameObjects;
@@ -20,6 +21,7 @@ namespace Robust.Client.GameObjects;
internal sealed class ClientOccluderSystem : OccluderSystem
{
private readonly HashSet<EntityUid> _dirtyEntities = new();
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
/// <inheritdoc />
public override void Initialize()
@@ -102,7 +104,8 @@ internal sealed class ClientOccluderSystem : OccluderSystem
if (occluder.Enabled && xform.Anchored && TryComp(xform.GridUid, out grid))
{
pos = grid.TileIndicesFor(xform.Coordinates);
gridId = xform.GridUid.Value;
pos = _mapSystem.TileIndicesFor(gridId, grid, xform.Coordinates);
_dirtyEntities.Add(sender);
}
else if (occluder.LastPosition != null)
@@ -117,10 +120,10 @@ internal sealed class ClientOccluderSystem : OccluderSystem
return;
}
DirtyNeighbours(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(0, 1)), query);
DirtyNeighbours(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(0, -1)), query);
DirtyNeighbours(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(1, 0)), query);
DirtyNeighbours(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(-1, 0)), query);
DirtyNeighbours(_mapSystem.GetAnchoredEntitiesEnumerator(gridId, grid, pos + new Vector2i(0, 1)), query);
DirtyNeighbours(_mapSystem.GetAnchoredEntitiesEnumerator(gridId, grid, pos + new Vector2i(0, -1)), query);
DirtyNeighbours(_mapSystem.GetAnchoredEntitiesEnumerator(gridId, grid, pos + new Vector2i(1, 0)), query);
DirtyNeighbours(_mapSystem.GetAnchoredEntitiesEnumerator(gridId, grid, pos + new Vector2i(-1, 0)), query);
}
private void DirtyNeighbours(AnchoredEntitiesEnumerator enumerator, EntityQuery<OccluderComponent> occluderQuery)
@@ -166,7 +169,7 @@ internal sealed class ClientOccluderSystem : OccluderSystem
return;
}
var tile = grid.TileIndicesFor(xform.Coordinates);
var tile = _mapSystem.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates);
// TODO: Sub to parent changes instead or something.
// DebugTools.Assert(occluder.LastPosition == null
@@ -175,16 +178,16 @@ internal sealed class ClientOccluderSystem : OccluderSystem
// dir starts at the relative effective south direction;
var dir = xform.LocalRotation.GetCardinalDir();
CheckDir(dir, OccluderDir.South, tile, occluder, grid, occluders, xforms);
CheckDir(dir, OccluderDir.South, tile, occluder, xform.GridUid.Value, grid, occluders, xforms);
dir = dir.GetClockwise90Degrees();
CheckDir(dir, OccluderDir.West, tile, occluder, grid, occluders, xforms);
CheckDir(dir, OccluderDir.West, tile, occluder, xform.GridUid.Value, grid, occluders, xforms);
dir = dir.GetClockwise90Degrees();
CheckDir(dir, OccluderDir.North, tile, occluder, grid, occluders, xforms);
CheckDir(dir, OccluderDir.North, tile, occluder, xform.GridUid.Value, grid, occluders, xforms);
dir = dir.GetClockwise90Degrees();
CheckDir(dir, OccluderDir.East, tile, occluder, grid, occluders, xforms);
CheckDir(dir, OccluderDir.East, tile, occluder, xform.GridUid.Value, grid, occluders, xforms);
}
private void CheckDir(
@@ -192,6 +195,7 @@ internal sealed class ClientOccluderSystem : OccluderSystem
OccluderDir occDir,
Vector2i tile,
OccluderComponent occluder,
EntityUid gridUid,
MapGridComponent grid,
EntityQuery<OccluderComponent> query,
EntityQuery<TransformComponent> xforms)
@@ -199,7 +203,7 @@ internal sealed class ClientOccluderSystem : OccluderSystem
if ((occluder.Occluding & occDir) != 0)
return;
foreach (var neighbor in grid.GetAnchoredEntities(tile.Offset(dir)))
foreach (var neighbor in _mapSystem.GetAnchoredEntities(gridUid, grid, tile.Offset(dir)))
{
if (!query.TryGetComponent(neighbor, out var otherOccluder) || !otherOccluder.Enabled)
continue;

View File

@@ -18,6 +18,8 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
private GridChunkBoundsOverlay? _overlay;
@@ -36,7 +38,9 @@ namespace Robust.Client.GameObjects
_overlay = new GridChunkBoundsOverlay(
EntityManager,
_eyeManager,
_mapManager);
_mapManager,
_transform,
_map);
_overlayManager.AddOverlay(_overlay);
}
@@ -56,16 +60,20 @@ namespace Robust.Client.GameObjects
private readonly IEntityManager _entityManager;
private readonly IEyeManager _eyeManager;
private readonly IMapManager _mapManager;
private readonly SharedTransformSystem _transformSystem;
private readonly SharedMapSystem _mapSystem;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private List<Entity<MapGridComponent>> _grids = new();
public GridChunkBoundsOverlay(IEntityManager entManager, IEyeManager eyeManager, IMapManager mapManager)
public GridChunkBoundsOverlay(IEntityManager entManager, IEyeManager eyeManager, IMapManager mapManager, SharedTransformSystem transformSystem, SharedMapSystem mapSystem)
{
_entityManager = entManager;
_eyeManager = eyeManager;
_mapManager = mapManager;
_transformSystem = transformSystem;
_mapSystem = mapSystem;
}
protected internal override void Draw(in OverlayDrawArgs args)
@@ -78,11 +86,11 @@ namespace Robust.Client.GameObjects
_mapManager.FindGridsIntersecting(currentMap, viewport, ref _grids);
foreach (var grid in _grids)
{
var worldMatrix = _entityManager.GetComponent<TransformComponent>(grid).WorldMatrix;
var worldMatrix = _transformSystem.GetWorldMatrix(grid);
worldHandle.SetTransform(worldMatrix);
var transform = new Transform(Vector2.Zero, Angle.Zero);
var chunkEnumerator = grid.Comp.GetMapChunks(viewport);
var chunkEnumerator = _mapSystem.GetMapChunks(grid.Owner, grid.Comp, viewport);
while (chunkEnumerator.MoveNext(out var chunk))
{

View File

@@ -196,7 +196,7 @@ namespace Robust.Client.GameObjects
wOffset = new Vector2(wX, wY);
}
var coords = EntityCoordinates.FromMap(pent, _transform.GetMapCoordinates(pent).Offset(wOffset), _transform, EntityManager);
var coords = _transform.ToCoordinates(pent, _transform.GetMapCoordinates(pent).Offset(wOffset));
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
var message = new ClientFullInputCmdMessage(_timing.CurTick,

View File

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

View File

@@ -30,10 +30,12 @@ namespace Robust.Client.GameObjects
public sealed partial class SpriteSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IEyeManager _eye = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly SharedTransformSystem _xforms = default!;
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
@@ -66,6 +68,11 @@ namespace Robust.Client.GameObjects
_sawmill = _logManager.GetSawmill("sprite");
}
public bool IsVisible(Layer layer)
{
return layer.Visible && layer.CopyToShaderParameters == null;
}
private void OnInit(EntityUid uid, SpriteComponent component, ComponentInit args)
{
// I'm not 100% this is needed, but I CBF with this ATM. Somebody kill server sprite component please.
@@ -184,7 +191,8 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Gets the specified frame for this sprite at the specified time.
/// </summary>
public Texture GetFrame(SpriteSpecifier spriteSpec, TimeSpan curTime)
/// <param name="loop">Should we clamp on the last frame and not loop</param>
public Texture GetFrame(SpriteSpecifier spriteSpec, TimeSpan curTime, bool loop = true)
{
Texture? sprite = null;
@@ -196,19 +204,29 @@ namespace Robust.Client.GameObjects
var frames = state!.GetFrames(RsiDirection.South);
var delays = state.GetDelays();
var totalDelay = delays.Sum();
var time = curTime.TotalSeconds % totalDelay;
var delaySum = 0f;
for (var i = 0; i < delays.Length; i++)
// No looping
if (!loop && curTime.TotalSeconds >= totalDelay)
{
var delay = delays[i];
delaySum += delay;
sprite = frames[^1];
}
// Loopable
else
{
var time = curTime.TotalSeconds % totalDelay;
var delaySum = 0f;
if (time > delaySum)
continue;
for (var i = 0; i < delays.Length; i++)
{
var delay = delays[i];
delaySum += delay;
sprite = frames[i];
break;
if (time > delaySum)
continue;
sprite = frames[i];
break;
}
}
sprite ??= Frame0(spriteSpec);

View File

@@ -1,8 +1,38 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Robust.Client.GameObjects;
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
{
public override void Initialize()
{
base.Initialize();
ProtoManager.PrototypesReloaded += OnProtoReload;
}
public override void Shutdown()
{
base.Shutdown();
ProtoManager.PrototypesReloaded -= OnProtoReload;
}
private void OnProtoReload(PrototypesReloadedEventArgs obj)
{
var player = Player.LocalEntity;
if (!UserQuery.TryComp(player, out var userComp))
return;
foreach (var uid in userComp.OpenInterfaces.Keys)
{
if (!UIQuery.TryComp(uid, out var uiComp))
continue;
foreach (var bui in uiComp.ClientOpenInterfaces.Values)
{
bui.OnProtoReload(obj);
}
}
}
}

View File

@@ -14,6 +14,7 @@ namespace Robust.Client.GameObjects
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly TransformSystem _transform = default!;
internal bool Enabled { get; set; }
@@ -43,7 +44,7 @@ namespace Robust.Client.GameObjects
return;
}
var screenPos = _eyeManager.WorldToScreen(EntityManager.GetComponent<TransformComponent>(player.Value).WorldPosition);
var screenPos = _eyeManager.WorldToScreen(_transform.GetWorldPosition(Transform(player.Value)));
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
_label.Visible = true;

View File

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

View File

@@ -7,9 +7,5 @@ namespace Robust.Client.GameObjects
// These methods are used by the Game State Manager.
EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata);
void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null);
void StartEntity(EntityUid entity);
}
}

View File

@@ -603,7 +603,7 @@ namespace Robust.Client.GameStates
if (compState != null)
{
var handleState = new ComponentHandleState(compState, null);
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
_entities.EventBus.RaiseComponentEvent(entity, comp, ref handleState);
}
comp.LastModifiedTick = _timing.LastRealTick;
@@ -640,7 +640,7 @@ namespace Robust.Client.GameStates
if (state != null)
{
var stateEv = new ComponentHandleState(state, null);
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
_entities.EventBus.RaiseComponentEvent(entity, comp, ref stateEv);
}
comp.ClearCreationTick(); // don't undo the re-adding.
@@ -1361,7 +1361,7 @@ namespace Robust.Client.GameStates
continue;
var handleState = new ComponentHandleState(cur, next);
bus.RaiseComponentEvent(comp, ref handleState);
bus.RaiseComponentEvent(uid, comp, ref handleState);
}
}
@@ -1516,7 +1516,7 @@ namespace Robust.Client.GameStates
continue;
var handleState = new ComponentHandleState(state, null);
_entityManager.EventBus.RaiseComponentEvent(comp, ref handleState);
_entityManager.EventBus.RaiseComponentEvent(uid, comp, ref handleState);
}
// ensure we don't have any extra components

View File

@@ -33,7 +33,7 @@ namespace Robust.Client.Graphics.Clyde
#if EXCEPTION_TOLERANCE_LOCAL
catch (Exception e)
{
Logger.ErrorS("clyde.win", $"Error dispatching window event {ev.GetType().Name}:\n{e}");
_sawmillWin.Error($"Error dispatching window event {ev.GetType().Name}:\n{e}");
}
#endif
}

View File

@@ -5,7 +5,6 @@ using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
@@ -15,8 +14,6 @@ namespace Robust.Client.Graphics.Clyde
{
internal partial class Clyde
{
[Dependency] private readonly IEntityManager _entityManager = default!;
private readonly Dictionary<EntityUid, Dictionary<Vector2i, MapChunkData>> _mapChunkData =
new();
@@ -67,7 +64,7 @@ namespace Robust.Client.Graphics.Clyde
}
var transform = _entityManager.GetComponent<TransformComponent>(mapGrid);
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
gridProgram.SetUniform(UniIModelMatrix, _transformSystem.GetWorldMatrix(transform));
var enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
while (enumerator.MoveNext(out var chunk))

View File

@@ -9,6 +9,7 @@ using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
@@ -513,7 +514,7 @@ namespace Robust.Client.Graphics.Clyde
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawLight && eye.DrawFov)
{
var mapUid = _mapManager.GetMapEntityId(eye.Position.MapId);
var mapUid = _mapSystem.GetMap(eye.Position.MapId);
if (_entityManager.GetComponent<MapComponent>(mapUid).LightingEnabled)
ApplyFovToBuffer(viewport, eye);
}

View File

@@ -203,7 +203,7 @@ namespace Robust.Client.Graphics.Clyde
return resource.ClydeHandle;
}
Logger.Warning($"Can't load shader {path}\n");
_clydeSawmill.Warning($"Can't load shader {path}\n");
return default;
}
@@ -345,13 +345,12 @@ namespace Robust.Client.Graphics.Clyde
return;
// If this map has lighting disabled, return
var mapUid = _mapManager.GetMapEntityId(mapId);
var mapUid = _mapSystem.GetMapOrInvalid(mapId);
if (!_entityManager.TryGetComponent<MapComponent>(mapUid, out var map) || !map.LightingEnabled)
{
return;
}
(PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot)[] lights;
int count;
Box2 expandedBounds;
using (_prof.Group("LightsToRender"))
@@ -541,7 +540,6 @@ namespace Robust.Client.Graphics.Clyde
Clyde clyde,
int count,
int shadowCastingCount,
TransformSystem xformSystem,
EntityQuery<TransformComponent> xforms,
Box2 worldAABB) state,
in ComponentTreeEntry<PointLightComponent> value)
@@ -554,7 +552,7 @@ namespace Robust.Client.Graphics.Clyde
return false;
var (light, transform) = value;
var (lightPos, rot) = state.xformSystem.GetWorldPositionRotation(transform, state.xforms);
var (lightPos, rot) = state.clyde._transformSystem.GetWorldPositionRotation(transform, state.xforms);
lightPos += rot.RotateVec(light.Offset);
var circle = new Circle(lightPos, light.Radius);
@@ -600,16 +598,13 @@ namespace Robust.Client.Graphics.Clyde
in Box2Rotated worldBounds,
in Box2 worldAABB)
{
var lightTreeSys = _entitySystemManager.GetEntitySystem<LightTreeSystem>();
var xformSystem = _entitySystemManager.GetEntitySystem<TransformSystem>();
// Use worldbounds for this one as we only care if the light intersects our actual bounds
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
var state = (this, count: 0, shadowCastingCount: 0, xformSystem, xforms, worldAABB);
var state = (this, count: 0, shadowCastingCount: 0, xforms, worldAABB);
foreach (var (uid, comp) in lightTreeSys.GetIntersectingTrees(map, worldAABB))
foreach (var (uid, comp) in _lightTreeSystem.GetIntersectingTrees(map, worldAABB))
{
var bounds = xformSystem.GetInvWorldMatrix(uid, xforms).TransformBox(worldBounds);
var bounds = _transformSystem.GetInvWorldMatrix(uid, xforms).TransformBox(worldBounds);
comp.Tree.QueryAabb(ref state, LightQuery, bounds);
}
@@ -941,18 +936,16 @@ namespace Robust.Client.Graphics.Clyde
var imi = 0;
var amiMax = _maxOccluders * 4;
var occluderSystem = _entitySystemManager.GetEntitySystem<OccluderSystem>();
var xformSystem = _entitySystemManager.GetEntitySystem<TransformSystem>();
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
try
{
foreach (var (uid, comp) in occluderSystem.GetIntersectingTrees(map, expandedBounds))
foreach (var (uid, comp) in _occluderSystem.GetIntersectingTrees(map, expandedBounds))
{
if (ami >= amiMax)
break;
var treeBounds = xforms.GetComponent(uid).InvWorldMatrix.TransformBox(expandedBounds);
var treeBounds = _transformSystem.GetInvWorldMatrix(uid).TransformBox(expandedBounds);
comp.Tree.QueryAabb((in ComponentTreeEntry<OccluderComponent> entry) =>
{
@@ -965,7 +958,7 @@ namespace Robust.Client.Graphics.Clyde
if (ami >= amiMax)
return false;
var worldTransform = xformSystem.GetWorldMatrix(transform, xforms);
var worldTransform = _transformSystem.GetWorldMatrix(transform, xforms);
var box = occluder.BoundingBox;
var tl = Vector2.Transform(box.TopLeft, worldTransform);

View File

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

View File

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

View File

@@ -61,12 +61,11 @@ internal partial class Clyde
var index = 0;
var added = 0;
var opts = new ParallelOptions { MaxDegreeOfParallelism = _parMan.ParallelProcessCount };
var xformSystem = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
foreach (var (treeOwner, comp) in _entitySystemManager.GetEntitySystem<SpriteTreeSystem>().GetIntersectingTrees(map, worldBounds))
foreach (var (treeOwner, comp) in _spriteTreeSystem.GetIntersectingTrees(map, worldBounds))
{
var treeXform = query.GetComponent(treeOwner);
var bounds = xformSystem.GetInvWorldMatrix(treeOwner).TransformBox(worldBounds);
var bounds = _transformSystem.GetInvWorldMatrix(treeOwner).TransformBox(worldBounds);
DebugTools.Assert(treeXform.MapUid == treeXform.ParentUid || !treeXform.ParentUid.IsValid());
treeData = treeData with

View File

@@ -0,0 +1,39 @@
using Robust.Client.ComponentTrees;
using Robust.Client.GameObjects;
namespace Robust.Client.Graphics.Clyde;
internal sealed partial class Clyde
{
// Caches entity systems required by Clyde.
private MapSystem _mapSystem = default!;
private LightTreeSystem _lightTreeSystem = default!;
private TransformSystem _transformSystem = default!;
private SpriteTreeSystem _spriteTreeSystem = default!;
private ClientOccluderSystem _occluderSystem = default!;
private void InitSystems()
{
_entityManager.AfterStartup += EntityManagerOnAfterStartup;
_entityManager.AfterShutdown += EntityManagerOnAfterShutdown;
}
private void EntityManagerOnAfterStartup()
{
_mapSystem = _entitySystemManager.GetEntitySystem<MapSystem>();
_lightTreeSystem = _entitySystemManager.GetEntitySystem<LightTreeSystem>();
_transformSystem = _entitySystemManager.GetEntitySystem<TransformSystem>();
_spriteTreeSystem = _entitySystemManager.GetEntitySystem<SpriteTreeSystem>();
_occluderSystem = _entitySystemManager.GetEntitySystem<ClientOccluderSystem>();
}
private void EntityManagerOnAfterShutdown()
{
_mapSystem = null!;
_lightTreeSystem = null!;
_transformSystem = null!;
_spriteTreeSystem = null!;
_occluderSystem = null!;
}
}

View File

@@ -188,7 +188,7 @@ namespace Robust.Client.Graphics.Clyde
{
if (!TryInitMainWindow(glSpec, out lastError))
{
Logger.DebugS("clyde.win", $"OpenGL {glSpec.OpenGLVersion} unsupported: {lastError}");
_sawmillWin.Debug($"OpenGL {glSpec.OpenGLVersion} unsupported: {lastError}");
continue;
}
@@ -199,7 +199,7 @@ namespace Robust.Client.Graphics.Clyde
else
{
if (!TryInitMainWindow(null, out lastError))
Logger.DebugS("clyde.win", $"Failed to create window: {lastError}");
_sawmillWin.Debug($"Failed to create window: {lastError}");
else
succeeded = true;
}
@@ -230,8 +230,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
Logger.FatalS("clyde.win",
"Failed to create main game window! " +
_sawmillWin.Fatal("Failed to create main game window! " +
"This probably means your GPU is too old to run the game. " +
$"That or update your graphics drivers. {lastError}");

View File

@@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
using System.Threading;
using OpenToolkit;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Client.Map;
using Robust.Client.ResourceManagement;
@@ -46,6 +47,7 @@ namespace Robust.Client.Graphics.Clyde
[Dependency] private readonly IDependencyCollection _deps = default!;
[Dependency] private readonly ILocalizationManager _loc = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly ClientEntityManager _entityManager = default!;
private GLUniformBuffer<ProjViewMatrices> ProjViewUBO = default!;
private GLUniformBuffer<UniformConstants> UniformConstantsUBO = default!;
@@ -78,6 +80,7 @@ namespace Robust.Client.Graphics.Clyde
private ISawmill _clydeSawmill = default!;
private ISawmill _sawmillOgl = default!;
private ISawmill _sawmillWin = default!;
private IBindingsContext _glBindingsContext = default!;
private bool _earlyGLInit;
@@ -94,6 +97,7 @@ namespace Robust.Client.Graphics.Clyde
{
_clydeSawmill = _logManager.GetSawmill("clyde");
_sawmillOgl = _logManager.GetSawmill("clyde.ogl");
_sawmillWin = _logManager.GetSawmill("clyde.win");
_cfg.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
_cfg.OnValueChanged(CVars.DisplayVSync, VSyncChanged, true);
@@ -122,6 +126,8 @@ namespace Robust.Client.Graphics.Clyde
{
_gameThread = Thread.CurrentThread;
InitSystems();
InitGLContextManager();
if (!InitMainWindowAndRenderer())
return false;

View File

@@ -361,6 +361,10 @@ namespace Robust.Client.Graphics.Clyde
{
}
private protected override void SetParameterImpl(string name, bool[] value)
{
}
private protected override void SetParameterImpl(string name, in Matrix3x2 value)
{
}

View File

@@ -37,6 +37,8 @@ namespace Robust.Client.Graphics.Clyde
// NOTE: This class only handles GLES3/D3D11.
// For anything lower we just let ANGLE fall back and do the work 100%.
private readonly ISawmill _sawmill;
private IDXGIFactory1* _factory;
private IDXGIAdapter1* _adapter;
private ID3D11Device* _device;
@@ -58,6 +60,7 @@ namespace Robust.Client.Graphics.Clyde
public GLContextAngle(Clyde clyde) : base(clyde)
{
_sawmill = clyde._logManager.GetSawmill("clyde.ogl.angle");
}
public override GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version)
@@ -187,7 +190,7 @@ namespace Robust.Client.Graphics.Clyde
}
catch (Exception e)
{
Logger.ErrorS("clyde.ogl.angle", $"Failed to initialize custom ANGLE: {e}");
_sawmill.Error($"Failed to initialize custom ANGLE: {e}");
Shutdown();
return false;
}
@@ -207,7 +210,7 @@ namespace Robust.Client.Graphics.Clyde
private void TryInitializeCore()
{
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(null, EGL_EXTENSIONS));
Logger.DebugS("clyde.ogl.angle", $"EGL client extensions: {extensions}!");
_sawmill.Debug($"EGL client extensions: {extensions}!");
CreateD3D11Device();
CreateEglContext();
@@ -232,10 +235,10 @@ namespace Robust.Client.Graphics.Clyde
var version = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VERSION));
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_EXTENSIONS));
Logger.DebugS("clyde.ogl.angle", "EGL initialized!");
Logger.DebugS("clyde.ogl.angle", $"EGL vendor: {vendor}!");
Logger.DebugS("clyde.ogl.angle", $"EGL version: {version}!");
Logger.DebugS("clyde.ogl.angle", $"EGL extensions: {extensions}!");
_sawmill.Debug("EGL initialized!");
_sawmill.Debug($"EGL vendor: {vendor}!");
_sawmill.Debug($"EGL version: {version}!");
_sawmill.Debug($"EGL extensions: {extensions}!");
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE)
throw new Exception("eglBindAPI failed.");
@@ -262,11 +265,11 @@ namespace Robust.Client.Graphics.Clyde
if (numConfigs == 0)
throw new Exception("No compatible EGL configurations returned!");
Logger.DebugS("clyde.ogl.angle", $"{numConfigs} EGL configs possible!");
_sawmill.Debug($"{numConfigs} EGL configs possible!");
for (var i = 0; i < numConfigs; i++)
{
Logger.DebugS("clyde.ogl.angle", DumpEglConfig(_eglDisplay, configs[i]));
_sawmill.Debug(DumpEglConfig(_eglDisplay, configs[i]));
}
_eglConfig = configs[0];
@@ -286,7 +289,7 @@ namespace Robust.Client.Graphics.Clyde
if (_eglContext == (void*) EGL_NO_CONTEXT)
throw new Exception("eglCreateContext failed!");
Logger.DebugS("clyde.ogl.angle", "EGL context created!");
_sawmill.Debug("EGL context created!");
Clyde._openGLVersion = _es3 ? RendererOpenGLVersion.GLES3 : RendererOpenGLVersion.GLES2;
}
@@ -311,11 +314,10 @@ namespace Robust.Client.Graphics.Clyde
if (_adapter == null)
{
Logger.WarningS("clyde.ogl.angle",
$"Unable to find display adapter with requested name: {adapterName}");
_sawmill.Warning($"Unable to find display adapter with requested name: {adapterName}");
}
Logger.DebugS("clyde.ogl.angle", $"Found display adapter with name: {adapterName}");
_sawmill.Debug($"Found display adapter with name: {adapterName}");
}
#pragma warning disable CA1416
@@ -415,9 +417,9 @@ namespace Robust.Client.Graphics.Clyde
var descName = ((ReadOnlySpan<char>)desc.Description).TrimEnd('\0');
Logger.DebugS("clyde.ogl.angle", "Successfully created D3D11 device!");
Logger.DebugS("clyde.ogl.angle", $"D3D11 Device Adapter: {descName.ToString()}");
Logger.DebugS("clyde.ogl.angle", $"D3D11 Device FL: {_deviceFl}");
_sawmill.Debug("Successfully created D3D11 device!");
_sawmill.Debug($"D3D11 Device Adapter: {descName.ToString()}");
_sawmill.Debug($"D3D11 Device FL: {_deviceFl}");
if (_deviceFl == D3D_FEATURE_LEVEL_9_1)
{

View File

@@ -22,6 +22,8 @@ namespace Robust.Client.Graphics.Clyde
private readonly Dictionary<WindowId, WindowData> _windowData = new();
private readonly ISawmill _sawmill;
private void* _eglDisplay;
private void* _eglContext;
private void* _eglConfig;
@@ -30,6 +32,7 @@ namespace Robust.Client.Graphics.Clyde
public GLContextEgl(Clyde clyde) : base(clyde)
{
_sawmill = clyde._logManager.GetSawmill("clyde.ogl.egl");
}
public override GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version)
@@ -47,7 +50,7 @@ namespace Robust.Client.Graphics.Clyde
public void InitializePublic()
{
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(null, EGL_EXTENSIONS));
Logger.DebugS("clyde.ogl.egl", $"EGL client extensions: {extensions}!");
_sawmill.Debug($"EGL client extensions: {extensions}!");
}
public override void WindowCreated(GLContextSpec? spec, WindowReg reg)
@@ -133,10 +136,10 @@ namespace Robust.Client.Graphics.Clyde
var version = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VERSION));
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_EXTENSIONS));
Logger.DebugS("clyde.ogl.egl", "EGL initialized!");
Logger.DebugS("clyde.ogl.egl", $"EGL vendor: {vendor}!");
Logger.DebugS("clyde.ogl.egl", $"EGL version: {version}!");
Logger.DebugS("clyde.ogl.egl", $"EGL extensions: {extensions}!");
_sawmill.Debug("EGL initialized!");
_sawmill.Debug($"EGL vendor: {vendor}!");
_sawmill.Debug($"EGL version: {version}!");
_sawmill.Debug($"EGL extensions: {extensions}!");
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE)
throw new Exception("eglBindAPI failed.");
@@ -164,11 +167,11 @@ namespace Robust.Client.Graphics.Clyde
if (numConfigs == 0)
throw new Exception("No compatible EGL configurations returned!");
Logger.DebugS("clyde.ogl.egl", $"{numConfigs} EGL configs possible!");
_sawmill.Debug($"{numConfigs} EGL configs possible!");
for (var i = 0; i < numConfigs; i++)
{
Logger.DebugS("clyde.ogl.egl", DumpEglConfig(_eglDisplay, configs[i]));
_sawmill.Debug(DumpEglConfig(_eglDisplay, configs[i]));
}
_eglConfig = configs[0];
@@ -183,7 +186,7 @@ namespace Robust.Client.Graphics.Clyde
if (_eglContext == (void*) EGL_NO_CONTEXT)
throw new Exception("eglCreateContext failed!");
Logger.DebugS("clyde.ogl.egl", "EGL context created!");
_sawmill.Debug("EGL context created!");
}
public override void Shutdown()

View File

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

View File

@@ -22,7 +22,7 @@ namespace Robust.Client.Graphics
/// If set to true, <see cref="ScreenTexture"/> will be set to the current frame (at the moment before the overlay is rendered). This can be costly to performance, but
/// some shaders will require it as a passed in uniform to operate.
/// </summary>
public virtual bool RequestScreenTexture => false;
public virtual bool RequestScreenTexture { get; set; } = false;
/// <summary>
/// If <see cref="RequestScreenTexture"> is true, then this will be set to the texture corresponding to the current frame. If false, it will always be null.

View File

@@ -224,7 +224,8 @@ namespace Robust.Client.Graphics
// TODO: add support for int, and vec3/4 arrays
return
(type == ShaderDataType.Float) ||
(type == ShaderDataType.Vec2);
(type == ShaderDataType.Vec2) ||
(type == ShaderDataType.Bool);
}
[SuppressMessage("ReSharper", "StringLiteralTypo")]

View File

@@ -148,6 +148,13 @@ namespace Robust.Client.Graphics
SetParameterImpl(name, value);
}
public void SetParameter(string name, bool[] value)
{
EnsureAlive();
EnsureMutable();
SetParameterImpl(name, value);
}
public void SetParameter(string name, in Matrix3x2 value)
{
EnsureAlive();
@@ -219,6 +226,7 @@ namespace Robust.Client.Graphics
private protected abstract void SetParameterImpl(string name, int value);
private protected abstract void SetParameterImpl(string name, Vector2i value);
private protected abstract void SetParameterImpl(string name, bool value);
private protected abstract void SetParameterImpl(string name, bool[] value);
private protected abstract void SetParameterImpl(string name, in Matrix3x2 value);
private protected abstract void SetParameterImpl(string name, in Matrix4 value);
private protected abstract void SetParameterImpl(string name, Texture value);

View File

@@ -21,9 +21,12 @@ using SixLabors.ImageSharp.PixelFormats;
namespace Robust.Client.Map
{
internal sealed class ClydeTileDefinitionManager : TileDefinitionManager, IClydeTileDefinitionManager
internal sealed class ClydeTileDefinitionManager : TileDefinitionManager, IClydeTileDefinitionManager, IPostInjectInit
{
[Dependency] private readonly IResourceManager _manager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private ISawmill _sawmill = default!;
private Texture? _tileTextureAtlas;
@@ -98,8 +101,7 @@ namespace Robust.Client.Map
if (imgWidth >= 2048 || imgHeight >= 2048)
{
// Sanity warning, some machines don't have textures larger than this and need multiple atlases.
Logger.WarningS("clyde",
$"Tile texture atlas is ({imgWidth} x {imgHeight}), larger than 2048 x 2048. If you really need {tileCount} tiles, file an issue on RobustToolbox.");
_sawmill.Warning($"Tile texture atlas is ({imgWidth} x {imgHeight}), larger than 2048 x 2048. If you really need {tileCount} tiles, file an issue on RobustToolbox.");
}
var column = 1;
@@ -151,6 +153,11 @@ namespace Robust.Client.Map
_tileTextureAtlas = Texture.LoadFromImage(sheet, "Tile Atlas");
}
void IPostInjectInit.PostInject()
{
_sawmill = _logManager.GetSawmill("clyde");
}
}
public sealed class ReloadTileTexturesCommand : LocalizedCommands

View File

@@ -34,13 +34,13 @@ namespace Robust.Client.Placement.Modes
{
var from = ScreenToWorld(new Vector2(a, 0));
var to = ScreenToWorld(new Vector2(a, viewportSize.Y));
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 1f));
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 0.3f));
}
for (var a = gridstart.Y; a < viewportSize.Y; a += SnapSize * EyeManager.PixelsPerMeter)
{
var from = ScreenToWorld(new Vector2(0, a));
var to = ScreenToWorld(new Vector2(viewportSize.X, a));
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 1f));
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 0.3f));
}
}

View File

@@ -167,7 +167,6 @@ namespace Robust.Client.Player
if (_client.RunLevel != ClientRunLevel.SinglePlayerGame)
Sawmill.Warning($"Attaching local player to an entity {EntManager.ToPrettyString(uid)} without an eye. This eye will not be netsynced and may cause issues.");
var eye = (EyeComponent) Factory.GetComponent(typeof(EyeComponent));
eye.Owner = uid.Value;
eye.NetSyncEnabled = false;
EntManager.AddComponent(uid.Value, eye);
}

View File

@@ -162,9 +162,7 @@ public sealed partial class ReplayLoadManager
}
using var stringFile = fileReader.Open(FileStrings);
var stringData = new byte[stringFile.Length];
stringFile.ReadExactly(stringData);
_serializer.SetStringSerializerPackage(stringHash, stringData);
_serializer.SetStringSerializerPackage(stringHash, stringFile.CopyToArray());
using var cvarsFile = fileReader.Open(FileCvars);
// Note, this does not invoke the received-initial-cvars event. But at least currently, that doesn't matter

View File

@@ -47,7 +47,7 @@ namespace Robust.Client.ResourceManagement
{
sawmill.Debug("Preloading textures...");
var sw = Stopwatch.StartNew();
var resList = GetTypeDict<TextureResource>();
var resList = GetTypeData<TextureResource>().Resources;
var texList = _manager.ContentFindFiles("/Textures/")
// Skip PNG files inside RSIs.
@@ -119,7 +119,7 @@ namespace Robust.Client.ResourceManagement
private void PreloadRsis(ISawmill sawmill)
{
var sw = Stopwatch.StartNew();
var resList = GetTypeDict<RSIResource>();
var resList = GetTypeData<RSIResource>().Resources;
var rsiList = _manager.ContentFindFiles("/Textures/")
.Where(p => p.ToString().EndsWith(".rsi/meta.json"))

View File

@@ -17,9 +17,7 @@ namespace Robust.Client.ResourceManagement;
/// </summary>
internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInternal, IDisposable
{
private readonly Dictionary<Type, Dictionary<ResPath, BaseResource>> _cachedResources =
new();
private readonly Dictionary<Type, TypeData> _cachedResources = new();
private readonly Dictionary<Type, BaseResource> _fallbacks = new();
public T GetResource<T>(string path, bool useFallback = true) where T : BaseResource, new()
@@ -29,8 +27,8 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
public T GetResource<T>(ResPath path, bool useFallback = true) where T : BaseResource, new()
{
var cache = GetTypeDict<T>();
if (cache.TryGetValue(path, out var cached))
var cache = GetTypeData<T>();
if (cache.Resources.TryGetValue(path, out var cached))
{
return (T) cached;
}
@@ -40,7 +38,7 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
{
var dependencies = IoCManager.Instance!;
resource.Load(dependencies, path);
cache[path] = resource;
cache.Resources[path] = resource;
return resource;
}
catch (Exception e)
@@ -67,24 +65,31 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
public bool TryGetResource<T>(ResPath path, [NotNullWhen(true)] out T? resource) where T : BaseResource, new()
{
var cache = GetTypeDict<T>();
if (cache.TryGetValue(path, out var cached))
var cache = GetTypeData<T>();
if (cache.Resources.TryGetValue(path, out var cached))
{
resource = (T) cached;
return true;
}
if (cache.NonExistent.Contains(path))
{
resource = null;
return false;
}
var _resource = new T();
try
{
var dependencies = IoCManager.Instance!;
_resource.Load(dependencies, path);
resource = _resource;
cache[path] = resource;
cache.Resources[path] = resource;
return true;
}
catch (FileNotFoundException)
{
cache.NonExistent.Add(path);
resource = null;
return false;
}
@@ -109,9 +114,9 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
public void ReloadResource<T>(ResPath path) where T : BaseResource, new()
{
var cache = GetTypeDict<T>();
var cache = GetTypeData<T>();
if (!cache.TryGetValue(path, out var res))
if (!cache.Resources.TryGetValue(path, out var res))
{
return;
}
@@ -145,7 +150,7 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
public void CacheResource<T>(ResPath path, T resource) where T : BaseResource, new()
{
GetTypeDict<T>()[path] = resource;
GetTypeData<T>().Resources[path] = resource;
}
public T GetFallback<T>() where T : BaseResource, new()
@@ -168,7 +173,7 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
public IEnumerable<KeyValuePair<ResPath, T>> GetAllResources<T>() where T : BaseResource, new()
{
return GetTypeDict<T>().Select(p => new KeyValuePair<ResPath, T>(p.Key, (T) p.Value));
return GetTypeData<T>().Resources.Select(p => new KeyValuePair<ResPath, T>(p.Key, (T) p.Value));
}
public event Action<TextureLoadedEventArgs>? OnRawTextureLoaded;
@@ -193,7 +198,7 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
if (disposing)
{
foreach (var res in _cachedResources.Values.SelectMany(dict => dict.Values))
foreach (var res in _cachedResources.Values.SelectMany(dict => dict.Resources.Values))
{
res.Dispose();
}
@@ -210,15 +215,9 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
#endregion IDisposable Members
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Dictionary<ResPath, BaseResource> GetTypeDict<T>()
private TypeData GetTypeData<T>()
{
if (!_cachedResources.TryGetValue(typeof(T), out var ret))
{
ret = new Dictionary<ResPath, BaseResource>();
_cachedResources.Add(typeof(T), ret);
}
return ret;
return _cachedResources.GetOrNew(typeof(T));
}
public void TextureLoaded(TextureLoadedEventArgs eventArgs)
@@ -230,4 +229,13 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
{
OnRsiLoaded?.Invoke(eventArgs);
}
private sealed class TypeData
{
public readonly Dictionary<ResPath, BaseResource> Resources = new();
// List of resources which DON'T exist.
// Needed to avoid innocuous TryGet calls repeatedly trying to re-load non-existent resources from disk.
public readonly HashSet<ResPath> NonExistent = new();
}
}

View File

@@ -44,6 +44,10 @@
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(RobustToolsBuild)' == 'True'">
<ProjectReference Include="..\Robust.Xaml\Robust.Xaml.csproj" />
</ItemGroup>
<!-- Shader embedding -->
<ItemGroup>
<EmbeddedResource Include="Graphics\Clyde\Shaders\*" />

View File

@@ -0,0 +1,33 @@
using System;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects;
namespace Robust.Client.UserInterface;
public static class BoundUserInterfaceExt
{
/// <summary>
/// Helper method to create a window and also handle closing the BUI when it's closed.
/// </summary>
public static T CreateWindow<T>(this BoundUserInterface bui) where T : BaseWindow, new()
{
var window = bui.CreateDisposableControl<T>();
window.OpenCentered();
window.OnClose += bui.Close;
return window;
}
/// <summary>
/// Creates a control for this BUI that will be disposed when it is disposed.
/// </summary>
/// <param name="bui"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T CreateDisposableControl<T>(this BoundUserInterface bui) where T : Control, IDisposable, new()
{
var control = new T();
bui.Disposals ??= [];
bui.Disposals.Add(control);
return control;
}
}

View File

@@ -212,9 +212,18 @@ namespace Robust.Client.UserInterface
}
}
/// <summary>
/// Called when this control's visibility in the control tree changed.
/// </summary>
protected virtual void VisibilityChanged(bool newVisible)
{
}
private void _propagateVisibilityChanged(bool newVisible)
{
VisibilityChanged(newVisible);
OnVisibilityChanged?.Invoke(this);
if (!VisibleInTree)
{
UserInterfaceManagerInternal.ControlHidden(this);
@@ -593,6 +602,7 @@ namespace Robust.Client.UserInterface
/// Dispose this control, its own scene control, and all its children.
/// Basically the big delete button.
/// </summary>
[Obsolete("Controls should only be removed from UI tree instead of being disposed")]
public void Dispose()
{
if (Disposed)
@@ -604,6 +614,7 @@ namespace Robust.Client.UserInterface
Disposed = true;
}
[Obsolete("Controls should only be removed from UI tree instead of being disposed")]
protected virtual void Dispose(bool disposing)
{
if (!disposing)

View File

@@ -327,8 +327,7 @@ public sealed class EntitySpawningUIController : UIController
if (_window == null || _window.Disposed)
return;
var textures = SpriteComponent.GetPrototypeTextures(prototype, _resources).Select(o => o.Default).ToList();
var button = _window.InsertEntityButton(prototype, insertFirst, index, textures);
var button = _window.InsertEntityButton(prototype, insertFirst, index);
button.ActualButton.OnToggled += OnEntityButtonToggled;
}

View File

@@ -15,6 +15,36 @@ namespace Robust.Client.UserInterface.Controls
public Label Label { get; }
public TextureRect TextureRect { get; }
/// <summary>
/// Should the checkbox be to the left or the right of the label.
/// </summary>
public bool LeftAlign
{
get => _leftAlign;
set
{
if (_leftAlign == value)
return;
_leftAlign = value;
if (value)
{
Label.HorizontalExpand = false;
TextureRect.SetPositionFirst();
Label.SetPositionInParent(1);
}
else
{
Label.HorizontalExpand = true;
Label.SetPositionFirst();
TextureRect.SetPositionInParent(1);
}
}
}
private bool _leftAlign = true;
public CheckBox()
{
ToggleMode = true;
@@ -31,10 +61,21 @@ namespace Robust.Client.UserInterface.Controls
StyleClasses = { StyleClassCheckBox },
VerticalAlignment = VAlignment.Center,
};
hBox.AddChild(TextureRect);
Label = new Label();
hBox.AddChild(Label);
if (LeftAlign)
{
Label.HorizontalExpand = false;
hBox.AddChild(TextureRect);
hBox.AddChild(Label);
}
else
{
Label.HorizontalExpand = true;
hBox.AddChild(Label);
hBox.AddChild(TextureRect);
}
}
protected override void DrawModeChanged()

View File

@@ -15,6 +15,8 @@ namespace Robust.Client.UserInterface.Controls
public const string StylePseudoClassHover = "hover";
public const string StylePseudoClassDisabled = "disabled";
public StyleBox? StyleBoxOverride { get; set; }
public ContainerButton()
{
DrawModeChanged();
@@ -24,6 +26,11 @@ namespace Robust.Client.UserInterface.Controls
{
get
{
if (StyleBoxOverride != null)
{
return StyleBoxOverride;
}
if (TryGetStyleProperty<StyleBox>(StylePropertyStyleBox, out var box))
{
return box;

View File

@@ -33,10 +33,7 @@ public class EntityPrototypeView : SpriteView
_currentPrototype = entProto;
SetEntity(null);
if (_ourEntity != null)
{
EntMan.DeleteEntity(_ourEntity);
}
EntMan.DeleteEntity(_ourEntity);
if (_currentPrototype != null)
{
@@ -57,8 +54,6 @@ public class EntityPrototypeView : SpriteView
protected override void ExitedTree()
{
base.ExitedTree();
if (!EntMan.Deleted(_ourEntity))
EntMan.QueueDeleteEntity(_ourEntity);
EntMan.TryQueueDeleteEntity(_ourEntity);
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Numerics;
using Robust.Client.Graphics;
@@ -14,6 +14,7 @@ namespace Robust.Client.UserInterface.Controls
public const string StyleClassOptionButton = "optionButton";
public const string StyleClassPopup = "optionButtonPopup";
public const string StyleClassOptionTriangle = "optionTriangle";
public const string StyleClassOptionsBackground = "optionButtonBackground";
public readonly ScrollContainer OptionsScroll;
private readonly List<ButtonData> _buttonData = new();
@@ -75,7 +76,12 @@ namespace Robust.Client.UserInterface.Controls
_popup = new Popup()
{
Children = { new PanelContainer(), OptionsScroll },
Children = {
new PanelContainer {
StyleClasses = { StyleClassOptionsBackground }
},
OptionsScroll
},
StyleClasses = { StyleClassPopup }
};
_popup.OnPopupHide += OnPopupHide;

View File

@@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.InteropServices;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.RichText;
using Robust.Shared.Collections;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -20,7 +19,7 @@ namespace Robust.Client.UserInterface.Controls
public const string StylePropertyStyleBox = "stylebox";
private readonly List<RichTextEntry> _entries = new();
private readonly RingBufferList<RichTextEntry> _entries = new();
private bool _isAtBottom = true;
private int _totalContentHeight;
@@ -30,6 +29,8 @@ namespace Robust.Client.UserInterface.Controls
public bool ScrollFollowing { get; set; } = true;
private bool _invalidOnVisible;
public OutputPanel()
{
IoCManager.InjectDependencies(this);
@@ -45,6 +46,8 @@ namespace Robust.Client.UserInterface.Controls
_scrollBar.OnValueChanged += _ => _isAtBottom = _scrollBar.IsAtEnd;
}
public int EntryCount => _entries.Count;
public StyleBox? StyleBoxOverride
{
get => _styleBoxOverride;
@@ -91,7 +94,7 @@ namespace Robust.Client.UserInterface.Controls
{
var entry = new RichTextEntry(message, this, _tagManager, null);
entry.Update(_getFont(), _getContentBox().Width, UIScale);
entry.Update(_tagManager, _getFont(), _getContentBox().Width, UIScale);
_entries.Add(entry);
var font = _getFont();
@@ -134,7 +137,7 @@ namespace Robust.Client.UserInterface.Controls
// So when a new color tag gets hit this stack gets the previous color pushed on.
var context = new MarkupDrawingContext(2);
foreach (ref var entry in CollectionsMarshal.AsSpan(_entries))
foreach (ref var entry in _entries)
{
if (entryOffset + entry.Height < 0)
{
@@ -147,7 +150,7 @@ namespace Robust.Client.UserInterface.Controls
break;
}
entry.Draw(handle, font, contentBox, entryOffset, context, UIScale);
entry.Draw(_tagManager, handle, font, contentBox, entryOffset, context, UIScale);
entryOffset += entry.Height + font.GetLineSeparation(UIScale);
}
@@ -185,9 +188,9 @@ namespace Robust.Client.UserInterface.Controls
_totalContentHeight = 0;
var font = _getFont();
var sizeX = _getContentBox().Width;
foreach (ref var entry in CollectionsMarshal.AsSpan(_entries))
foreach (ref var entry in _entries)
{
entry.Update(font, sizeX, UIScale);
entry.Update(_tagManager, font, sizeX, UIScale);
_totalContentHeight += entry.Height + font.GetLineSeparation(UIScale);
}
@@ -239,7 +242,13 @@ namespace Robust.Client.UserInterface.Controls
protected internal override void UIScaleChanged()
{
_invalidateEntries();
// If this control isn't visible, don't invalidate entries immediately.
// This saves invalidating the debug console if it's hidden,
// which is a huge boon as auto-scaling changes UI scale a lot in that scenario.
if (!VisibleInTree)
_invalidOnVisible = true;
else
_invalidateEntries();
base.UIScaleChanged();
}
@@ -257,5 +266,14 @@ namespace Robust.Client.UserInterface.Controls
// existing ones were valid when the UI scale was set.
_invalidateEntries();
}
protected override void VisibilityChanged(bool newVisible)
{
if (newVisible && _invalidOnVisible)
{
_invalidateEntries();
_invalidOnVisible = false;
}
}
}
}

View File

@@ -38,6 +38,21 @@ namespace Robust.Client.UserInterface.Controls
}
}
public string? Text
{
get => _message?.ToMarkup();
set
{
if (value == null)
{
_message?.Clear();
return;
}
SetMessage(FormattedMessage.FromMarkupPermissive(value));
}
}
public RichTextLabel()
{
IoCManager.InjectDependencies(this);
@@ -68,7 +83,7 @@ namespace Robust.Client.UserInterface.Controls
}
var font = _getFont();
_entry.Update(font, availableSize.X * UIScale, UIScale, LineHeightScale);
_entry.Update(_tagManager, font, availableSize.X * UIScale, UIScale, LineHeightScale);
return new Vector2(_entry.Width / UIScale, _entry.Height / UIScale);
}
@@ -82,7 +97,7 @@ namespace Robust.Client.UserInterface.Controls
return;
}
_entry.Draw(handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale, LineHeightScale);
_entry.Draw(_tagManager, handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale, LineHeightScale);
}
[Pure]

View File

@@ -1,4 +1,3 @@
using Robust.Shared.Maths;
using System;
using System.Collections.Generic;
using System.Numerics;
@@ -15,9 +14,10 @@ namespace Robust.Client.UserInterface.Controls
public const string RightButtonStyle = "spinbox-right";
public const string MiddleButtonStyle = "spinbox-middle";
public LineEdit LineEditControl { get; }
private List<Button> _leftButtons = new();
private List<Button> _rightButtons = new();
private List<SpinBoxButton> _leftButtons = new();
private List<SpinBoxButton> _rightButtons = new();
private int _stepSize = 1;
private bool _buttonsDisabled;
/// <summary>
/// Determines whether the SpinBox value gets changed by the input text.
@@ -30,12 +30,7 @@ namespace Robust.Client.UserInterface.Controls
get => _value;
set
{
if (IsValid != null && !IsValid(value))
{
return;
}
_value = value;
LineEditControl.Text = value.ToString();
OverrideValue(value);
ValueChanged?.Invoke(new ValueChangedEventArgs(value));
}
}
@@ -52,6 +47,7 @@ namespace Robust.Client.UserInterface.Controls
return;
}
_value = value;
UpdateButtonCanPress();
LineEditControl.Text = value.ToString();
}
@@ -87,6 +83,7 @@ namespace Robust.Client.UserInterface.Controls
ClearButtons();
AddLeftButton(-1, "-");
AddRightButton(1, "+");
UpdateButtonCanPress();
}
/// <summary>
@@ -94,8 +91,8 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
public void AddRightButton(int num, string text)
{
var button = new Button { Text = text };
button.OnPressed += (args) => Value += num;
var button = new SpinBoxButton(num) { Text = text };
button.OnPressed += _ => Value += num;
AddChild(button);
button.AddStyleClass(RightButtonStyle);
if (_rightButtons.Count > 0)
@@ -111,8 +108,8 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
public void AddLeftButton(int num, string text)
{
var button = new Button { Text = text };
button.OnPressed += (args) => Value += num;
var button = new SpinBoxButton(num) { Text = text };
button.OnPressed += _ => Value += num;
AddChild(button);
button.SetPositionInParent(_leftButtons.Count);
button.AddStyleClass(_leftButtons.Count == 0 ? LeftButtonStyle : MiddleButtonStyle);
@@ -162,6 +159,24 @@ namespace Robust.Client.UserInterface.Controls
{
rightButton.Disabled = disabled;
}
_buttonsDisabled = disabled;
}
private void UpdateButtonCanPress()
{
if (IsValid == null)
return;
foreach (var button in _leftButtons)
{
button.Disabled = !IsValid(_value + button.Value) || _buttonsDisabled;
}
foreach (var button in _rightButtons)
{
button.Disabled = !IsValid(_value + button.Value) || _buttonsDisabled;
}
}
/// <summary>
@@ -195,6 +210,16 @@ namespace Robust.Client.UserInterface.Controls
else if (args.Delta.Y < 0)
Value -= _stepSize;
}
private sealed class SpinBoxButton : Button
{
public readonly int Value;
public SpinBoxButton(int value)
{
Value = value;
}
}
}
public sealed class ValueChangedEventArgs : EventArgs

View File

@@ -191,6 +191,19 @@ namespace Robust.Client.UserInterface.Controls
_splitDragArea.OnMouseMove += OnMove;
}
/// <summary>
/// Swaps the position of the first and zeroeth children; for a 2-control viewport it effectively flips them.
/// </summary>
public void Flip()
{
if (ChildCount < 3)
return;
DebugTools.Assert(ChildCount <= 3);
GetChild(1).SetPositionFirst();
InvalidateArrange();
}
private void OnMove(GUIMouseMoveEventArgs args)
{
if (ResizeMode == SplitResizeMode.NotResizable)

View File

@@ -65,6 +65,10 @@ namespace Robust.Client.UserInterface.Controls
}
}
public StyleBox? PanelStyleBoxOverride { get; set; }
public Color? TabFontColorOverride { get; set; }
public Color? TabFontColorInactiveOverride { get; set; }
public event Action<int>? OnTabChanged;
public TabContainer()
@@ -361,6 +365,9 @@ namespace Robust.Client.UserInterface.Controls
[System.Diagnostics.Contracts.Pure]
private Color _getTabFontColorActive()
{
if (TabFontColorOverride != null)
return TabFontColorOverride.Value;
if (TryGetStyleProperty(stylePropertyTabFontColor, out Color color))
{
return color;
@@ -371,6 +378,9 @@ namespace Robust.Client.UserInterface.Controls
[System.Diagnostics.Contracts.Pure]
private Color _getTabFontColorInactive()
{
if (TabFontColorInactiveOverride != null)
return TabFontColorInactiveOverride.Value;
if (TryGetStyleProperty(StylePropertyTabFontColorInactive, out Color color))
{
return color;
@@ -381,6 +391,9 @@ namespace Robust.Client.UserInterface.Controls
[System.Diagnostics.Contracts.Pure]
private StyleBox? _getPanel()
{
if (PanelStyleBoxOverride != null)
return PanelStyleBoxOverride;
TryGetStyleProperty<StyleBox>(StylePropertyPanelStyleBox, out var box);
return box;
}

View File

@@ -13,6 +13,12 @@ namespace Robust.Client.UserInterface.Controls
}
public override float UIScale => UIScaleSet;
internal float UIScaleSet { get; set; }
/// <summary>
/// Set after the window is resized, to batch up UI scale updates on window resizes.
/// </summary>
internal bool UIScaleUpdateNeeded { get; set; }
public override IClydeWindow Window { get; }
/// <summary>

View File

@@ -1,5 +1,6 @@
using System;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
using Robust.Shared.IoC;
@@ -235,6 +236,15 @@ namespace Robust.Client.UserInterface.CustomControls
public void OpenToRight() => OpenCenteredAt(new Vector2(1, 0.5f));
public void OpenCenteredRight() => OpenCenteredAt(new Vector2(0.75f, 0.5f));
/// <summary>
/// Opens a window and centers it relative to the screen position.
/// </summary>
public void OpenScreenAt(Vector2 relativePosition, IClyde clyde)
{
var adjusted = relativePosition / clyde.ScreenSize;
OpenCenteredAt(adjusted);
}
/// <summary>
/// Opens a window, attempting to place the center of the window at some relative point on the screen.
/// </summary>

View File

@@ -277,8 +277,21 @@ public sealed partial class DebugConsole
CommandBar.CursorPosition = lastRange.end;
CommandBar.SelectionStart = lastRange.start;
var insertValue = CommandParsing.Escape(completion);
// If the replacement contains a space, we must quote it to treat it as a single argument.
var mustQuote = insertValue.Contains(' ');
if ((completionFlags & CompletionOptionFlags.PartialCompletion) == 0)
{
if (mustQuote)
insertValue = $"\"{insertValue}\"";
insertValue += " ";
}
else if (mustQuote)
{
// If it's a partial completion, only quote the start.
insertValue = '"' + insertValue;
}
CommandBar.InsertAtCursor(insertValue);

View File

@@ -7,6 +7,7 @@ using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Input;
@@ -51,6 +52,8 @@ namespace Robust.Client.UserInterface.CustomControls
private readonly ConcurrentQueue<FormattedMessage> _messageQueue = new();
private readonly ISawmill _logger;
private int _maxEntries;
public DebugConsole()
{
RobustXamlLoader.Load(this);
@@ -78,6 +81,7 @@ namespace Robust.Client.UserInterface.CustomControls
_consoleHost.AddString += OnAddString;
_consoleHost.AddFormatted += OnAddFormatted;
_consoleHost.ClearText += OnClearText;
_cfg.OnValueChanged(CVars.ConMaxEntries, MaxEntriesChanged, true);
UserInterfaceManager.ModalRoot.AddChild(_compPopup);
}
@@ -89,10 +93,17 @@ namespace Robust.Client.UserInterface.CustomControls
_consoleHost.AddString -= OnAddString;
_consoleHost.AddFormatted -= OnAddFormatted;
_consoleHost.ClearText -= OnClearText;
_cfg.UnsubValueChanged(CVars.ConMaxEntries, MaxEntriesChanged);
UserInterfaceManager.ModalRoot.RemoveChild(_compPopup);
}
private void MaxEntriesChanged(int value)
{
_maxEntries = value;
TrimExtraOutputEntries();
}
private void OnClearText(object? _, EventArgs args)
{
Clear();
@@ -165,6 +176,15 @@ namespace Robust.Client.UserInterface.CustomControls
private void _addFormattedLineInternal(FormattedMessage message)
{
Output.AddMessage(message);
TrimExtraOutputEntries();
}
private void TrimExtraOutputEntries()
{
while (Output.EntryCount > _maxEntries)
{
Output.RemoveEntry(0);
}
}
private void _flushQueue()

View File

@@ -83,7 +83,7 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
}
else
{
mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId),
mouseGridPos = new EntityCoordinates(mapSystem.GetMapOrInvalid(mouseWorldMap.MapId),
mouseWorldMap.Position);
tile = new TileRef(EntityUid.Invalid,
mouseGridPos.ToVector2i(_entityManager, _mapManager, xformSystem), Tile.Empty);

View File

@@ -12,7 +12,7 @@ public sealed class EntitySpawnButton : Control
public EntityPrototype Prototype { get; set; } = default!;
public Button ActualButton { get; private set; }
public Label EntityLabel { get; private set; }
public LayeredTextureRect EntityTextureRects { get; private set; }
public EntityPrototypeView EntityTextureRects {get; private set; }
public int Index { get; set; }
public EntitySpawnButton()
@@ -27,13 +27,12 @@ public sealed class EntitySpawnButton : Control
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
(EntityTextureRects = new LayeredTextureRect
(EntityTextureRects = new EntityPrototypeView
{
MinSize = new Vector2(32, 32),
SetSize = new Vector2(32, 32),
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
Stretch = TextureRect.StretchMode.KeepAspectCentered,
CanShrink = true
Stretch = SpriteView.StretchMode.Fill
}),
(EntityLabel = new Label
{

View File

@@ -44,7 +44,7 @@ namespace Robust.Client.UserInterface.CustomControls
}
// Create a spawn button and insert it into the start or end of the list.
public EntitySpawnButton InsertEntityButton(EntityPrototype prototype, bool insertFirst, int index, List<Texture> textures)
public EntitySpawnButton InsertEntityButton(EntityPrototype prototype, bool insertFirst, int index)
{
var button = new EntitySpawnButton
{
@@ -67,7 +67,7 @@ namespace Robust.Client.UserInterface.CustomControls
}
var rect = button.EntityTextureRects;
rect.Textures = textures;
rect.SetPrototype(prototype.ID);
PrototypeList.AddChild(button);
if (insertFirst)

View File

@@ -64,7 +64,7 @@ namespace Robust.Client.UserInterface
IDebugMonitors DebugMonitors { get; }
void Popup(string contents, string title = "Alert!");
void Popup(string contents, string? title = null, bool clipboardButton = true);
Control? MouseGetControl(ScreenCoordinates coordinates);

View File

@@ -17,7 +17,6 @@ namespace Robust.Client.UserInterface
internal struct RichTextEntry
{
private readonly Color _defaultColor;
private readonly MarkupTagManager _tagManager;
private readonly Type[]? _tagsAllowed;
public readonly FormattedMessage Message;
@@ -37,7 +36,7 @@ namespace Robust.Client.UserInterface
/// </summary>
public ValueList<int> LineBreaks;
private readonly Dictionary<int, Control> _tagControls = new();
private readonly Dictionary<int, Control>? _tagControls;
public RichTextEntry(FormattedMessage message, Control parent, MarkupTagManager tagManager, Type[]? tagsAllowed = null, Color? defaultColor = null)
{
@@ -46,23 +45,26 @@ namespace Robust.Client.UserInterface
Width = 0;
LineBreaks = default;
_defaultColor = defaultColor ?? new(200, 200, 200);
_tagManager = tagManager;
_tagsAllowed = tagsAllowed;
Dictionary<int, Control>? tagControls = null;
var nodeIndex = -1;
foreach (var node in Message.Nodes)
foreach (var node in Message)
{
nodeIndex++;
if (node.Name == null)
continue;
if (!_tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag) || !tag.TryGetControl(node, out var control))
if (!tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag) || !tag.TryGetControl(node, out var control))
continue;
parent.Children.Add(control);
_tagControls.Add(nodeIndex, control);
tagControls ??= new Dictionary<int, Control>();
tagControls.Add(nodeIndex, control);
}
_tagControls = tagControls;
}
/// <summary>
@@ -72,7 +74,7 @@ namespace Robust.Client.UserInterface
/// <param name="maxSizeX">The maximum horizontal size of the container of this entry.</param>
/// <param name="uiScale"></param>
/// <param name="lineHeightScale"></param>
public void Update(Font defaultFont, float maxSizeX, float uiScale, float lineHeightScale = 1)
public void Update(MarkupTagManager tagManager, Font defaultFont, float maxSizeX, float uiScale, float lineHeightScale = 1)
{
// This method is gonna suck due to complexity.
// Bear with me here.
@@ -91,10 +93,10 @@ namespace Robust.Client.UserInterface
// Nodes can change the markup drawing context and return additional text.
// It's also possible for nodes to return inline controls. They get treated as one large rune.
var nodeIndex = -1;
foreach (var node in Message.Nodes)
foreach (var node in Message)
{
nodeIndex++;
var text = ProcessNode(node, context);
var text = ProcessNode(tagManager, node, context);
if (!context.Font.TryPeek(out var font))
font = defaultFont;
@@ -113,7 +115,7 @@ namespace Robust.Client.UserInterface
return;
}
if (!_tagControls.TryGetValue(nodeIndex, out var control))
if (_tagControls == null || !_tagControls.TryGetValue(nodeIndex, out var control))
continue;
if (ProcessRune(ref this, new Rune(' '), out breakLine))
@@ -166,6 +168,7 @@ namespace Robust.Client.UserInterface
}
public readonly void Draw(
MarkupTagManager tagManager,
DrawingHandleScreen handle,
Font defaultFont,
UIBox2 drawBox,
@@ -184,10 +187,10 @@ namespace Robust.Client.UserInterface
var controlYAdvance = 0f;
var nodeIndex = -1;
foreach (var node in Message.Nodes)
foreach (var node in Message)
{
nodeIndex++;
var text = ProcessNode(node, context);
var text = ProcessNode(tagManager, node, context);
if (!context.Color.TryPeek(out var color) || !context.Font.TryPeek(out var font))
{
color = _defaultColor;
@@ -210,7 +213,7 @@ namespace Robust.Client.UserInterface
globalBreakCounter += 1;
}
if (!_tagControls.TryGetValue(nodeIndex, out var control))
if (_tagControls == null || !_tagControls.TryGetValue(nodeIndex, out var control))
continue;
var invertedScale = 1f / uiScale;
@@ -223,24 +226,22 @@ namespace Robust.Client.UserInterface
}
}
private readonly string ProcessNode(MarkupNode node, MarkupDrawingContext context)
private readonly string ProcessNode(MarkupTagManager tagManager, MarkupNode node, MarkupDrawingContext context)
{
// If a nodes name is null it's a text node.
if (node.Name == null)
return node.Value.StringValue ?? "";
//Skip the node if there is no markup tag for it.
if (!_tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag))
if (!tagManager.TryGetMarkupTag(node.Name, _tagsAllowed, out var tag))
return "";
if (!node.Closing)
{
context.Tags.Add(tag);
tag.PushDrawContext(node, context);
return tag.TextBefore(node);
}
context.Tags.Remove(tag);
tag.PopDrawContext(node, context);
return tag.TextAfter(node);
}

View File

@@ -1,3 +1,4 @@
using System.Linq;
using Robust.Client.State;
using Robust.Shared.Console;
using Robust.Shared.IoC;
@@ -7,11 +8,12 @@ namespace Robust.Client.UserInterface
{
sealed class ChangeSceneCommpand : LocalizedCommands
{
[Dependency] private readonly IReflectionManager _reflection = default!;
public override string Command => "scene";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var reflection = IoCManager.Resolve<IReflectionManager>();
var types = reflection.GetAllChildren(typeof(State.State));
var types = _reflection.GetAllChildren(typeof(State.State));
foreach (var tryType in types)
{
@@ -26,5 +28,13 @@ namespace Robust.Client.UserInterface
shell.WriteError($"No scene child class type ends with {args[0]}");
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length != 1)
return CompletionResult.Empty;
var types = _reflection.GetAllChildren(typeof(State.State));
return CompletionResult.FromHintOptions(types.Select(x => x.Name), "[State name]");
}
}
}

View File

@@ -4,6 +4,7 @@ using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Profiling;
@@ -53,14 +54,47 @@ internal sealed partial class UserInterfaceManager
}
}
public void Popup(string contents, string title = "Alert!")
public void Popup(string contents, string? title = null, bool clipboardButton = true)
{
var popup = new DefaultWindow
{
Title = title
Title = string.IsNullOrEmpty(title) ? Loc.GetString("popup-title") : title,
};
popup.Contents.AddChild(new Label {Text = contents});
var label = new Label { Text = contents };
var vBox = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
};
vBox.AddChild(label);
if (clipboardButton)
{
var copyButton = new Button
{
Text = Loc.GetString("popup-copy-button"),
HorizontalExpand = true,
};
copyButton.OnPressed += _ =>
{
_clipboard.SetText(contents);
};
var hBox = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
HorizontalAlignment = Control.HAlignment.Right,
};
hBox.AddChild(copyButton);
vBox.AddChild(hBox);
}
popup.Contents.AddChild(vBox);
popup.OpenCentered();
}

View File

@@ -123,7 +123,12 @@ internal partial class UserInterfaceManager
private void UpdateUIScale(WindowRoot root)
{
root.UIScaleSet = CalculateAutoScale(root);
var newScale = CalculateAutoScale(root);
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (newScale == root.UIScaleSet)
return;
root.UIScaleSet = newScale;
_propagateUIScaleChanged(root);
root.InvalidateMeasure();
}
@@ -142,7 +147,21 @@ internal partial class UserInterfaceManager
{
if (!_windowsToRoot.TryGetValue(windowResizedEventArgs.Window.Id, out var root))
return;
UpdateUIScale(root);
root.UIScaleUpdateNeeded = true;
root.InvalidateMeasure();
}
private void CheckRootUIScaleUpdate(WindowRoot root)
{
if (!root.UIScaleUpdateNeeded)
return;
using (_prof.Group("UIScaleUpdate"))
{
UpdateUIScale(root);
}
root.UIScaleUpdateNeeded = false;
}
}

View File

@@ -54,6 +54,7 @@ namespace Robust.Client.UserInterface
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IRuntimeLog _runtime = default!;
[Dependency] private readonly IClipboardManager _clipboard = null!;
private IAudioSource? _clickSource;
private IAudioSource? _hoverSource;
@@ -216,6 +217,8 @@ namespace Robust.Client.UserInterface
{
foreach (var root in _roots)
{
CheckRootUIScaleUpdate(root);
using (_prof.Group("Root"))
{
var totalUpdated = root.DoFrameUpdateRecursive(args);

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