Compare commits

...

56 Commits

Author SHA1 Message Date
Pieter-Jan Briers
5ef7ef139b Version: 225.0.2 2024-08-11 19:54:42 +02:00
Pieter-Jan Briers
facc16e931 Use absolute path for explorer.exe
frick me

(cherry picked from commit 0284eb0430)
2024-08-11 19:54:42 +02:00
Pieter-Jan Briers
11e4084652 Version: 225.0.1 2024-08-11 17:56:05 +02:00
Pieter-Jan Briers
f12eb3a36a Security updates (#5353)
* Fix security bug in WritableDirProvider.OpenOsWindow()

Reported by @NarryG and @nyeogmi

* Sandbox updates

* Update ImageSharp again

(cherry picked from commit 7d778248ee)
(cherry picked from commit f66cda74e95619ddba2221bda644bf4394619805)
(cherry picked from commit db8ba83866c523e08e4fba0b80cd954f4f190613)
2024-08-11 17:56:05 +02:00
metalgearsloth
2fa83181e2 Version: 225.0.0 2024-06-15 16:46:54 +10:00
Leon Friedrich
36f02b4a18 Fix IComponentFactory mock in tests (#5240) 2024-06-15 16:38:05 +10:00
metalgearsloth
e842142dd7 Minor API niceties (#5219)
* Minor API niceties

* weh
2024-06-15 16:26:01 +10:00
Leon Friedrich
2eb740cea8 Try prevent eventbus loops (#5166)
* Add test

* Try prevent event bus linked list loops

* Eh, add an upper limit anyways
2024-06-15 12:20:48 +10:00
metalgearsloth
a044f04e3b Remove CompIdx locks (#5231)
* Remove CompIdx locks

So GetComponentState in PVS calls RaiseComponentEvent which in turn calls this. When you start getting a significant number of players it seems to run into lock contention considering every single compstate get will lock this.

Instead we'll just update the dictionary whenever RegisterClass is called instead.

* Fix thread-safety issues
2024-06-15 01:30:45 +10:00
Leon Friedrich
a4723d1f62 Avoid read lock in GetEntityQuery (#5236) 2024-06-15 01:19:29 +10:00
Pieter-Jan Briers
627c1eb054 Rewrite HappyEyeballsHttp
This makes the game use HTTPS more when available.

Implementation is just taken from my work on the launcher, nothing special here.
2024-06-14 11:27:34 +02:00
Pieter-Jan Briers
836aec0b87 Changelog for Toolshed ent change
Forgot this in 5c83678c78. Oops.
2024-06-14 03:03:33 +02:00
Pieter-Jan Briers
9116e64291 Implement info query ?can_skip_build=1
The hub has been adding this parameter for a while, instructing the game server that it doesn't need to run ACZ. This fixes the (relatively common) issue where the first publish fails because ACZ takes longer than the hub status timeout.

I apparently already committed some code for this once on accident. Whoops.
2024-06-14 03:02:12 +02:00
metalgearsloth
a6bfb5f557 Fix lookupflags oversight (#5233)
The ONE codepath CM-14 used and I forgot to add it.
2024-06-14 09:59:47 +10:00
Pieter-Jan Briers
5c83678c78 Fix "ent" toolshed command
Makes it use NetEntity instead of EntityUid.
2024-06-13 00:22:22 +02:00
Pieter-Jan Briers
eac94b1032 Allow Eye position to be set directly.
Eye is not a well-designed API, but we've got it so here we go. It was originally designed to have some form of support for non-entity eyes through the FixedEye type, by overriding the Position property in a child type. #1016 broke this however.

This PR just makes the property writable so this is possible again.

Co-authored-by: moonheart08 <moony@hellomouse.net>
2024-06-12 23:56:29 +02:00
metalgearsloth
efd870d070 Mark System<T> as pure (#5225) 2024-06-11 03:43:20 +02:00
Leon Friedrich
94f98073b0 Make PrototypeManager.TryIndex log errors when using invalid id structs (#5203)
* Make `PrototypeManager.TryIndex` log errors when using id structs

* A
2024-06-08 22:15:21 +10:00
DrSmugleaf
5aa9378de0 Add an overload of TerminatingOrDeleted with a nullable EntityUid (#5214) 2024-06-08 20:51:05 +10:00
Leon Friedrich
850e9ab695 Try optimize NetEntities console completion helper (#5217)
* Try optimize `NetEntities` completion options

* Actually just remove it

* a
2024-06-08 20:44:21 +10:00
Tayrtahn
7319f3a241 Raise an event when an entity's name is changed (#5216) 2024-06-08 10:45:28 +10:00
DrSmugleaf
b6252c9e4f Make Entity<T> work as a loc parameter (#5215) 2024-06-07 15:48:00 +02:00
ElectroJr
fcd507d1f9 Version: 224.1.1 2024-06-06 00:58:26 +12:00
Leon Friedrich
1eb874f4c3 Fix storage key-not-found exception (#5213) 2024-06-05 22:56:50 +10:00
ElectroJr
a628d31c4b Version: 224.1.0 2024-06-05 20:19:18 +12:00
Leon Friedrich
2b0ecd7166 Fix cvar type errors (#5212) 2024-06-05 18:18:49 +10:00
Tom Leys
bde650689b Perf: Avoid a copy of ComponentChanges every tick within Checkpoints (#5146)
* Perf: Avoid a copy of ComponentChanges every tick within Checkpoints

- Also remove temporary Dictionary created every tick * every change
- Reduces GC load, 6GB less temporary allocations on typical replays.

* perf: Checkpoints: Apply state changes in-place when possible

- Avoids >1GB of gas tile allocations.

* Revert "perf: Checkpoints: Apply state changes in-place when possible"

This reverts commit 1a478944a6.

* Fix delta state merge issues

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2024-06-05 18:06:38 +10:00
Tom Leys
87d8d74d8c Perf: Improve replay playback responsiveness (#5152)
* Perf: Improve replay playback responsiveness

- new CVAR ReplayMaxScrubTime
- There is a time budget when applying replay ticks updates of only 10 ms
- Ensure we don't apply checkpoints that move us backwards in time by accident
- Prevent double-lookup of checkpoints.

* Fix merge error

* Fix it again, but for real this time

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2024-06-05 17:38:36 +10:00
Nemanja
dddf13a19a EntityPrototypeView (#5185)
* ent proto view

* pee jay bee rahvew

* Fix EnteredTree() not respawning the entity

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2024-06-05 17:04:49 +10:00
Leon Friedrich
75626a86a3 Add dummy sessions for integration tests (#5202)
* Add dummy sessions

* if FULL_RELEASE
2024-06-05 16:50:06 +10:00
Tornado Tech
3e3cd0e257 Fixed incmd command (#5192)
* Fixed incmd command

* Change `HandleInputCommand` argument type

* Localize console errors

* Why is input code even like this
2024-06-05 16:32:56 +10:00
deltanedas
a3a90154a4 add SetUi to shared ui system (#5092)
* re-add AddUi

* rename to SetUi, add if missing

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-06-05 15:25:49 +10:00
Ygg01
9240c94e59 Write Errors when a duplicate localization key is found. (#4885)
* Update Linguini to v0.8.1

* Add tests and verify desired behavior has been reached.

* Remove duplicate messages.

* Minor fix to message output. Add Wrapper for Fluent errors.

* Restart the test pipeline.

* Restart the test pipeline.

* Make so test don't do an early bailout.

* Ensure all errors get written rather than bailing on first.

* Fix text breakage.

* Remove obsolete // TODO LINGUINI

* line wrapping conventions

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2024-06-05 15:23:41 +10:00
ShadowCommander
a95ba9f181 Fix Toolshed EnumTypeParse on non-lowercase enum values (#5211) 2024-06-05 15:22:02 +10:00
Leon Friedrich
074a4faa92 Try fix client-side BUI error spam (#5208) 2024-06-05 15:21:27 +10:00
metalgearsloth
6b4d74f46e Maybe fix bad resolve logging (#5207)
I'm not entirely sure what happens, I couldn't repro it locally even when I tried to force it to use threadpool threads. The only info I have is it happens and no other info has been provided so.
2024-06-05 09:00:41 +10:00
ElectroJr
f648218756 Version: 224.0.1 2024-06-03 02:57:09 +12:00
Leon Friedrich
b497efb0c0 Try fix IPrototypeManager.ResolveResults() error (#5200)
* Try fix `IPrototypeManager.ResolveResults()` error

* A

* I love test IoC

* I love test init logic
2024-06-03 00:52:27 +10:00
Leon Friedrich
d8f2b917b4 Fix buis not closing properly on entity deletion (#5198) 2024-06-02 22:12:39 +10:00
Leon Friedrich
6dd6b79db6 Fix PVS exception (#5196) 2024-06-02 21:58:48 +10:00
ElectroJr
ff4548f108 Version: 224.0.0 2024-06-02 16:24:12 +12:00
ElectroJr
8f41405b31 Update release notes 2024-06-02 16:23:06 +12:00
Leon Friedrich
f285c62674 Rework entity prototype categories (#5061)
* Improve entity categories

* A
2024-06-02 14:10:11 +10:00
eoineoineoin
56c30edf04 Replace Matrix3 with System.Numerics.Matrix3x2 (#5078)
* Delete Matrix3. Replace with System.Numerics.Matrix3x2

* Feedback

* release notes
2024-06-02 14:08:47 +10:00
Nemanja
783d529ec4 more placement manager fixes (#5186)
* more placement manager fixes

* Update Robust.Shared/GameObjects/EntityManager.cs

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

---------

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
2024-06-01 10:13:52 -07:00
Pieter-Jan Briers
895bfb8ec0 Add Array.Clear(Array) to sandbox
API added in .NET 6, overload that took explicit array bounds was already in sandbox.
2024-06-01 14:17:28 +02:00
Leon Friedrich
6ef67cf513 Add stack trace to some error logs and remove some audio error logs (#5188)
* Add stack trace to some error logs

* Remove terminating entity audio error logs
2024-05-31 13:16:47 -07:00
Leon Friedrich
15a2f6702c Add try-catch to EnsureClientBui() (#5189)
* Add `try-catch` to `EnsureClientBui()`

* Better comment

* Uneccesary !

* a
2024-05-31 18:01:29 +10:00
Leon Friedrich
c5c2c2022a Ignore invalid entityuids in bui system update method. (#5187) 2024-05-31 10:47:55 +10:00
Pieter-Jan Briers
3c378640dd Add cvar_subs command
Dumb little thing, just wanted to test something.
2024-05-31 02:00:53 +02:00
metalgearsloth
0a149fa91c Expand PVS BUIs (#5179)
Mainly happens to aghosts who go out of range. Shouldn't be a huge perf impact as the User component is added / removed as BUIs get opened.
2024-05-31 09:51:11 +10:00
metalgearsloth
fe8d1d9422 Fix BUI state getting stuck (#5180) 2024-05-30 11:02:44 +10:00
metalgearsloth
c89c529ba4 Version: 223.3.0 2024-05-29 17:35:10 +10:00
metalgearsloth
dd56de70b7 Fix grid-based audio (#5087)
* Fix grid-based audio

- Fixes parenting issues.
- Add SetGridAudio as an easy way to set it up and apply the override too.

* No more global

* Rejig it all

* mergew

* review

* Minor optimisation

* Revert "Minor optimisation"

This reverts commit d0cdac7690.
2024-05-29 16:14:37 +10:00
Nemanja
710408c613 Fix placement manager rotation jank (#5176) 2024-05-29 12:32:14 +10:00
metalgearsloth
2461cd94dd Better parallel exception logging (#5175) 2024-05-29 12:31:46 +10:00
171 changed files with 3787 additions and 2484 deletions

View File

@@ -15,10 +15,10 @@
<PackageVersion Include="ILReader.Core" Version="1.0.0.4" />
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageVersion Include="JetBrains.Profiler.Api" Version="1.4.0" />
<PackageVersion Include="Linguini.Bundle" Version="0.1.3" />
<PackageVersion Include="Linguini.Bundle" Version="0.8.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit" Version="1.1.1"/>
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit" Version="1.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.8.0" />
@@ -55,7 +55,7 @@
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.3" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />

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,123 @@ END TEMPLATE-->
*None yet*
## 225.0.2
## 225.0.1
## 225.0.0
### Breaking changes
* `NetEntity.Parse` and `TryParse` will now fail to parse empty strings.
* Try to prevent EventBus looping. This also caps the amount of directed component subscriptions for a particular component to 256.
### New features
* `IPrototypeManager.TryIndex` will now default to logging errors if passed an invalid prototype id struct (i,e., `EntProtoId` or `ProtoId<T>`). There is a new optional bool argument to disable logging errors.
* `Eye` now allows its `Position` to be set directly. Please only do this with the `FixedEye` child type constructed manually.
* Engine now respects the hub's `can_skip_build` parameter on info query, fixing an issue where the first hub advertisement fails due to ACZ taking too long.
* Add GetSession & TryGetSession to ActorSystem.
* Raise an event when an entity's name is changed.
### Bugfixes
* The `ent` toolshed command now takes `NetEntity` values, fixing parsing in practical uses.
* Fix ComponentFactory test mocks.
* Fix LookupFlags missing from a couple of EntityLookupSystem methods.
### Other
* Improved engine's Happy Eyeballs implementation, should result in more usage of IPv6 for HTTP APIs when available.
* Remove CompIdx locks to improve performance inside Pvs at higher player counts.
* Avoid a read lock in GetEntityQuery to also improve performance.
* Mark `EntityManager.System<T>` as Pure.
## 224.1.1
### Bugfixes
* Fixed UserInterfaceSystem sometimes throwing a key-not-found exception when trying to close UIs.
## 224.1.0
### New features
* `ServerIntegrationInstance` has new methods for adding dummy player sessions for tests that require multiple players.
* Linguini has been updated to v0.8.1. Errors will now be logged when a duplicate localization key is found.
* Added `UserInterfaceSystem.SetUi()` for modifying the `InterfaceData` associated with some BUI.
* Added the `EntityPrototypeView` control for spawning & rendering an entity prototype.
### Bugfixes
* Fix `UserInterfaceSystem` spamming client side errors when entities with UIs open are deleted while outside of PVS range.
* Fix Toolshed's EnumTypeParse not working enum values with upercase characters.
* Fixed `incmd` command not working due to an invalid cast.
### Other
* There have been various performance improvements to replay loading & playback.
### Internal
* Added `DummySession` and `DummyChannel` classes for use in integration tests and benchmarks to fool the server into thinking that there are multiple players connected.
* Added `ICommonSessionInternal` and updated `CommonSession` so that the internal setters now go through that interface.
## 224.0.1
### Bugfixes
* Fixes PVS throwing exceptions when invalid entities are passed to `ExpandPvsEvent`. Now it just logs an error.
* Fixes BUIs not properly closing, resulting in invalid entities in `UserInterfaceUserComponent.OpenInterfaces`
* Fixes an unknown/invalid prototype exception sometimes being thrown when running ``IPrototypeManager.ResolveResults()`
## 224.0.0
### Breaking changes
* `Matrix3` has been replaced with `System.Numerics.Matrix3x2`. Various Matrix related methods have been turned into extension methods in the `Matrix3Helpers` class.
* Engine `EntityCategory` prototype IDs have been changed to use CamelCase. I.e., `hideSpawnMenu` -> `HideSpawnMenu`
* Prototypes can now be implicitly cast `ProtoId<T>` or `EntProtoId` ID structs. The new implicit cast might cause previous function calls to be ambiguous.
### New features
* `Array.Clear(Array)` is now available in the sandbox.
* BUIs now use `ExpandPvsEvent`. I.e., if a player has a UI open, then the entity associated with that UI will always get sent to the player by the PVS system.
* Added `cvar_subs` command for listing all subscribers to cvar changes
* Entity categories have been reworked
* Each category now has a `HideSpawnMenu` field. The old `HideSpawnMenu` category is now just a normal category with that field set to true.
* Reworked category inheritance. Inheritance can now be disabled per category using a `Inheritable` field.
* Entity prototypes can now be automatically added to categories based on the components that they have, either by specifying components when defining the category in yml, or by adding the EntityCategoryAttribute to the component class.
### Bugfixes
* Fixed client-side BUI error log spam if an unknown entity has a UI open.
* Fixed placement manager spawning entities with incorrect rotations.
### Other
* Added a try-catch block to BUI constructors, to avoid clients getting stuck in error loops while applying states.
* Attempting to play sounds on terminating entities no longer logs an error.
## 223.3.0
### New features
* Better exception logging for IRobustJob.
* Add SetGridAudio helper for SharedAudioSystem.
### Bugfixes
* Fix placement manager not setting entity rotation correctly.
* Fix grid-based audio not playing correctly.
## 223.2.0
### New features

View File

@@ -2,4 +2,7 @@
id: Audio
name: Audio
description: Audio entity used by engine
save: false
save: false
components:
- type: Transform
gridTraversal: false

View File

@@ -1,7 +1,7 @@
- type: entity
id: debugRotation
abstract: true
categories: [ debug ]
categories: [ Debug ]
components:
- type: Sprite
netsync: false

View File

@@ -1,17 +1,20 @@
# debug related entities
- type: entityCategory
id: debug
id: Debug
name: entity-category-name-debug
description: entity-category-desc-debug
suffix: entity-category-suffix-debug
# entities that spawn other entities
- type: entityCategory
id: spawner
id: Spawner
name: entity-category-name-spawner
description: entity-category-desc-spawner
# entities that should be hidden from the spawn menu
# simple category that just exists to hide prototypes in spawn menus
- type: entityCategory
id: hideSpawnMenu
id: HideSpawnMenu
name: entity-category-name-hide
description: entity-category-desc-hide
hideSpawnMenu: true
inheritable: false

View File

@@ -9,6 +9,7 @@ cmd-parse-failure-float = {$arg} is not a valid float.
cmd-parse-failure-bool = {$arg} is not a valid bool.
cmd-parse-failure-uid = {$arg} is not a valid entity UID.
cmd-parse-failure-mapid = {$arg} is not a valid MapId.
cmd-parse-failure-enum = {$arg} is not a {$enum} Enum.
cmd-parse-failure-grid = {$arg} is not a valid grid.
cmd-parse-failure-entity-exist = UID {$arg} does not correspond to an existing entity.
cmd-parse-failure-session = There is no session with username: {$username}
@@ -43,6 +44,13 @@ cmd-cvar-compl-list = List available CVars
cmd-cvar-arg-name = <name | ?>
cmd-cvar-value-hidden = <value hidden>
## 'cvar_subs' command
cmd-cvar_subs-desc = Lists the OnValueChanged subscriptions for a CVar.
cmd-cvar_subs-help = Usage: cvar_subs <name>
cmd-cvar_subs-invalid-args = Must provide exactly one argument.
cmd-cvar_subs-arg-name = <name>
## 'list' command
cmd-list-desc = Lists available commands, with optional search filter
cmd-list-help = Usage: list [filter]
@@ -245,9 +253,6 @@ cmd-bind-arg-command = <InputCommand>
cmd-net-draw-interp-desc = Toggles the debug drawing of the network interpolation.
cmd-net-draw-interp-help = Usage: net_draw_interp
cmd-net-draw-interp-desc = Toggles the debug drawing of the network interpolation.
cmd-net-draw-interp-help = Usage: net_draw_interp
cmd-net-watch-ent-desc = Dumps all network updates for an EntityId to the console.
cmd-net-watch-ent-help = Usage: net_watchent <0|EntityUid>
@@ -299,16 +304,9 @@ cmd-savegrid-help = savegrid <gridID> <Path>
cmd-testbed-desc = Loads a physics testbed on the specified map.
cmd-testbed-help = testbed <mapid> <test>
cmd-saveconfig-desc = Saves the client configuration to the config file.
cmd-saveconfig-help = saveconfig
## 'flushcookies' command
# Note: the flushcookies command is from Robust.Client.WebView, it's not in the main engine code.
cmd-flushcookies-desc = Flush CEF cookie storage to disk
cmd-flushcookies-help = This ensure cookies are properly saved to disk in the event of unclean shutdowns.
Note that the actual operation is asynchronous.
## 'addcomp' command
cmd-addcomp-desc = Adds a component to an entity.
cmd-addcomp-help = addcomp <uid> <componentName>
@@ -384,9 +382,9 @@ cmd-tp-desc = Teleports a player to any location in the round.
cmd-tp-help = tp <x> <y> [<mapID>]
cmd-tpto-desc = Teleports the current player or the specified players/entities to the location of the first player/entity.
cmd-tpto-help = tpto <username|uid> [username|uid]...
cmd-tpto-destination-hint = destination (uid or username)
cmd-tpto-victim-hint = entity to teleport (uid or username)
cmd-tpto-help = tpto <username|uid> [username|NetEntity]...
cmd-tpto-destination-hint = destination (NetEntity or username)
cmd-tpto-victim-hint = entity to teleport (NetEntity or username)
cmd-tpto-parse-error = Cant resolve entity or player: {$str}
cmd-listplayers-desc = Lists all players currently connected.
@@ -446,9 +444,6 @@ cmd-showanchored-help = Usage: showanchored
cmd-dmetamem-desc = Dumps a type's members in a format suitable for the sandbox configuration file.
cmd-dmetamem-help = Usage: dmetamem <type>
cmd-dmetamem-desc = Displays chunk bounds for the purposes of rendering.
cmd-dmetamem-help = Usage: showchunkbb <type>
cmd-launchauth-desc = Load authentication tokens from launcher data to aid in testing of live servers.
cmd-launchauth-help = Usage: launchauth <account name>
@@ -515,9 +510,6 @@ cmd-profsnap-help = Usage: profsnap
cmd-devwindow-desc = Dev Window
cmd-devwindow-help = Usage: devwindow
cmd-devwindow-desc = Open file
cmd-devwindow-help = Usage: testopenfile
cmd-scene-desc = Immediately changes the UI scene/state.
cmd-scene-help = Usage: scene <className>
@@ -528,14 +520,11 @@ cmd-hwid-desc = Returns the current HWID (HardWare ID).
cmd-hwid-help = Usage: hwid
cmd-vvread-desc = Retrieve a path's value using VV (View Variables).
cmd-vvread-desc = Usage: vvread <path>
cmd-vvread-help = Usage: vvread <path>
cmd-vvwrite-desc = Modify a path's value using VV (View Variables).
cmd-vvwrite-help = Usage: vvwrite <path>
cmd-vv-desc = Opens View Variables (VV).
cmd-vv-help = Usage: vv <path|entity ID|guihover>
cmd-vvinvoke-desc = Invoke/Call a path with arguments using VV.
cmd-vvinvoke-help = Usage: vvinvoke <path> [arguments...]

View File

@@ -1,8 +1,9 @@
entity-category-name-debug = Debug
entity-category-desc-debug = Entity prototypes intended for debugging & testing.
entity-category-suffix-debug = Debug
entity-category-name-spawner = Spawner
entity-category-desc-spawner = Entity prototypes that spawn other entities.
entity-category-name-hide = Hidden
entity-category-desc-hide = Entity prototypes that should be hidden from the spawn menu
entity-category-desc-hide = Entity prototypes that should be hidden from entity spawn menus

View File

@@ -41,7 +41,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
[Dependency] private readonly IParallelManager _parMan = default!;
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
[Dependency] private readonly IAudioInternal _audio = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly SharedMapSystem _maps = default!;
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
@@ -216,11 +216,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
}
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
{
_metadata.SetFlag(entity.Owner, MetaDataFlags.Undetachable, true);
}
// Need to set all initial data for first frame.
ApplyAudioParams(component.Params, component);
component.Source.Global = component.Global;
@@ -336,55 +331,22 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
Vector2 worldPos;
var gridUid = xform.ParentUid;
component.Volume = component.Params.Volume;
Vector2 delta;
// Handle grid audio differently by using nearest-edge instead of entity centre.
// Handle grid audio differently by using grid position.
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
{
// It's our grid so max volume.
if (_listenerGrid == gridUid)
{
component.Volume = component.Params.Volume;
component.Occlusion = 0f;
component.Position = listener.Position;
return;
}
// TODO: Need a grid-optimised version because this is gonna be expensive.
// Just to avoid clipping on and off grid or nearestPoint changing we'll
// always set the sound to listener's pos, we'll just manually do gain ourselves.
if (_physics.TryGetNearest(gridUid, listener, out _, out var gridDistance))
{
// Out of range
if (gridDistance > component.MaxDistance)
{
component.Gain = 0f;
return;
}
var paramsGain = VolumeToGain(component.Params.Volume);
// Thought I'd never have to manually calculate gain again but this is the least
// unpleasant audio I could get at the moment.
component.Gain = paramsGain * _audio.GetAttenuationGain(
gridDistance,
component.Params.RolloffFactor,
component.Params.ReferenceDistance,
component.Params.MaxDistance);
component.Position = listener.Position;
return;
}
// Can't get nearest point so don't play anymore.
component.Gain = 0f;
return;
var parentUid = xform.ParentUid;
worldPos = _maps.GetGridPosition(parentUid);
}
else
{
worldPos = _xformSys.GetWorldPosition(entity);
}
worldPos = _xformSys.GetWorldPosition(entity);
component.Volume = component.Params.Volume;
// Max distance check
var delta = worldPos - listener.Position;
delta = worldPos - listener.Position;
var distance = delta.Length();
// Out of range so just clip it for us.
@@ -403,8 +365,15 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
// Update audio occlusion
var occlusion = GetOcclusion(listener, delta, distance, entity);
component.Occlusion = occlusion;
if ((component.Flags & AudioFlags.NoOcclusion) == AudioFlags.NoOcclusion)
{
component.Occlusion = 0f;
}
else
{
var occlusion = GetOcclusion(listener, delta, distance, entity);
component.Occlusion = occlusion;
}
// Update audio positions.
component.Position = worldPos;
@@ -671,10 +640,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
private (EntityUid Entity, AudioComponent Component) CreateAndStartPlayingStream(AudioParams? audioParams, AudioStream stream)
{
var audioP = audioParams ?? AudioParams.Default;
var entity = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace);
var comp = SetupAudio(entity, null, audioP, stream.Length);
LoadStream((entity, comp), stream);
var entity = SetupAudio(null, audioP, initialize: false, length: stream.Length);
LoadStream(entity, stream);
EntityManager.InitializeAndStartEntity(entity);
var comp = entity.Comp;
var source = comp.Source;
// TODO clamp the offset inside of SetPlaybackPosition() itself.

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

@@ -368,7 +368,7 @@ namespace Robust.Client.Debugging
}
worldHandle.UseShader(null);
worldHandle.SetTransform(Matrix3.Identity);
worldHandle.SetTransform(Matrix3x2.Identity);
}
private void DrawScreen(DrawingHandleScreen screenHandle, OverlayDrawArgs args)
@@ -443,7 +443,7 @@ namespace Robust.Client.Debugging
}
screenHandle.UseShader(null);
screenHandle.SetTransform(Matrix3.Identity);
screenHandle.SetTransform(Matrix3x2.Identity);
}
protected internal override void Draw(in OverlayDrawArgs args)
@@ -520,11 +520,11 @@ namespace Robust.Client.Debugging
var matrix1 = xform1.WorldMatrix;
var matrix2 = xform2.WorldMatrix;
var xf1 = new Vector2(matrix1.R0C2, matrix1.R1C2);
var xf2 = new Vector2(matrix2.R0C2, matrix2.R1C2);
var xf1 = new Vector2(matrix1.M31, matrix1.M32);
var xf2 = new Vector2(matrix2.M31, matrix2.M32);
var p1 = matrix1.Transform(joint.LocalAnchorA);
var p2 = matrix2.Transform(joint.LocalAnchorB);
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);

View File

@@ -150,7 +150,7 @@ namespace Robust.Client.GameObjects
[DataField("color")]
private Color color = Color.White;
public Matrix3 LocalMatrix = Matrix3.Identity;
public Matrix3x2 LocalMatrix = Matrix3x2.Identity;
[Animatable]
[ViewVariables(VVAccess.ReadWrite)]
@@ -389,10 +389,10 @@ namespace Robust.Client.GameObjects
internal void UpdateLocalMatrix()
{
LocalMatrix = Matrix3.CreateTransform(in offset, in rotation, in scale);
LocalMatrix = Matrix3Helpers.CreateTransform(in offset, in rotation, in scale);
}
public Matrix3 GetLocalMatrix()
public Matrix3x2 GetLocalMatrix()
{
return LocalMatrix;
}
@@ -1304,22 +1304,21 @@ namespace Robust.Client.GameObjects
// worldRotation + eyeRotation should be the angle of the entity on-screen. If no-rot is enabled this is just set to zero.
// However, at some point later the eye-matrix is applied separately, so we subtract -eye rotation for now:
var entityMatrix = Matrix3.CreateTransform(worldPosition, NoRotation ? -eyeRotation : worldRotation - cardinal);
var entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, NoRotation ? -eyeRotation : worldRotation - cardinal);
Matrix3.Multiply(in LocalMatrix, in entityMatrix, out var transformSprite);
var transformSprite = Matrix3x2.Multiply(LocalMatrix, entityMatrix);
if (GranularLayersRendering)
{
//Default rendering
entityMatrix = Matrix3.CreateTransform(worldPosition, worldRotation);
Matrix3.Multiply(in LocalMatrix, in entityMatrix, out var transformDefault);
entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, worldRotation);
var transformDefault = Matrix3x2.Multiply(LocalMatrix, entityMatrix);
//Snap to cardinals
entityMatrix = Matrix3.CreateTransform(worldPosition, worldRotation - angle.GetCardinalDir().ToAngle());
Matrix3.Multiply(in LocalMatrix, in entityMatrix, out var transformSnap);
entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, worldRotation - angle.GetCardinalDir().ToAngle());
var transformSnap = Matrix3x2.Multiply(LocalMatrix, entityMatrix);
//No rotation
entityMatrix = Matrix3.CreateTransform(worldPosition, -eyeRotation);
Matrix3.Multiply(in LocalMatrix, in entityMatrix, out var transformNoRot);
entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, -eyeRotation);
var transformNoRot = Matrix3x2.Multiply(LocalMatrix, entityMatrix);
foreach (var layer in Layers) {
switch (layer.RenderingStrategy)
@@ -1546,7 +1545,7 @@ namespace Robust.Client.GameObjects
private RSI.State? _actualState;
[ViewVariables] public RSI.State? ActualState => _actualState;
public Matrix3 LocalMatrix = Matrix3.Identity;
public Matrix3x2 LocalMatrix = Matrix3x2.Identity;
[ViewVariables(VVAccess.ReadWrite)]
public Vector2 Scale
@@ -1692,7 +1691,7 @@ namespace Robust.Client.GameObjects
internal void UpdateLocalMatrix()
{
LocalMatrix = Matrix3.CreateTransform(in _offset, in _rotation, in _scale);
LocalMatrix = Matrix3Helpers.CreateTransform(in _offset, in _rotation, in _scale);
}
RSI? ISpriteLayer.Rsi { get => RSI; set => SetRsi(value); }
@@ -1964,27 +1963,27 @@ namespace Robust.Client.GameObjects
/// Given the apparent rotation of an entity on screen (world + eye rotation), get layer's matrix for drawing &
/// relevant RSI direction.
/// </summary>
public void GetLayerDrawMatrix(RsiDirection dir, out Matrix3 layerDrawMatrix)
public void GetLayerDrawMatrix(RsiDirection dir, out Matrix3x2 layerDrawMatrix)
{
if (_parent.NoRotation || dir == RsiDirection.South)
layerDrawMatrix = LocalMatrix;
else
{
Matrix3.Multiply(in _rsiDirectionMatrices[(int)dir], in LocalMatrix, out layerDrawMatrix);
layerDrawMatrix = Matrix3x2.Multiply(_rsiDirectionMatrices[(int)dir], LocalMatrix);
}
}
private static Matrix3[] _rsiDirectionMatrices = new Matrix3[]
private static Matrix3x2[] _rsiDirectionMatrices = new Matrix3x2[]
{
// array order chosen such that this array can be indexed by casing an RSI direction to an int
Matrix3.Identity, // should probably just avoid matrix multiplication altogether if the direction is south.
Matrix3.CreateRotation(-Direction.North.ToAngle()),
Matrix3.CreateRotation(-Direction.East.ToAngle()),
Matrix3.CreateRotation(-Direction.West.ToAngle()),
Matrix3.CreateRotation(-Direction.SouthEast.ToAngle()),
Matrix3.CreateRotation(-Direction.SouthWest.ToAngle()),
Matrix3.CreateRotation(-Direction.NorthEast.ToAngle()),
Matrix3.CreateRotation(-Direction.NorthWest.ToAngle())
Matrix3x2.Identity, // should probably just avoid matrix multiplication altogether if the direction is south.
Matrix3Helpers.CreateRotation(-Direction.North.ToAngle()),
Matrix3Helpers.CreateRotation(-Direction.East.ToAngle()),
Matrix3Helpers.CreateRotation(-Direction.West.ToAngle()),
Matrix3Helpers.CreateRotation(-Direction.SouthEast.ToAngle()),
Matrix3Helpers.CreateRotation(-Direction.SouthWest.ToAngle()),
Matrix3Helpers.CreateRotation(-Direction.NorthEast.ToAngle()),
Matrix3Helpers.CreateRotation(-Direction.NorthWest.ToAngle())
};
/// <summary>
@@ -2018,7 +2017,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Render a layer. This assumes that the input angle is between 0 and 2pi.
/// </summary>
internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3 spriteMatrix, Angle angle, Direction? overrideDirection)
internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3x2 spriteMatrix, Angle angle, Direction? overrideDirection)
{
if (!Visible || Blank)
return;
@@ -2040,7 +2039,7 @@ namespace Robust.Client.GameObjects
if (CopyToShaderParameters == null)
{
// Set the drawing transform for this layer
Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix);
var transformMatrix = Matrix3x2.Multiply(layerMatrix, spriteMatrix);
drawingHandle.SetTransform(in transformMatrix);
RenderTexture(drawingHandle, texture);

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.Containers;
@@ -118,7 +119,7 @@ public sealed class EntityLookupOverlay : Overlay
//DebugTools.Assert(!ent.IsInContainer(_entityManager));
var (entPos, entRot) = _transform.GetWorldPositionRotation(ent);
var lookupPos = invMatrix.Transform(entPos);
var lookupPos = Vector2.Transform(entPos, invMatrix);
var lookupRot = entRot - rotation;
var aabb = _lookup.GetAABB(ent, lookupPos, lookupRot, xform, _xformQuery);
@@ -127,6 +128,6 @@ public sealed class EntityLookupOverlay : Overlay
}
});
worldHandle.SetTransform(Matrix3.Identity);
worldHandle.SetTransform(Matrix3x2.Identity);
}
}

View File

@@ -109,7 +109,7 @@ namespace Robust.Client.GameObjects
}
}
worldHandle.SetTransform(Matrix3.Identity);
worldHandle.SetTransform(Matrix3x2.Identity);
}
}
}

View File

@@ -20,6 +20,7 @@ namespace Robust.Client.GameObjects
/// </summary>
public sealed class InputSystem : SharedInputSystem, IPostInjectInit
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientGameStateManager _stateManager = default!;
@@ -82,18 +83,35 @@ namespace Robust.Client.GameObjects
}
}
// send it off to the server
var clientMsg = (ClientFullInputCmdMessage)message;
var fullMsg = new FullInputCmdMessage(
clientMsg.Tick,
clientMsg.SubTick,
(int)clientMsg.InputSequence,
clientMsg.InputFunctionId,
clientMsg.State,
GetNetCoordinates(clientMsg.Coordinates),
clientMsg.ScreenCoordinates)
var clientMsg = message switch
{
Uid = GetNetEntity(clientMsg.Uid)
ClientFullInputCmdMessage clientInput => clientInput,
FullInputCmdMessage fullInput => new ClientFullInputCmdMessage(
fullInput.Tick,
fullInput.SubTick,
fullInput.InputFunctionId,
GetCoordinates(fullInput.Coordinates),
fullInput.ScreenCoordinates,
fullInput.State,
GetEntity(fullInput.Uid)),
_ => throw new ArgumentOutOfRangeException()
};
var fullMsg = message switch
{
FullInputCmdMessage fullInput => fullInput,
ClientFullInputCmdMessage client => new FullInputCmdMessage(
client.Tick,
client.SubTick,
client.InputFunctionId,
clientMsg.State,
GetNetCoordinates(client.Coordinates),
clientMsg.ScreenCoordinates,
GetNetEntity(clientMsg.Uid)
),
_ => throw new ArgumentOutOfRangeException()
};
DispatchInputCommand(clientMsg, fullMsg);
@@ -131,7 +149,7 @@ namespace Robust.Client.GameObjects
_conHost.RegisterCommand("incmd",
"Inserts an input command into the simulation",
"incmd <KeyFunction> <d|u KeyState> <wxPos> <wyPos>",
"incmd <KeyFunction> <KeyState> [wxPos] [wyPos]",
GenerateInputCommand);
}
@@ -147,17 +165,47 @@ namespace Robust.Client.GameObjects
if (_playerManager.LocalEntity is not { } pent)
return;
BoundKeyFunction keyFunction = new BoundKeyFunction(args[0]);
BoundKeyState state = args[1] == "u" ? BoundKeyState.Up: BoundKeyState.Down;
if (args.Length is not (2 or 4))
{
shell.WriteLine(Loc.GetString($"cmd-invalid-arg-number-error"));
return;
}
var pxform = Transform(pent);
var wPos = pxform.WorldPosition + new Vector2(float.Parse(args[2]), float.Parse(args[3]));
var coords = EntityCoordinates.FromMap(pent, new MapCoordinates(wPos, pxform.MapID), _transform, EntityManager);
var keyFunction = new BoundKeyFunction(args[0]);
if (!Enum.TryParse<BoundKeyState>(args[1], out var state))
{
shell.WriteLine(Loc.GetString("cmd-parse-failure-enum", ("arg", args[1]), ("enum", nameof(BoundKeyState))));
return;
}
var wOffset = Vector2.Zero;
if (args.Length == 4)
{
if (!float.TryParse(args[2], out var wX))
{
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[2])));
return;
}
if (!float.TryParse(args[3], out var wY))
{
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[3])));
return;
}
wOffset = new Vector2(wX, wY);
}
var coords = EntityCoordinates.FromMap(pent, _transform.GetMapCoordinates(pent).Offset(wOffset), _transform, EntityManager);
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state,
GetNetCoordinates(coords), new ScreenCoordinates(0, 0, default), NetEntity.Invalid);
var message = new ClientFullInputCmdMessage(_timing.CurTick,
_timing.TickFraction,
funcId,
coords,
new ScreenCoordinates(0, 0, default),
state,
EntityUid.Invalid);
HandleInputCommand(_playerManager.LocalSession, keyFunction, message);
}

View File

@@ -101,18 +101,16 @@ namespace Robust.Client.Graphics
}
/// <inheritdoc />
public void GetScreenProjectionMatrix(out Matrix3 projMatrix)
public void GetScreenProjectionMatrix(out Matrix3x2 projMatrix)
{
Matrix3 result = default;
Matrix3x2 result = default;
result.R0C0 = PixelsPerMeter;
result.R1C1 = -PixelsPerMeter;
result.M11 = PixelsPerMeter;
result.M22 = -PixelsPerMeter;
var screenSize = _displayManager.ScreenSize;
result.R0C2 = screenSize.X / 2f;
result.R1C2 = screenSize.Y / 2f;
result.R2C2 = 1;
result.M31 = screenSize.X / 2f;
result.M32 = screenSize.Y / 2f;
/* column major
Sx 0 Tx

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Numerics;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Graphics;
@@ -44,7 +44,7 @@ namespace Robust.Client.Graphics
/// to UI screen space.
/// </summary>
/// <param name="projMatrix"></param>
void GetScreenProjectionMatrix(out Matrix3 projMatrix);
void GetScreenProjectionMatrix(out Matrix3x2 projMatrix);
/// <summary>
/// Projects a point from world space to UI screen space using the main viewport.

View File

@@ -367,7 +367,7 @@ namespace Robust.Client.Graphics.Clyde
_renderHandle.UseShader(entry.Sprite.PostShader);
CalcScreenMatrices(viewport.Size, out var proj, out var view);
_renderHandle.SetProjView(proj, view);
_renderHandle.SetModelTransform(Matrix3.Identity);
_renderHandle.SetModelTransform(Matrix3x2.Identity);
var rounded = roundedPos - entityPostRenderTarget.Size / 2;
@@ -530,7 +530,7 @@ namespace Robust.Client.Graphics.Clyde
// Because the math is wrong.
// So there are distortions from incorrect projection.
_renderHandle.UseShader(_fovDebugShaderInstance);
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3.Identity);
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3x2.Identity);
var pos = UIBox2.FromDimensions(viewport.Size / 2 - new Vector2(200, 200), new Vector2(400, 400));
_renderHandle.DrawingHandleScreen.DrawTextureRect(FovTexture, pos);
}
@@ -538,7 +538,7 @@ namespace Robust.Client.Graphics.Clyde
if (DebugLayers == ClydeDebugLayers.Light)
{
_renderHandle.UseShader(null);
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3.Identity);
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3x2.Identity);
_renderHandle.DrawingHandleScreen.DrawTextureRect(
viewport.WallBleedIntermediateRenderTarget2.Texture,
UIBox2.FromDimensions(Vector2.Zero, viewport.Size), new Color(1, 1, 1, 0.5f));

View File

@@ -98,28 +98,34 @@ namespace Robust.Client.Graphics.Clyde
[FieldOffset(20 * sizeof(float))] public Vector3 ViewMatrixC2;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ProjViewMatrices(in Matrix3 projMatrix, in Matrix3 viewMatrix)
public ProjViewMatrices(in Matrix3x2 projMatrix, in Matrix3x2 viewMatrix)
{
ProjMatrixC0 = new Vector3(projMatrix.R0C0, projMatrix.R1C0, projMatrix.R2C0);
ProjMatrixC1 = new Vector3(projMatrix.R0C1, projMatrix.R1C1, projMatrix.R2C1);
ProjMatrixC2 = new Vector3(projMatrix.R0C2, projMatrix.R1C2, projMatrix.R2C2);
// We put the rows of the input matrix into the columns of our GPU matrices
// this transpose is required, as in C#, we premultiply vectors with matrices
// (vM) while GL postmultiplies vectors with matrices (Mv); however, since
// the Matrix3x2 data is stored row-major, and GL uses column-major, the
// memory layout is the same (or would be, if Matrix3x2 didn't have an
// implicit column)
ProjMatrixC0 = new Vector3(projMatrix.M11, projMatrix.M12, 0);
ProjMatrixC1 = new Vector3(projMatrix.M21, projMatrix.M22, 0);
ProjMatrixC2 = new Vector3(projMatrix.M31, projMatrix.M32, 1);
ViewMatrixC0 = new Vector3(viewMatrix.R0C0, viewMatrix.R1C0, viewMatrix.R2C0);
ViewMatrixC1 = new Vector3(viewMatrix.R0C1, viewMatrix.R1C1, viewMatrix.R2C1);
ViewMatrixC2 = new Vector3(viewMatrix.R0C2, viewMatrix.R1C2, viewMatrix.R2C2);
ViewMatrixC0 = new Vector3(viewMatrix.M11, viewMatrix.M12, 0);
ViewMatrixC1 = new Vector3(viewMatrix.M21, viewMatrix.M22, 0);
ViewMatrixC2 = new Vector3(viewMatrix.M31, viewMatrix.M32, 1);
}
public void Apply(Clyde clyde, GLShaderProgram program)
{
program.SetUniformMaybe("projectionMatrix", new Matrix3(
ProjMatrixC0.X, ProjMatrixC1.X, ProjMatrixC2.X,
ProjMatrixC0.Y, ProjMatrixC1.Y, ProjMatrixC2.Y,
ProjMatrixC0.Z, ProjMatrixC1.Z, ProjMatrixC2.Z
program.SetUniformMaybe("projectionMatrix", new Matrix3x2(
ProjMatrixC0.X, ProjMatrixC0.Y, // Implicit 0
ProjMatrixC1.X, ProjMatrixC1.Y, // Implicit 0
ProjMatrixC2.X, ProjMatrixC2.Y // Implicit 1
));
program.SetUniformMaybe("viewMatrix", new Matrix3(
ViewMatrixC0.X, ViewMatrixC1.X, ViewMatrixC2.X,
ViewMatrixC0.Y, ViewMatrixC1.Y, ViewMatrixC2.Y,
ViewMatrixC0.Z, ViewMatrixC1.Z, ViewMatrixC2.Z
program.SetUniformMaybe("viewMatrix", new Matrix3x2(
ViewMatrixC0.X, ViewMatrixC0.Y, // Implicit 0
ViewMatrixC1.X, ViewMatrixC1.Y, // Implicit 0
ViewMatrixC2.X, ViewMatrixC2.Y // Implicit 1
));
}
}

View File

@@ -492,18 +492,18 @@ namespace Robust.Client.Graphics.Clyde
var offset = new Vector2(component.Radius, component.Radius);
Matrix3 matrix;
Matrix3x2 matrix;
if (mask == null)
{
matrix = Matrix3.Identity;
matrix = Matrix3x2.Identity;
}
else
{
// Only apply rotation if a mask is said, because else it doesn't matter.
matrix = Matrix3.CreateRotation(rotation);
matrix = Matrix3Helpers.CreateRotation(rotation);
}
(matrix.R0C2, matrix.R1C2) = lightPos;
(matrix.M31, matrix.M32) = lightPos;
_drawQuad(-offset, offset, matrix, lightShader);
}
@@ -692,7 +692,7 @@ namespace Robust.Client.Graphics.Clyde
// Blur horizontally to _wallBleedIntermediateRenderTarget1.
shader.SetUniformMaybe("direction", Vector2.UnitX);
_drawQuad(Vector2.Zero, viewport.Size, Matrix3.Identity, shader);
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
SetTexture(TextureUnit.Texture0, viewport.LightBlurTarget.Texture);
@@ -700,7 +700,7 @@ namespace Robust.Client.Graphics.Clyde
// Blur vertically to _wallBleedIntermediateRenderTarget2.
shader.SetUniformMaybe("direction", Vector2.UnitY);
_drawQuad(Vector2.Zero, viewport.Size, Matrix3.Identity, shader);
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
SetTexture(TextureUnit.Texture0, viewport.LightRenderTarget.Texture);
}
@@ -754,14 +754,14 @@ namespace Robust.Client.Graphics.Clyde
// Blur horizontally to _wallBleedIntermediateRenderTarget1.
shader.SetUniformMaybe("direction", Vector2.UnitX);
_drawQuad(Vector2.Zero, viewport.Size, Matrix3.Identity, shader);
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
SetTexture(TextureUnit.Texture0, viewport.WallBleedIntermediateRenderTarget1.Texture);
BindRenderTargetFull(viewport.WallBleedIntermediateRenderTarget2);
// Blur vertically to _wallBleedIntermediateRenderTarget2.
shader.SetUniformMaybe("direction", Vector2.UnitY);
_drawQuad(Vector2.Zero, viewport.Size, Matrix3.Identity, shader);
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
SetTexture(TextureUnit.Texture0, viewport.WallBleedIntermediateRenderTarget2.Texture);
}
@@ -909,13 +909,13 @@ namespace Robust.Client.Graphics.Clyde
// Second modification is that output must be fov-centred (difference-space)
uZero -= fovCentre;
var clipToDiff = new Matrix3(in uX, in uY, in uZero);
var clipToDiff = new Matrix3x2(uX.X, uX.Y, uY.X, uY.Y, uZero.X, uZero.Y);
fovShader.SetUniformMaybe("clipToDiff", clipToDiff);
_drawQuad(Vector2.Zero, Vector2.One, Matrix3.Identity, fovShader);
_drawQuad(Vector2.Zero, Vector2.One, Matrix3x2.Identity, fovShader);
}
private void UpdateOcclusionGeometry(MapId map, Box2 expandedBounds, Matrix3 eyeTransform)
private void UpdateOcclusionGeometry(MapId map, Box2 expandedBounds, Matrix3x2 eyeTransform)
{
using var _ = _prof.Group("UpdateOcclusionGeometry");
using var _p = DebugGroup(nameof(UpdateOcclusionGeometry));
@@ -968,9 +968,9 @@ namespace Robust.Client.Graphics.Clyde
var worldTransform = xformSystem.GetWorldMatrix(transform, xforms);
var box = occluder.BoundingBox;
var tl = worldTransform.Transform(box.TopLeft);
var tr = worldTransform.Transform(box.TopRight);
var br = worldTransform.Transform(box.BottomRight);
var tl = Vector2.Transform(box.TopLeft, worldTransform);
var tr = Vector2.Transform(box.TopRight, worldTransform);
var br = Vector2.Transform(box.BottomRight, worldTransform);
var bl = tl + br - tr;
// Faces.
@@ -1010,9 +1010,9 @@ namespace Robust.Client.Graphics.Clyde
//
// Calculate delta positions from camera.
var dTl = eyeTransform.Transform(tl);
var dTr = eyeTransform.Transform(tr);
var dBl = eyeTransform.Transform(bl);
var dTl = Vector2.Transform(tl, eyeTransform);
var dTr = Vector2.Transform(tr, eyeTransform);
var dBl = Vector2.Transform(bl, eyeTransform);
var dBr = dBl + dTr - dTl;
// Get which neighbors are occluding.

View File

@@ -33,17 +33,17 @@ namespace Robust.Client.Graphics.Clyde
DrawingHandleWorld = new DrawingHandleWorldImpl(white, this);
}
public void SetModelTransform(in Matrix3 matrix)
public void SetModelTransform(in Matrix3x2 matrix)
{
_clyde.DrawSetModelTransform(matrix);
}
public Matrix3 GetModelTransform()
public Matrix3x2 GetModelTransform()
{
return _clyde.DrawGetModelTransform();
}
public void SetProjView(in Matrix3 proj, in Matrix3 view)
public void SetProjView(in Matrix3x2 proj, in Matrix3x2 view)
{
_clyde.DrawSetProjViewTransform(proj, view);
}
@@ -177,9 +177,9 @@ namespace Robust.Client.Graphics.Clyde
var oldModel = _clyde._currentMatrixModel;
var newModel = oldModel;
position += new Vector2(oldModel.R0C2, oldModel.R1C2);
newModel.R0C2 = 0;
newModel.R1C2 = 0;
position += new Vector2(oldModel.M31, oldModel.M32);
newModel.M31 = 0;
newModel.M32 = 0;
SetModelTransform(newModel);
// Switch rendering to pseudo-world space.
@@ -194,7 +194,7 @@ namespace Robust.Client.Graphics.Clyde
// Maaaaybe this is meant to have a minus sign.
var rot = -(float) eyeRot.Theta;
var view = Matrix3.CreateTransform(ofsX, ofsY, rot, scale.X, scale.Y);
var view = Matrix3Helpers.CreateTransform(ofsX, ofsY, rot, scale.X, scale.Y);
SetProjView(proj, view);
}
@@ -297,12 +297,12 @@ namespace Robust.Client.Graphics.Clyde
_renderHandle = renderHandle;
}
public override void SetTransform(in Matrix3 matrix)
public override void SetTransform(in Matrix3x2 matrix)
{
_renderHandle.SetModelTransform(matrix);
}
public override Matrix3 GetTransform()
public override Matrix3x2 GetTransform()
{
return _renderHandle.GetModelTransform();
}
@@ -402,12 +402,12 @@ namespace Robust.Client.Graphics.Clyde
_renderHandle = renderHandle;
}
public override void SetTransform(in Matrix3 matrix)
public override void SetTransform(in Matrix3x2 matrix)
{
_renderHandle.SetModelTransform(matrix);
}
public override Matrix3 GetTransform()
public override Matrix3x2 GetTransform()
{
return _renderHandle.GetModelTransform();
}

View File

@@ -62,7 +62,7 @@ namespace Robust.Client.Graphics.Clyde
// This matrix is applied to most normal geometry coming in.
// Some is applied while the batch is being created (e.g. simple texture draw calls).
// For DrawPrimitives OTOH the model matrix is passed along with the render command so is applied in the shader.
private Matrix3 _currentMatrixModel = Matrix3.Identity;
private Matrix3x2 _currentMatrixModel = Matrix3x2.Identity;
// Buffers and data for the batching system. Written into during (queue) and processed during (submit).
private readonly Vertex2D[] BatchVertexData = new Vertex2D[MaxBatchQuads * 4];
@@ -84,8 +84,8 @@ namespace Robust.Client.Graphics.Clyde
// Current projection & view matrices that are being used ot render.
// This gets updated to keep track during (queue) and (misc), but not during (submit).
private Matrix3 _currentMatrixProj;
private Matrix3 _currentMatrixView;
private Matrix3x2 _currentMatrixProj;
private Matrix3x2 _currentMatrixView;
// (queue) and (misc), current state of the scissor test. Null if disabled.
private UIBox2i? _currentScissorState;
@@ -110,25 +110,25 @@ namespace Robust.Client.Graphics.Clyde
UniformConstantsUBO.Reallocate(constants);
}
private void CalcScreenMatrices(in Vector2i screenSize, out Matrix3 proj, out Matrix3 view)
private void CalcScreenMatrices(in Vector2i screenSize, out Matrix3x2 proj, out Matrix3x2 view)
{
proj = Matrix3.Identity;
proj.R0C0 = 2f / screenSize.X;
proj.R1C1 = -2f / screenSize.Y;
proj.R0C2 = -1;
proj.R1C2 = 1;
proj = Matrix3x2.Identity;
proj.M11 = 2f / screenSize.X;
proj.M22 = -2f / screenSize.Y;
proj.M31 = -1;
proj.M32 = 1;
if (_currentRenderTarget.FlipY)
{
proj.R1C1 *= -1;
proj.R1C2 *= -1;
proj.M22 *= -1;
proj.M32 *= -1;
}
view = Matrix3.Identity;
view = Matrix3x2.Identity;
}
private void CalcWorldMatrices(in Vector2i screenSize, in Vector2 renderScale, IEye eye,
out Matrix3 proj, out Matrix3 view)
out Matrix3x2 proj, out Matrix3x2 view)
{
eye.GetViewMatrix(out view, renderScale);
@@ -136,20 +136,20 @@ namespace Robust.Client.Graphics.Clyde
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CalcWorldProjMatrix(in Vector2i screenSize, out Matrix3 proj)
private void CalcWorldProjMatrix(in Vector2i screenSize, out Matrix3x2 proj)
{
proj = Matrix3.Identity;
proj.R0C0 = EyeManager.PixelsPerMeter * 2f / screenSize.X;
proj.R1C1 = EyeManager.PixelsPerMeter * 2f / screenSize.Y;
proj = Matrix3x2.Identity;
proj.M11 = EyeManager.PixelsPerMeter * 2f / screenSize.X;
proj.M22 = EyeManager.PixelsPerMeter * 2f / screenSize.Y;
if (_currentRenderTarget.FlipY)
{
proj.R1C1 *= -1;
proj.R1C2 *= -1;
proj.M22 *= -1;
proj.M32 *= -1;
}
}
private void SetProjViewBuffer(in Matrix3 proj, in Matrix3 view)
private void SetProjViewBuffer(in Matrix3x2 proj, in Matrix3x2 view)
{
// TODO: Fix perf here.
// This immediately causes a glBufferData() call every time this is changed.
@@ -160,7 +160,7 @@ namespace Robust.Client.Graphics.Clyde
ProjViewUBO.Reallocate(combined);
}
private void SetProjViewFull(in Matrix3 proj, in Matrix3 view)
private void SetProjViewFull(in Matrix3x2 proj, in Matrix3x2 view)
{
_currentMatrixProj = proj;
_currentMatrixView = view;
@@ -285,21 +285,21 @@ namespace Robust.Client.Graphics.Clyde
};
}
private void _drawQuad(Vector2 a, Vector2 b, in Matrix3 modelMatrix, GLShaderProgram program)
private void _drawQuad(Vector2 a, Vector2 b, in Matrix3x2 modelMatrix, GLShaderProgram program)
{
DrawQuadWithVao(QuadVAO, a, b, modelMatrix, program);
}
private void DrawQuadWithVao(GLHandle vao, Vector2 a, Vector2 b, in Matrix3 modelMatrix,
private void DrawQuadWithVao(GLHandle vao, Vector2 a, Vector2 b, in Matrix3x2 modelMatrix,
GLShaderProgram program)
{
BindVertexArray(vao.Handle);
CheckGlError();
var rectTransform = Matrix3.Identity;
(rectTransform.R0C0, rectTransform.R1C1) = b - a;
(rectTransform.R0C2, rectTransform.R1C2) = a;
rectTransform.Multiply(modelMatrix);
var rectTransform = Matrix3x2.Identity;
(rectTransform.M11, rectTransform.M22) = b - a;
(rectTransform.M31, rectTransform.M32) = a;
rectTransform = rectTransform * modelMatrix;
program.SetUniformMaybe(UniIModelMatrix, rectTransform);
_debugStats.LastGLDrawCalls += 1;
@@ -315,7 +315,7 @@ namespace Robust.Client.Graphics.Clyde
FlushBatchQueue();
// Reset renderer state.
_currentMatrixModel = Matrix3.Identity;
_currentMatrixModel = Matrix3x2.Identity;
_queuedShaderInstance = _defaultShader;
SetScissorFull(null);
}
@@ -496,7 +496,7 @@ namespace Robust.Client.Graphics.Clyde
case bool b:
program.SetUniform(name, b ? 1 : 0);
break;
case Matrix3 matrix3:
case Matrix3x2 matrix3:
program.SetUniform(name, matrix3);
break;
case Matrix4 matrix4:
@@ -532,17 +532,17 @@ namespace Robust.Client.Graphics.Clyde
return ref command;
}
private void DrawSetModelTransform(in Matrix3 matrix)
private void DrawSetModelTransform(in Matrix3x2 matrix)
{
_currentMatrixModel = matrix;
}
private Matrix3 DrawGetModelTransform()
private Matrix3x2 DrawGetModelTransform()
{
return _currentMatrixModel;
}
private void DrawSetProjViewTransform(in Matrix3 proj, in Matrix3 view)
private void DrawSetProjViewTransform(in Matrix3x2 proj, in Matrix3x2 view)
{
BreakBatch();
@@ -571,9 +571,9 @@ namespace Robust.Client.Graphics.Clyde
EnsureBatchSpaceAvailable(4, GetQuadBatchIndexCount());
EnsureBatchState(texture, true, GetQuadBatchPrimitiveType(), _queuedShader);
bl = _currentMatrixModel.Transform(bl);
br = _currentMatrixModel.Transform(br);
tr = _currentMatrixModel.Transform(tr);
bl = Vector2.Transform(bl, _currentMatrixModel);
br = Vector2.Transform(br, _currentMatrixModel);
tr = Vector2.Transform(tr, _currentMatrixModel);
tl = tr + bl - br;
// TODO: split batch if necessary.
@@ -676,8 +676,8 @@ namespace Robust.Client.Graphics.Clyde
EnsureBatchSpaceAvailable(2, 0);
EnsureBatchState(_stockTextureWhite.TextureId, false, BatchPrimitiveType.LineList, _queuedShader);
a = _currentMatrixModel.Transform(a);
b = _currentMatrixModel.Transform(b);
a = Vector2.Transform(a, _currentMatrixModel);
b = Vector2.Transform(b, _currentMatrixModel);
// TODO: split batch if necessary.
var vIdx = BatchVertexIndex;
@@ -807,7 +807,7 @@ namespace Robust.Client.Graphics.Clyde
command.DrawBatch.ShaderInstance = metaData.ShaderInstance;
command.DrawBatch.Count = currentIndex - metaData.StartIndex;
command.DrawBatch.ModelMatrix = Matrix3.Identity;
command.DrawBatch.ModelMatrix = Matrix3x2.Identity;
_debugStats.LastBatches += 1;
}
@@ -882,7 +882,7 @@ namespace Robust.Client.Graphics.Clyde
_queuedRenderCommands.Clear();
_currentViewport = null;
_lightingReady = false;
_currentMatrixModel = Matrix3.Identity;
_currentMatrixModel = Matrix3x2.Identity;
SetScissorFull(null);
BindRenderTargetFull(_mainWindow!.RenderTarget);
_batchMetaData = null;
@@ -961,13 +961,13 @@ namespace Robust.Client.Graphics.Clyde
public BatchPrimitiveType PrimitiveType;
// TODO: this makes the render commands so much more large please remove.
public Matrix3 ModelMatrix;
public Matrix3x2 ModelMatrix;
}
private struct RenderCommandProjViewMatrix
{
public Matrix3 ProjMatrix;
public Matrix3 ViewMatrix;
public Matrix3x2 ProjMatrix;
public Matrix3x2 ViewMatrix;
}
private struct RenderCommandScissor
@@ -1056,11 +1056,11 @@ namespace Robust.Client.Graphics.Clyde
private readonly struct FullStoredRendererState
{
public readonly Matrix3 ProjMatrix;
public readonly Matrix3 ViewMatrix;
public readonly Matrix3x2 ProjMatrix;
public readonly Matrix3x2 ViewMatrix;
public readonly LoadedRenderTarget RenderTarget;
public FullStoredRendererState(in Matrix3 projMatrix, in Matrix3 viewMatrix,
public FullStoredRendererState(in Matrix3x2 projMatrix, in Matrix3x2 viewMatrix,
LoadedRenderTarget renderTarget)
{
ProjMatrix = projMatrix;

View File

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

View File

@@ -52,7 +52,7 @@ namespace Robust.Client.Graphics.Clyde
// view matrix
vp.Eye.GetViewMatrixInv(out var viewMatrixInv, vp.RenderScale);
point = viewMatrixInv * point;
point = Vector2.Transform(point, viewMatrixInv);
return point;
}
@@ -136,7 +136,7 @@ namespace Robust.Client.Graphics.Clyde
// view matrix
Eye.GetViewMatrixInv(out var viewMatrixInv, RenderScale);
newPoint = viewMatrixInv * newPoint;
newPoint = Vector2.Transform(newPoint, viewMatrixInv);
return new MapCoordinates(newPoint, Eye.Position.MapId);
}
@@ -150,7 +150,7 @@ namespace Robust.Client.Graphics.Clyde
var newPoint = point;
eye.GetViewMatrix(out var viewMatrix, RenderScale);
newPoint = viewMatrix * newPoint;
newPoint = Vector2.Transform(newPoint, viewMatrix);
// (inlined version of UiProjMatrix)
newPoint *= new Vector2(1, -1) * EyeManager.PixelsPerMeter;
@@ -159,14 +159,14 @@ namespace Robust.Client.Graphics.Clyde
return newPoint;
}
public Matrix3 GetWorldToLocalMatrix()
public Matrix3x2 GetWorldToLocalMatrix()
{
if (Eye == null)
return Matrix3.Identity;
return Matrix3x2.Identity;
Eye.GetViewMatrix(out var viewMatrix, RenderScale * new Vector2(EyeManager.PixelsPerMeter, -EyeManager.PixelsPerMeter));
viewMatrix.R0C2 += Size.X / 2f;
viewMatrix.R1C2 += Size.Y / 2f;
viewMatrix.M31 += Size.X / 2f;
viewMatrix.M32 += Size.Y / 2f;
return viewMatrix;
}

View File

@@ -361,7 +361,7 @@ namespace Robust.Client.Graphics.Clyde
{
}
private protected override void SetParameterImpl(string name, in Matrix3 value)
private protected override void SetParameterImpl(string name, in Matrix3x2 value)
{
}
@@ -482,7 +482,7 @@ namespace Robust.Client.Graphics.Clyde
return default;
}
public Matrix3 GetWorldToLocalMatrix() => default;
public Matrix3x2 GetWorldToLocalMatrix() => default;
public Vector2 WorldToLocal(Vector2 point)
{

View File

@@ -247,28 +247,33 @@ namespace Robust.Client.Graphics.Clyde
GL.Uniform1(uniformId, singles.Length, singles);
}
public void SetUniform(string uniformName, in Matrix3 matrix)
public void SetUniform(string uniformName, in Matrix3x2 matrix)
{
var uniformId = GetUniform(uniformName);
SetUniformDirect(uniformId, matrix);
}
public void SetUniform(int uniformName, in Matrix3 matrix)
public void SetUniform(int uniformName, in Matrix3x2 matrix)
{
var uniformId = GetUniform(uniformName);
SetUniformDirect(uniformId, matrix);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void SetUniformDirect(int slot, in Matrix3 value, bool transpose=true)
private unsafe void SetUniformDirect(int slot, in Matrix3x2 value)
{
Matrix3 tmpTranspose = value;
if (transpose)
{
// transposition not supported on GLES2, & no access to _hasGLES
tmpTranspose.Transpose();
}
GL.UniformMatrix3(slot, 1, false, (float*) &tmpTranspose);
// We put the rows of the input matrix into the columns of our GPU matrices
// this transpose is required, as in C#, we premultiply vectors with matrices
// (vM) while GL postmultiplies vectors with matrices (Mv); however, since
// the Matrix3x2 data is stored row-major, and GL uses column-major, the
// memory layout is the same (or would be, if Matrix3x2 didn't have an
// implicit column)
var buf = stackalloc float[9]{
value.M11, value.M12, 0,
value.M21, value.M22, 0,
value.M31, value.M32, 1
};
GL.UniformMatrix3(slot, 1, false, (float*)buf);
_clyde.CheckGlError();
}
@@ -474,7 +479,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
public void SetUniformMaybe(string uniformName, in Matrix3 value)
public void SetUniformMaybe(string uniformName, in Matrix3x2 value)
{
if (TryGetUniform(uniformName, out var slot))
{
@@ -490,7 +495,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
public void SetUniformMaybe(int uniformName, in Matrix3 value)
public void SetUniformMaybe(int uniformName, in Matrix3x2 value)
{
if (TryGetUniform(uniformName, out var slot))
{

View File

@@ -44,19 +44,19 @@ namespace Robust.Client.Graphics
{
CheckDisposed();
var matrix = Matrix3.CreateTransform(in position, in rotation, in scale);
var matrix = Matrix3Helpers.CreateTransform(in position, in rotation, in scale);
SetTransform(in matrix);
}
public void SetTransform(in Vector2 position, in Angle rotation)
{
var matrix = Matrix3.CreateTransform(in position, in rotation);
var matrix = Matrix3Helpers.CreateTransform(in position, in rotation);
SetTransform(in matrix);
}
public abstract void SetTransform(in Matrix3 matrix);
public abstract void SetTransform(in Matrix3x2 matrix);
public abstract Matrix3 GetTransform();
public abstract Matrix3x2 GetTransform();
public abstract void UseShader(ShaderInstance? shader);

View File

@@ -49,7 +49,7 @@ namespace Robust.Client.Graphics
/// <summary>
/// Matrix equivalent of <see cref="LocalToWorld(Vector2)"/>.
/// </summary>
Matrix3 GetWorldToLocalMatrix();
Matrix3x2 GetWorldToLocalMatrix();
/// <summary>
/// Converts a point in world-space to the viewport's screen coordinates.

View File

@@ -148,7 +148,7 @@ namespace Robust.Client.Graphics
SetParameterImpl(name, value);
}
public void SetParameter(string name, in Matrix3 value)
public void SetParameter(string name, in Matrix3x2 value)
{
EnsureAlive();
EnsureMutable();
@@ -219,7 +219,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, in Matrix3 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);
private protected abstract void SetStencilImpl(StencilParameters value);

View File

@@ -176,7 +176,7 @@ namespace Robust.Client.Graphics
return node.AsVector4();
}
case ShaderDataType.Mat3:
return node.AsMatrix3();
return node.AsMatrix3x2();
case ShaderDataType.Mat4:
return node.AsMatrix4();
default:
@@ -219,7 +219,7 @@ namespace Robust.Client.Graphics
case bool i:
instance.SetParameter(key, i);
break;
case Matrix3 i:
case Matrix3x2 i:
instance.SetParameter(key, i);
break;
case Matrix4 i:

View File

@@ -120,6 +120,6 @@ public sealed class TileEdgeOverlay : GridOverlay
}
}
args.WorldHandle.SetTransform(Matrix3.Identity);
args.WorldHandle.SetTransform(Matrix3x2.Identity);
}
}

View File

@@ -139,7 +139,7 @@ namespace Robust.Client.Physics
return true;
}, true);
worldHandle.SetTransform(Matrix3.Identity);
worldHandle.SetTransform(Matrix3x2.Identity);
}
}
}

View File

@@ -2,6 +2,7 @@ using System.Buffers;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;

View File

@@ -261,8 +261,8 @@ namespace Robust.Client.Player
{
// This is a new userid, so we create a new session.
DebugTools.Assert(state.UserId != LocalPlayer?.UserId);
var newSession = (CommonSession) CreateAndAddSession(state.UserId, state.Name);
newSession.Ping = state.Ping;
var newSession = (ICommonSessionInternal)CreateAndAddSession(state.UserId, state.Name);
newSession.SetPing(state.Ping);
SetStatus(newSession, state.Status);
SetAttachedEntity(newSession, controlled, out _, true);
dirty = true;
@@ -279,9 +279,9 @@ namespace Robust.Client.Player
}
dirty = true;
var local = (CommonSession) session;
local.Name = state.Name;
local.Ping = state.Ping;
var local = (ICommonSessionInternal)session;
local.SetName(state.Name);
local.SetPing(state.Ping);
SetStatus(local, state.Status);
SetAttachedEntity(local, controlled, out _, true);
}

View File

@@ -20,6 +20,31 @@ namespace Robust.Client.Replays.Loading;
// so that when jumping to tick 1001 the client only has to apply states for tick 1000 and 1001, instead of 0, 1, 2, ...
public sealed partial class ReplayLoadManager
{
// Scratch data used by UpdateEntityStates.
// Avoids copying changes for every change to an entity between checkpoints, instead copies once per checkpoint on
// first change. We can also use this to avoid building a dictionary of ComponentChange inside the inner loop.
private class UpdateScratchData
{
public Dictionary<ushort, ComponentChange> Changes;
public EntityState lastChange;
public HashSet<ushort>? netComps;
public UpdateScratchData(EntityState oldEntState)
{
Changes = oldEntState.ComponentChanges.Value.ToDictionary(x => x.NetID);
lastChange = oldEntState;
netComps = oldEntState.NetComponents;
}
public EntityState BakeChanges()
{
return new EntityState(lastChange.NetEntity,
Changes.Values.ToList(),
lastChange.EntityLastModified,
netComps);
}
}
public async Task<(CheckpointState[], TimeSpan[])> GenerateCheckpointsAsync(
ReplayMessage? initMessages,
HashSet<string> initialCvars,
@@ -138,6 +163,7 @@ public sealed partial class ReplayLoadManager
var stats_due_spawned = 0;
var stats_due_state = 0;
var modifiedEntities = new Dictionary<NetEntity, UpdateScratchData>();
for (var i = 1; i < states.Count; i++)
{
if (i % 10 == 0)
@@ -148,10 +174,10 @@ public sealed partial class ReplayLoadManager
DebugTools.Assert(curState.FromSequence <= lastState.ToSequence);
UpdatePlayerStates(curState.PlayerStates.Span, playerStates);
UpdateEntityStates(curState.EntityStates.Span, entStates, ref spawnedTracker, ref stateTracker, detached);
UpdateEntityStates(curState.EntityStates.Span, entStates, modifiedEntities, ref spawnedTracker, ref stateTracker, detached);
UpdateMessages(messages[i], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase);
ProcessQueue(curState.ToSequence, detachQueue, detached, entStates);
UpdateDeletions(curState.EntityDeletions, entStates, detached);
UpdateDeletions(curState.EntityDeletions, entStates, detached, modifiedEntities);
serverTime[i] = GetTime(curState.ToSequence) - initialTime;
ticksSinceLastCheckpoint++;
@@ -182,6 +208,8 @@ public sealed partial class ReplayLoadManager
ticksSinceLastCheckpoint = 0;
spawnedTracker = 0;
stateTracker = 0;
ApplyModifiedEntities(entStates, modifiedEntities);
var newState = new GameState(GameTick.Zero,
curState.ToSequence,
default,
@@ -339,16 +367,18 @@ public sealed partial class ReplayLoadManager
}
private void UpdateDeletions(NetListAsArray<NetEntity> entityDeletions,
Dictionary<NetEntity, EntityState> entStates, HashSet<NetEntity> detached)
Dictionary<NetEntity, EntityState> entStates, HashSet<NetEntity> detached, Dictionary<NetEntity, UpdateScratchData> modifiedEntities)
{
foreach (var ent in entityDeletions.Span)
{
entStates.Remove(ent);
detached.Remove(ent);
modifiedEntities.Remove(ent);
}
}
private void UpdateEntityStates(ReadOnlySpan<EntityState> span, Dictionary<NetEntity, EntityState> entStates,
Dictionary<NetEntity, UpdateScratchData> modified,
ref int spawnedTracker, ref int stateTracker, HashSet<NetEntity> detached)
{
foreach (var entState in span)
@@ -369,9 +399,22 @@ public sealed partial class ReplayLoadManager
continue;
}
// Get scratch versions (with write access) for entities modified since last checkpoint
UpdateScratchData? scratch;
if (!modified.TryGetValue(entState.NetEntity, out scratch))
{
scratch = new UpdateScratchData(oldEntState);
modified[entState.NetEntity] = scratch;
}
stateTracker++;
DebugTools.Assert(oldEntState.NetEntity == entState.NetEntity);
entStates[entState.NetEntity] = MergeStates(entState, oldEntState.ComponentChanges.Value, oldEntState.NetComponents);
// Note this does not change entStates, that change occurs later in ApplyModifiedEntities (to avoid early copies)
UpdateScratch(entState, scratch.Changes);
if (entState.NetComponents != null)
scratch.netComps = entState.NetComponents;
scratch.lastChange = entState;
#if DEBUG
foreach (var state in entStates[entState.NetEntity].ComponentChanges.Span)
@@ -382,6 +425,53 @@ public sealed partial class ReplayLoadManager
}
}
private void ApplyModifiedEntities(Dictionary<NetEntity, EntityState> entStates, Dictionary<NetEntity, UpdateScratchData> modifiedEntities)
{
foreach (var modified in modifiedEntities)
{
entStates[modified.Key] = modified.Value.BakeChanges();
}
modifiedEntities.Clear();
}
private void UpdateScratch(
EntityState newState,
Dictionary<ushort, ComponentChange> oldState)
{
// remove any deleted components
if (newState.NetComponents != null)
{
foreach (var change in oldState.Values)
{
if (!newState.NetComponents.Contains(change.NetID))
oldState.Remove(change.NetID);
}
}
foreach (var newCompState in newState.ComponentChanges.Value)
{
if (!oldState.TryGetValue(newCompState.NetID, out var existing))
{
// This is a new component
// I'm not 100% sure about this, but I think delta states should always be full states here?
DebugTools.Assert(newCompState.State is not IComponentDeltaState newDelta);
oldState[newCompState.NetID] = newCompState;
continue;
}
// Modify or replace existing component
if (newCompState.State is not IComponentDeltaState delta)
{
oldState[newCompState.NetID] = newCompState;
continue;
}
DebugTools.Assert(existing.State != null && existing.State is not IComponentDeltaState);
oldState[newCompState.NetID] = new ComponentChange(existing.NetID, delta.CreateNewFullState(existing.State!), newCompState.LastModifiedTick);
}
}
private EntityState MergeStates(
EntityState newState,
IReadOnlyCollection<ComponentChange> oldState,
@@ -420,7 +510,7 @@ public sealed partial class ReplayLoadManager
foreach (var compChange in newCompStates.Values)
{
// I'm not 100% sure about this, but I think delta states should always be full states here?
DebugTools.Assert(compChange.State is not IComponentDeltaState delta);
DebugTools.Assert(compChange.State is not IComponentDeltaState);
combined.Add(compChange);
}

View File

@@ -17,7 +17,7 @@ internal sealed partial class ReplayPlaybackManager
/// </summary>
/// <param name="index">The target tick/index. The actual checkpoint will have an index less than or equal to this.</param>
/// <param name="flushEntities">Whether to delete all entities</param>
public void ResetToNearestCheckpoint(int index, bool flushEntities)
public void ResetToNearestCheckpoint(int index, bool flushEntities, CheckpointState? checkpoint = null)
{
if (Replay == null)
throw new Exception("Not currently playing a replay");
@@ -25,7 +25,8 @@ internal sealed partial class ReplayPlaybackManager
if (flushEntities)
_entMan.FlushEntities();
var checkpoint = GetLastCheckpoint(Replay, index);
// Look up the desired checkpoint, unless our caller kindly provided one to us.
checkpoint ??= GetLastCheckpoint(Replay, index);
_sawmill.Info($"Resetting to checkpoint. From {Replay.CurrentIndex} to {checkpoint.Index}");
var st = new Stopwatch();

View File

@@ -1,6 +1,7 @@
using System;
using Robust.Client.GameObjects;
using Robust.Client.GameStates;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.Replays.Playback;
@@ -27,14 +28,19 @@ internal sealed partial class ReplayPlaybackManager
return;
}
Playing &= !pausePlayback;
value = Math.Clamp(value, 0, Replay.States.Count - 1);
if (value == Replay.CurrentIndex)
{
ScrubbingTarget = null;
return;
}
BeforeSetTick?.Invoke();
// Begin timing replay processing so we can abort when we run out of time (_replayMaxScrubTime)
var st = RStopwatch.StartNew();
bool skipEffectEvents = value > Replay.CurrentIndex + _visualEventThreshold;
if (value < Replay.CurrentIndex)
{
@@ -45,9 +51,12 @@ internal sealed partial class ReplayPlaybackManager
{
// If we are skipping many ticks into the future, we try to skip directly to a checkpoint instead of
// applying every tick.
var nextCheckpoint = GetNextCheckpoint(Replay, Replay.CurrentIndex);
if (nextCheckpoint.Index < value && nextCheckpoint.Index > Replay.CurrentIndex)
ResetToNearestCheckpoint(value, false);
var nextCheckpoint = GetLastCheckpoint(Replay, value);
// Sanity-Check that the checkpoint is actually BEFORE the desired position.
// Also check that it gets us closer to goal position than we already are.
if (nextCheckpoint.Index <= value && nextCheckpoint.Index > Replay.CurrentIndex)
ResetToNearestCheckpoint(value, false, nextCheckpoint);
}
_entMan.EntitySysManager.GetEntitySystem<ClientDirtySystem>().Reset();
@@ -75,6 +84,23 @@ internal sealed partial class ReplayPlaybackManager
DebugTools.Assert(Replay.LastApplied >= state.FromSequence);
DebugTools.Assert(Replay.LastApplied + 1 <= state.ToSequence);
Replay.LastApplied = state.ToSequence;
if (st.Elapsed.TotalMilliseconds > _replayMaxScrubTime)
{
// Out of time to advance replay this tick
// Note: We check at end of loop so we always advance at least 1 tick.
break;
}
}
// Use ScrubbingTarget to force a later invocation to continue moving towards the target tick
if (Replay.CurrentIndex < value)
{
ScrubbingTarget = value;
}
else
{
ScrubbingTarget = null;
}
AfterSetTick?.Invoke();

View File

@@ -52,6 +52,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
public ReplayData? Replay { get; private set; }
public NetUserId? Recorder => Replay?.Recorder;
private int _checkpointMinInterval;
private int _replayMaxScrubTime;
private int _visualEventThreshold;
public uint? AutoPauseCountdown { get; set; }
public int? ScrubbingTarget { get; set; }
@@ -94,6 +95,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
_sawmill = _logMan.GetSawmill("replay");
_metaId = _factory.GetRegistration(typeof(MetaDataComponent)).NetID!.Value;
_confMan.OnValueChanged(CVars.CheckpointMinInterval, (value) => _checkpointMinInterval = value, true);
_confMan.OnValueChanged(CVars.ReplayMaxScrubTime, (value) => _replayMaxScrubTime = value, true);
_confMan.OnValueChanged(CVars.ReplaySkipThreshold, (value) => _visualEventThreshold = value, true);
_client.RunLevelChanged += OnRunLevelChanged;
}

View File

@@ -567,7 +567,7 @@ namespace Robust.Client.UserInterface
public Vector2i Position;
public Color Modulate;
public UIBox2i? ScissorBox;
public ref Matrix3 CoordinateTransform;
public ref Matrix3x2 CoordinateTransform;
}
protected void RenderControl(ref ControlRenderArguments args, int childIndex, Vector2i position)

View File

@@ -1,3 +1,4 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.IoC;
@@ -20,19 +21,19 @@ public sealed class ColorSelectorStyleBox : StyleBoxTexture
/// <summary>
/// Base background colour.
/// </summary>
public Vector4 BaseColor;
public Robust.Shared.Maths.Vector4 BaseColor;
/// <summary>
/// Colour to add to the background colour along the X-axis.
/// I.e., from left to right the background colour will vary from (BaseColour) to (BaseColour + XAxis)
/// </summary>
public Vector4 XAxis;
public Robust.Shared.Maths.Vector4 XAxis;
/// <summary>
/// Colour to add to the background colour along the y-axis.
/// I.e., from left to right the background colour will vary from (BaseColour) to (BaseColour + XAxis)
/// </summary>
public Vector4 YAxis;
public Robust.Shared.Maths.Vector4 YAxis;
/// <summary>
/// If true, then <see cref="BaseColor"/>, <see cref="XAxis"/>, and <see cref="YAxis"/> will be interpreted as HSVa
@@ -53,7 +54,7 @@ public sealed class ColorSelectorStyleBox : StyleBoxTexture
var old = handle.GetShader();
handle.UseShader(_shader);
var globalPixelPos = handle.GetTransform().Transform(default);
var globalPixelPos = Vector2.Transform(default, handle.GetTransform());
_shader.SetParameter("size", box.Size);
_shader.SetParameter("offset", globalPixelPos);
_shader.SetParameter("xAxis", XAxis);
@@ -92,14 +93,14 @@ public sealed class ColorSelectorStyleBox : StyleBoxTexture
{
var colorData = Hsv
? Color.ToHsv(color)
: new Vector4(color.R, color.G, color.B, color.A);
: new Robust.Shared.Maths.Vector4(color.R, color.G, color.B, color.A);
SetBaseColor(colorData);
}
/// <summary>
/// Helper method that sets the base color by taking in some color and removing the components that are controlled by the x and y axes.
/// </summary>
public void SetBaseColor(Vector4 colorData)
public void SetBaseColor(Robust.Shared.Maths.Vector4 colorData)
{
BaseColor = colorData - colorData * XAxis - colorData * YAxis;
}

View File

@@ -0,0 +1,64 @@
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Robust.Client.UserInterface.Controls;
[Virtual]
public class EntityPrototypeView : SpriteView
{
private string? _currentPrototype;
private EntityUid? _ourEntity;
public EntityPrototypeView()
{
}
public EntityPrototypeView(EntProtoId? entProto, IEntityManager entMan) : base(entMan)
{
SetPrototype(entProto);
}
public void SetPrototype(EntProtoId? entProto)
{
SpriteSystem ??= EntMan.System<SpriteSystem>();
if (entProto == _currentPrototype
&& EntMan.TryGetComponent(Entity?.Owner, out MetaDataComponent? meta)
&& meta.EntityPrototype?.ID == _currentPrototype)
{
return;
}
_currentPrototype = entProto;
SetEntity(null);
if (_ourEntity != null)
{
EntMan.DeleteEntity(_ourEntity);
}
if (_currentPrototype != null)
{
_ourEntity = EntMan.Spawn(_currentPrototype);
SpriteSystem.ForceUpdate(_ourEntity.Value);
SetEntity(_ourEntity);
}
}
protected override void EnteredTree()
{
base.EnteredTree();
if (_currentPrototype != null)
SetPrototype(_currentPrototype);
}
protected override void ExitedTree()
{
base.ExitedTree();
if (!EntMan.Deleted(_ourEntity))
EntMan.QueueDeleteEntity(_ourEntity);
}
}

View File

@@ -14,9 +14,9 @@ namespace Robust.Client.UserInterface.Controls
[Virtual]
public class SpriteView : Control
{
private SpriteSystem? _sprite;
protected SpriteSystem? SpriteSystem;
private SharedTransformSystem? _transform;
private readonly IEntityManager _entMan;
protected readonly IEntityManager EntMan;
[ViewVariables]
public SpriteComponent? Sprite => Entity?.Comp1;
@@ -120,20 +120,26 @@ namespace Robust.Client.UserInterface.Controls
public SpriteView()
{
IoCManager.Resolve(ref _entMan);
IoCManager.Resolve(ref EntMan);
RectClipContent = true;
}
public SpriteView(IEntityManager entMan)
{
EntMan = entMan;
RectClipContent = true;
}
public SpriteView(EntityUid? uid, IEntityManager entMan)
{
_entMan = entMan;
EntMan = entMan;
RectClipContent = true;
SetEntity(uid);
}
public SpriteView(NetEntity uid, IEntityManager entMan)
{
_entMan = entMan;
EntMan = entMan;
RectClipContent = true;
SetEntity(uid);
}
@@ -154,8 +160,8 @@ namespace Robust.Client.UserInterface.Controls
if (Entity?.Owner == uid)
return;
if (!_entMan.TryGetComponent(uid, out SpriteComponent? sprite)
|| !_entMan.TryGetComponent(uid, out TransformComponent? xform))
if (!EntMan.TryGetComponent(uid, out SpriteComponent? sprite)
|| !EntMan.TryGetComponent(uid, out TransformComponent? xform))
{
Entity = null;
NetEnt = null;
@@ -163,7 +169,7 @@ namespace Robust.Client.UserInterface.Controls
}
Entity = new(uid.Value, sprite, xform);
NetEnt = _entMan.GetNetEntity(uid);
NetEnt = EntMan.GetNetEntity(uid);
}
protected override Vector2 MeasureOverride(Vector2 availableSize)
@@ -223,11 +229,11 @@ namespace Robust.Client.UserInterface.Controls
if (!ResolveEntity(out var uid, out var sprite, out var xform))
return;
_sprite ??= _entMan.System<SpriteSystem>();
_transform ??= _entMan.System<TransformSystem>();
SpriteSystem ??= EntMan.System<SpriteSystem>();
_transform ??= EntMan.System<TransformSystem>();
// Ensure the sprite is animated despite possible not being visible in any viewport.
_sprite.ForceUpdate(uid);
SpriteSystem.ForceUpdate(uid);
var stretchVec = Stretch switch
{
@@ -258,13 +264,13 @@ namespace Robust.Client.UserInterface.Controls
[NotNullWhen(true)] out SpriteComponent? sprite,
[NotNullWhen(true)] out TransformComponent? xform)
{
if (NetEnt != null && Entity == null && _entMan.TryGetEntity(NetEnt, out var ent))
if (NetEnt != null && Entity == null && EntMan.TryGetEntity(NetEnt, out var ent))
SetEntity(ent);
if (Entity != null)
{
(uid, sprite, xform) = Entity.Value;
return !_entMan.Deleted(uid);
return !EntMan.Deleted(uid);
}
sprite = null;

View File

@@ -43,11 +43,11 @@ namespace Robust.Client.UserInterface.CustomControls
/// <remarks>
/// This is generally just be a combination of <see cref="IClydeViewport.GetWorldToLocalMatrix"/> and <see cref="GetLocalToScreenMatrix"/>
/// </remarks>
Matrix3 GetWorldToScreenMatrix();
Matrix3x2 GetWorldToScreenMatrix();
/// <summary>
/// Returns a matrix that can be used to transform from view-port local to screen coordinates.
/// </summary>
Matrix3 GetLocalToScreenMatrix();
Matrix3x2 GetLocalToScreenMatrix();
}
}

View File

@@ -155,17 +155,17 @@ namespace Robust.Client.UserInterface.CustomControls
return WorldToLocalPixel(point) + GlobalPixelPosition;
}
public Matrix3 GetWorldToScreenMatrix()
public Matrix3x2 GetWorldToScreenMatrix()
{
if (Viewport == null)
return Matrix3.Identity;
return Matrix3x2.Identity;
return Viewport.GetWorldToLocalMatrix() * GetLocalToScreenMatrix();
}
public Matrix3 GetLocalToScreenMatrix()
public Matrix3x2 GetLocalToScreenMatrix()
{
return Matrix3.CreateTransform(GlobalPixelPosition, 0, Vector2.One / _viewportResolution);
return Matrix3Helpers.CreateTransform(GlobalPixelPosition, 0, Vector2.One / _viewportResolution);
}
}
}

View File

@@ -1,4 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.Input;
@@ -63,7 +63,7 @@ namespace Robust.Client.UserInterface
Color GetMainClearColor();
void RenderControl(IRenderHandle renderHandle, ref int total, Control control, Vector2i position, Color modulate,
UIBox2i? scissorBox, Matrix3 coordinateTransform);
UIBox2i? scissorBox, Matrix3x2 coordinateTransform);
}
}

View File

@@ -130,9 +130,9 @@ internal sealed partial class UserInterfaceManager
{
var total = 0;
var drawingHandle = renderHandle.DrawingHandleScreen;
drawingHandle.SetTransform(Matrix3.Identity);
RenderControl(renderHandle, ref total, root, Vector2i.Zero, Color.White, null, Matrix3.Identity);
drawingHandle.SetTransform(Matrix3.Identity);
drawingHandle.SetTransform(Matrix3x2.Identity);
RenderControl(renderHandle, ref total, root, Vector2i.Zero, Color.White, null, Matrix3x2.Identity);
drawingHandle.SetTransform(Matrix3x2.Identity);
OnPostDrawUIRoot?.Invoke(new PostDrawUIRootEventArgs(root, drawingHandle));
_prof.WriteValue("Controls rendered", ProfData.Int32(total));

View File

@@ -330,7 +330,7 @@ namespace Robust.Client.UserInterface
}
public void RenderControl(IRenderHandle renderHandle, ref int total, Control control, Vector2i position, Color modulate,
UIBox2i? scissorBox, Matrix3 coordinateTransform)
UIBox2i? scissorBox, Matrix3x2 coordinateTransform)
{
if (!control.Visible)
{
@@ -378,8 +378,7 @@ namespace Robust.Client.UserInterface
var handle = renderHandle.DrawingHandleScreen;
var oldXform = handle.GetTransform();
var xform = oldXform;
xform.Multiply(Matrix3.CreateTransform(position, Angle.Zero, Vector2.One));
var xform = oldXform * Matrix3Helpers.CreateTransform(position, Angle.Zero, Vector2.One);
handle.SetTransform(xform);
modulate *= control.Modulate;
@@ -406,7 +405,7 @@ namespace Robust.Client.UserInterface
foreach (var child in control.Children)
{
var pos = position + (Vector2i) coordinateTransform.Transform(child.PixelPosition);
var pos = position + (Vector2i)Vector2.Transform(child.PixelPosition, coordinateTransform);
control.RenderChildOverride(ref args, child.GetPositionInParent(), pos);
}

View File

@@ -4,22 +4,6 @@ namespace Robust.Client.Utility
{
internal static class OpenTKConversions
{
public static OpenToolkit.Mathematics.Matrix3 ConvertOpenTK(this Matrix3 matrix)
{
return new()
{
M11 = matrix.R0C0,
M12 = matrix.R0C1,
M13 = matrix.R0C2,
M21 = matrix.R1C0,
M22 = matrix.R1C1,
M23 = matrix.R1C2,
M31 = matrix.R2C0,
M32 = matrix.R2C1,
M33 = matrix.R2C2
};
}
public static OpenToolkit.Mathematics.Color4 ConvertOpenTK(this Color color)
{
return new(color.R, color.G, color.B, color.A);

View File

@@ -42,6 +42,17 @@ public sealed partial class AudioSystem : SharedAudioSystem
component.Source = new DummyAudioSource();
}
public override void SetGridAudio(Entity<AudioComponent>? entity)
{
if (entity == null)
return;
base.SetGridAudio(entity);
// Need to global override so everyone can hear it.
_pvs.AddGlobalOverride(entity.Value.Owner);
}
public override void SetMapAudio(Entity<AudioComponent>? audio)
{
if (audio == null)
@@ -81,11 +92,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
{
var entity = Spawn("Audio", MapCoordinates.Nullspace);
var audio = SetupAudio(entity, filename, audioParams);
AddAudioFilter(entity, audio, playerFilter);
audio.Global = true;
return (entity, audio);
var entity = SetupAudio(filename, audioParams);
AddAudioFilter(entity, entity.Comp, playerFilter);
entity.Comp.Global = true;
return (entity, entity.Comp);
}
/// <inheritdoc />
@@ -95,16 +105,14 @@ public sealed partial class AudioSystem : SharedAudioSystem
return null;
if (TerminatingOrDeleted(uid))
{
Log.Error($"Tried to play audio on a terminating / deleted entity {ToPrettyString(uid)}. Trace: {Environment.StackTrace}");
return null;
}
var entity = Spawn("Audio", new EntityCoordinates(uid, Vector2.Zero));
var audio = SetupAudio(entity, filename, audioParams);
AddAudioFilter(entity, audio, playerFilter);
var entity = SetupAudio(filename, audioParams);
// Move it after setting it up
XformSystem.SetCoordinates(entity, new EntityCoordinates(uid, Vector2.Zero));
AddAudioFilter(entity, entity.Comp, playerFilter);
return (entity, audio);
return (entity, entity.Comp);
}
/// <inheritdoc />
@@ -114,15 +122,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
return null;
if (TerminatingOrDeleted(uid))
{
Log.Error($"Tried to play audio on a terminating / deleted entity {ToPrettyString(uid)}. Trace: {Environment.StackTrace}");
return null;
}
var entity = Spawn("Audio", new EntityCoordinates(uid, Vector2.Zero));
var audio = SetupAudio(entity, filename, audioParams);
var entity = SetupAudio(filename, audioParams);
XformSystem.SetCoordinates(entity, new EntityCoordinates(uid, Vector2.Zero));
return (entity, audio);
return (entity, entity.Comp);
}
/// <inheritdoc />
@@ -140,11 +145,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
if (!coordinates.IsValid(EntityManager))
return null;
var entity = Spawn("Audio", coordinates);
var audio = SetupAudio(entity, filename, audioParams);
AddAudioFilter(entity, audio, playerFilter);
var entity = SetupAudio(filename, audioParams);
XformSystem.SetCoordinates(entity, coordinates);
AddAudioFilter(entity, entity.Comp, playerFilter);
return (entity, audio);
return (entity, entity.Comp);
}
/// <inheritdoc />
@@ -163,10 +168,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
if (!coordinates.IsValid(EntityManager))
return null;
var entity = Spawn("Audio", coordinates);
var audio = SetupAudio(entity, filename, audioParams);
// TODO: Transform TryFindGridAt mess + optimisation required.
var entity = SetupAudio(filename, audioParams);
XformSystem.SetCoordinates(entity, coordinates);
return (entity, audio);
return (entity, entity.Comp);
}
/// <inheritdoc />

View File

@@ -844,7 +844,7 @@ public sealed class MapLoaderSystem : EntitySystem
if (xformQuery.TryGetComponent(rootEntity, out var xform) && IsRoot(xform, mapQuery) && !HasComp<MapComponent>(rootEntity))
{
_transform.SetLocalPosition(xform, data.Options.TransformMatrix.Transform(xform.LocalPosition));
_transform.SetLocalPosition(xform, Vector2.Transform(xform.LocalPosition, data.Options.TransformMatrix));
xform.LocalRotation += data.Options.Rotation;
}
}
@@ -856,7 +856,7 @@ public sealed class MapLoaderSystem : EntitySystem
if (xformQuery.TryGetComponent(entity, out var xform) && IsRoot(xform, mapQuery))
{
// Don't want to trigger events
xform._localPosition = data.Options.TransformMatrix.Transform(xform.LocalPosition);
xform._localPosition = Vector2.Transform(xform.LocalPosition, data.Options.TransformMatrix);
if (!xform.NoLocalRotation)
xform._localRotation += data.Options.Rotation;

View File

@@ -1,15 +1,10 @@
using Robust.Server.GameStates;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Player;
namespace Robust.Server.GameObjects;
public sealed class ServerMetaDataSystem : MetaDataSystem
{
[Dependency] private readonly PvsSystem _pvsSystem = default!;
private EntityQuery<MetaDataComponent> _mQuery;
public override void Initialize()
{
base.Initialize();
@@ -18,7 +13,6 @@ public sealed class ServerMetaDataSystem : MetaDataSystem
EntityManager.ComponentAdded += OnComponentAdded;
EntityManager.ComponentRemoved += OnComponentRemoved;
_mQuery = GetEntityQuery<MetaDataComponent>();
}
public override void Shutdown()

View File

@@ -1,9 +1,32 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Robust.Server.GameStates;
using Robust.Shared.GameObjects;
using Robust.Shared.Utility;
namespace Robust.Server.GameObjects;
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<UserInterfaceUserComponent, ExpandPvsEvent>(OnBuiUserExpand);
}
private void OnBuiUserExpand(Entity<UserInterfaceUserComponent> ent, ref ExpandPvsEvent args)
{
var buis = ent.Comp.OpenInterfaces.Keys;
if (buis.Count == 0)
return;
args.Entities ??= new List<EntityUid>(buis.Count);
foreach (var ui in buis)
{
DebugTools.Assert(ent.Comp.OpenInterfaces[ui].Count > 0);
DebugTools.Assert(HasComp<UserInterfaceComponent>(ui));
args.Entities.Add(ui);
}
}
}

View File

@@ -62,7 +62,7 @@ internal sealed class PvsChunk
/// <summary>
/// The <see cref="Root"/>'s inverse world matrix.
/// </summary>
public Matrix3 InvWorldMatrix { get; set; }
public Matrix3x2 InvWorldMatrix { get; set; }
// These are only used while populating the chunk. They aren't local variables because the chunks are pooled, so
// the same chunk can be repopulated more than once.

View File

@@ -72,7 +72,7 @@ internal sealed partial class PvsSystem
var xform = Transform(chunk.Root);
DebugTools.AssertEqual(chunk.Map.Owner, xform.MapUid);
chunk.InvWorldMatrix = xform.InvLocalMatrix;
var worldPos = xform.LocalMatrix.Transform(chunk.Centre);
var worldPos = Vector2.Transform(chunk.Centre, xform.LocalMatrix);
chunk.Position = new(worldPos, xform.MapID);
chunk.UpdateQueued = false;
}
@@ -137,7 +137,7 @@ internal sealed partial class PvsSystem
foreach (var (grid, _) in _grids)
{
var localPos = _transform.GetInvWorldMatrix(grid).Transform(viewPos);
var localPos = Vector2.Transform(viewPos, _transform.GetInvWorldMatrix(grid));
var gridChunkEnumerator = new ChunkIndicesEnumerator(localPos, range, ChunkSize);
while (gridChunkEnumerator.MoveNext(out var gridChunkIndices))
{

View File

@@ -100,7 +100,13 @@ internal sealed partial class PvsSystem
/// </summary>
private bool RecursivelyAddOverride(PvsSession session, EntityUid uid, GameTick fromTick, bool addChildren)
{
var xform = _xformQuery.GetComponent(uid);
if (!_xformQuery.TryGetComponent(uid, out var xform))
{
// Can happen if systems add deleted entities to PVS move event.
Log.Error($"Attempted to add non-existent entity {uid} to PVS override for session {session.Session}");
return false;
}
var parent = xform.ParentUid;
// First we process all parents. This is because while this entity may already have been added

View File

@@ -7,6 +7,7 @@ using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Player;
using Robust.Shared.Timing;
@@ -42,7 +43,7 @@ internal sealed partial class PvsSystem
// PVS benchmarks use dummy sessions.
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (session.Channel != null)
if (session.Channel is not DummyChannel)
{
_netMan.ServerSendMessage(msg, session.Channel);
if (msg.ShouldSendReliably())
@@ -163,7 +164,7 @@ internal sealed partial class PvsSystem
dist = Math.Min(dist, (pos.Position - chunk.Position.Position).LengthSquared());
var relative = chunk.InvWorldMatrix.Transform(pos.Position) - chunk.Centre;
var relative = Vector2.Transform(pos.Position, chunk.InvWorldMatrix) - chunk.Centre;
relative = Vector2.Abs(relative);
chebDist = Math.Min(chebDist, Math.Max(relative.X, relative.Y));
}

View File

@@ -1,4 +1,4 @@
using System.Numerics;
using System.Numerics;
using JetBrains.Annotations;
using Robust.Shared.Maths;
@@ -21,7 +21,7 @@ namespace Robust.Server.Maps
get => _offset;
set
{
TransformMatrix = Matrix3.CreateTransform(value, Rotation);
TransformMatrix = Matrix3Helpers.CreateTransform(value, Rotation);
_offset = value;
}
}
@@ -37,14 +37,14 @@ namespace Robust.Server.Maps
get => _rotation;
set
{
TransformMatrix = Matrix3.CreateTransform(Offset, value);
TransformMatrix = Matrix3Helpers.CreateTransform(Offset, value);
_rotation = value;
}
}
private Angle _rotation = Angle.Zero;
public Matrix3 TransformMatrix { get; set; } = Matrix3.Identity;
public Matrix3x2 TransformMatrix { get; set; } = Matrix3x2.Identity;
/// <summary>
/// If there is a map entity serialized should we also load it.

View File

@@ -32,7 +32,7 @@ public sealed partial class GridFixtureSystem
TransformComponent? xformA = null,
TransformComponent? xformB = null)
{
var matrix = Matrix3.CreateTransform(offset, rotation);
var matrix = Matrix3Helpers.CreateTransform(offset, rotation);
Merge(gridAUid, gridBUid, matrix, gridA, gridB, xformA, xformB);
}
@@ -45,7 +45,7 @@ public sealed partial class GridFixtureSystem
public void Merge(
EntityUid gridAUid,
EntityUid gridBUid,
Matrix3 matrix,
Matrix3x2 matrix,
MapGridComponent? gridA = null,
MapGridComponent? gridB = null,
TransformComponent? xformA = null,
@@ -63,7 +63,7 @@ public sealed partial class GridFixtureSystem
while (enumerator.MoveNext(out var tileRef))
{
var offsetTile = matrix.Transform(new Vector2(tileRef.Value.GridIndices.X, tileRef.Value.GridIndices.Y) + gridA.TileSizeHalfVector);
var offsetTile = Vector2.Transform(new Vector2(tileRef.Value.GridIndices.X, tileRef.Value.GridIndices.Y) + gridA.TileSizeHalfVector, matrix);
tiles.Add((offsetTile.Floored(), tileRef.Value.Tile));
}
@@ -87,7 +87,7 @@ public sealed partial class GridFixtureSystem
if (snapgrid == null || snapgrid.Count == 0)
continue;
var offsetTile = matrix.Transform(new Vector2(tileRef.Value.GridIndices.X, tileRef.Value.GridIndices.Y) + gridA.TileSizeHalfVector);
var offsetTile = Vector2.Transform(new Vector2(tileRef.Value.GridIndices.X, tileRef.Value.GridIndices.Y) + gridA.TileSizeHalfVector, matrix);
var tileIndex = offsetTile.Floored();
for (var j = snapgrid.Count - 1; j >= 0; j--)
@@ -124,7 +124,7 @@ public sealed partial class GridFixtureSystem
if (entXform.ParentUid != gridBUid ||
!bounds.Contains(entXform.LocalPosition)) continue;
var newPos = matrix.Transform(entXform.LocalPosition);
var newPos = Vector2.Transform(entXform.LocalPosition, matrix);
_xformSystem.SetCoordinates(ent, entXform, new EntityCoordinates(gridAUid, newPos), entXform.LocalRotation + rotationDiff, oldParent: xformB, newParent: xformA);
}

View File

@@ -172,16 +172,10 @@ namespace Robust.Server.Placement
}
}
var created = _entityManager.SpawnEntity(entityTemplateName, coordinates);
var created = _entityManager.SpawnAttachedTo(entityTemplateName, coordinates, rotation: dirRcv.ToAngle());
var placementCreateEvent = new PlacementEntityEvent(created, coordinates, PlacementEventAction.Create, msg.MsgChannel.UserId);
_entityManager.EventBus.RaiseEvent(EventSource.Local, placementCreateEvent);
// Some entities immediately delete themselves
if (_entityManager.EntityExists(created))
{
_entityManager.GetComponent<TransformComponent>(created).LocalRotation = dirRcv.ToAngle();
}
}
else
{

View File

@@ -90,18 +90,18 @@ namespace Robust.Server.Player
_cfg.SyncConnectingClient(args.Channel);
}
private void EndSession(object? sender, NetChannelArgs args)
{
EndSession(args.Channel.UserId);
}
/// <summary>
/// Ends a clients session, and disconnects them.
/// </summary>
private void EndSession(object? sender, NetChannelArgs args)
internal void EndSession(NetUserId user)
{
if (!TryGetSessionByChannel(args.Channel, out var session))
{
if (!TryGetSessionById(user, out var session))
return;
}
// make sure nothing got messed up during the life of the session
DebugTools.Assert(session.Channel == args.Channel);
SetStatus(session, SessionStatus.Disconnected);
SetAttachedEntity(session, null, out _, true);
@@ -159,5 +159,32 @@ namespace Robust.Server.Player
session = actor.PlayerSession;
return true;
}
internal ICommonSession AddDummySession(NetUserId user, string name)
{
#if FULL_RELEASE
// Lets not make it completely trivial to fake player counts.
throw new NotSupportedException();
#endif
Lock.EnterWriteLock();
DummySession session;
try
{
UserIdMap[name] = user;
if (!PlayerData.TryGetValue(user, out var data))
PlayerData[user] = data = new(user, name);
session = new DummySession(user, name, data);
InternalSessions.Add(user, session);
}
finally
{
Lock.ExitWriteLock();
}
UpdateState(session);
return session;
}
}
}

View File

@@ -312,14 +312,14 @@ namespace Robust.Server.ServerStatus
}
// Only call this if the download URL is not available!
private async Task<AczManifestInfo?> PrepareAcz()
private async Task<AczManifestInfo?> PrepareAcz(bool optional = false)
{
// Take the ACZ lock asynchronously
await _aczLock.WaitAsync();
try
{
// Setting this now ensures that it won't fail repeatedly on exceptions/etc.
if (_aczPrepareAttempted)
if (_aczPrepareAttempted || optional)
return _aczPrepared;
_aczPrepareAttempted = true;

View File

@@ -80,8 +80,7 @@ namespace Robust.Server.ServerStatus
if (string.IsNullOrEmpty(downloadUrl))
{
var query = HttpUtility.ParseQueryString(context.Url.Query);
var optional = query.Keys;
buildInfo = await PrepareACZBuildInfo();
buildInfo = await PrepareACZBuildInfo(optional: query.Get("can_skip_build") == "1");
}
else
{
@@ -129,9 +128,9 @@ namespace Robust.Server.ServerStatus
};
}
private async Task<JsonObject?> PrepareACZBuildInfo()
private async Task<JsonObject?> PrepareACZBuildInfo(bool optional)
{
var acm = await PrepareAcz();
var acm = await PrepareAcz(optional);
if (acm == null) return null;
// Fork ID is an interesting case, we don't want to cause too many redownloads but we also don't want to pollute disk.

View File

@@ -33,7 +33,7 @@ namespace Robust.Shared.Maths
public readonly Vector2 BottomLeft => Origin + Rotation.RotateVec(Box.BottomLeft - Origin);
public readonly Vector2 Center => Origin + Rotation.RotateVec((Box.BottomLeft + Box.TopRight)/2 - Origin);
public Matrix3 Transform => Matrix3.CreateTransform(Origin - Rotation.RotateVec(Origin), Rotation);
public Matrix3x2 Transform => Matrix3Helpers.CreateTransform(Origin - Rotation.RotateVec(Origin), Rotation);
public Box2Rotated(Vector2 bottomLeft, Vector2 topRight)
: this(new Box2(bottomLeft, topRight))

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,193 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace Robust.Shared.Maths;
public static class Matrix3Helpers
{
public static bool EqualsApprox(this Matrix3x2 a, Matrix3x2 b, float tolerance = 1e-6f)
{
return
Math.Abs(a.M11 - b.M11) <= tolerance &&
Math.Abs(a.M12 - b.M12) <= tolerance &&
Math.Abs(a.M21 - b.M21) <= tolerance &&
Math.Abs(a.M22 - b.M22) <= tolerance &&
Math.Abs(a.M31 - b.M31) <= tolerance &&
Math.Abs(a.M32 - b.M32) <= tolerance;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EqualsApprox(this Matrix3x2 a, Matrix3x2 b, double tolerance)
{
return a.EqualsApprox(b, (float) tolerance);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Box2 TransformBox(this Matrix3x2 refFromBox, Box2Rotated box)
{
return (box.Transform * refFromBox).TransformBox(box.Box);
}
public static Box2 TransformBox(this Matrix3x2 refFromBox, in Box2 box)
{
// Do transformation on all 4 corners of the box at once.
// Then min/max the results to get the new AABB.
var boxVec = Unsafe.As<Box2, Vector128<float>>(ref Unsafe.AsRef(in box));
// Convert box into list of X and Y values for each of the 4 corners
var allX = Vector128.Shuffle(boxVec, Vector128.Create(0, 0, 2, 2));
var allY = Vector128.Shuffle(boxVec, Vector128.Create(1, 3, 3, 1));
// Transform coordinates
var modX = allX * Vector128.Create(refFromBox.M11);
var modY = allX * Vector128.Create(refFromBox.M12);
modX += allY * Vector128.Create(refFromBox.M21);
modY += allY * Vector128.Create(refFromBox.M22);
modX += Vector128.Create(refFromBox.M31);
modY += Vector128.Create(refFromBox.M32);
// Get bounding box by finding the min and max X and Y values.
var l = SimdHelpers.MinHorizontal128(modX);
var b = SimdHelpers.MinHorizontal128(modY);
var r = SimdHelpers.MaxHorizontal128(modX);
var t = SimdHelpers.MaxHorizontal128(modY);
var lbrt = SimdHelpers.MergeRows128(l, b, r, t);
return Unsafe.As<Vector128<float>, Box2>(ref lbrt);
}
/// <summary>
/// Gets the rotation of the Matrix. Will have some precision loss.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Angle Rotation(this Matrix3x2 t)
{
return new Vector2(t.M11, t.M12).ToAngle();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateTransform(float posX, float posY, double angle, float scaleX = 1, float scaleY = 1)
{
// returns a matrix that is equivalent to returning CreateScale(scale) * CreateRotation(angle) * CreateTranslation(posX, posY)
var sin = (float) Math.Sin(angle);
var cos = (float) Math.Cos(angle);
return new Matrix3x2
{
M11 = cos * scaleX,
M21 = -sin * scaleY,
M31 = posX,
M12 = sin * scaleX,
M22 = cos * scaleY,
M32 = posY,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateTransform(in Vector2 position, in Angle angle)
{
// Rounding moment
return angle.Theta switch
{
-Math.PI / 2 => new Matrix3x2(0f, -1f, 1, 0, position.X, position.Y),
Math.PI / 2 => new Matrix3x2(0f, 1f, -1f, 0f, position.X, position.Y),
Math.PI => new Matrix3x2(-1f, 0f, 0f, -1f, position.X, position.Y),
_ => CreateTransform(position.X, position.Y, (float) angle.Theta)
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateTransform(in Vector2 position, in Angle angle, in Vector2 scale)
{
return CreateTransform(position.X, position.Y, (float)angle.Theta, scale.X, scale.Y);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateInverseTransform(float posX, float posY, double angle, float scaleX = 1, float scaleY = 1)
{
// returns a matrix that is equivalent to returning CreateTranslation(-posX, -posY) * CreateRotation(-angle) * CreateScale(1/scaleX, 1/scaleY)
var sin = (float) Math.Sin(angle);
var cos = (float) Math.Cos(angle);
return new Matrix3x2
{
M11 = cos / scaleX,
M21 = sin / scaleX,
M31 = - (posX * cos + posY * sin) / scaleX,
M12 = -sin / scaleY,
M22 = cos / scaleY,
M32 = (posX * sin - posY * cos) / scaleY,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateInverseTransform(in Vector2 position, in Angle angle)
{
return CreateInverseTransform(position.X, position.Y, (float)angle.Theta);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateInverseTransform(in Vector2 position, in Angle angle, in Vector2 scale)
{
return CreateInverseTransform(position.X, position.Y, (float)angle.Theta, scale.X, scale.Y);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateTranslation(float x, float y)
{
return new Matrix3x2 {
M11 = 1,
M12 = 0,
M21 = 0,
M22 = 1,
M31 = x,
M32 = y
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateTranslation(Vector2 vector)
{
return CreateTranslation(vector.X, vector.Y);
}
public static Matrix3x2 CreateRotation(double angle)
{
var cos = (float) Math.Cos(angle);
var sin = (float) Math.Sin(angle);
return new Matrix3x2 {
M11 = cos,
M12 = sin,
M21 = -sin,
M22 = cos,
M31 = 0,
M32 = 0
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateScale(float x, float y)
{
return new Matrix3x2 {
M11 = x,
M12 = 0,
M21 = 0,
M22 = y,
M31 = 0,
M32 = 0
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateScale(in Vector2 scale)
{
return CreateScale(scale.X, scale.Y);
}
}

View File

@@ -1,4 +1,4 @@
#region --- License ---
#region --- License ---
/*
Copyright (c) 2006 - 2008 The Open Toolkit library.
@@ -26,6 +26,7 @@ SOFTWARE.
#endregion --- License ---
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Xml.Serialization;
@@ -73,9 +74,9 @@ namespace Robust.Shared.Maths
: this(new Vector3(x, y, z), w) { }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Quaternion(ref Matrix3 matrix)
public Quaternion(ref Matrix3x2 matrix)
{
var scale = Math.Pow(matrix.Determinant, 1.0d / 3.0d);
var scale = Math.Pow(matrix.GetDeterminant(), 1.0d / 3.0d);
float x, y, z;
w = (float) (Math.Sqrt(Math.Max(0, scale + matrix[0, 0] + matrix[1, 1] + matrix[2, 2])) / 2);

View File

@@ -272,4 +272,6 @@ public enum AudioFlags : byte
/// Should the audio act as if attached to a grid?
/// </summary>
GridAudio = 1 << 0,
NoOcclusion = 1 << 1,
}

View File

@@ -10,6 +10,7 @@ using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Network;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
@@ -34,6 +35,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
[Dependency] protected readonly IPrototypeManager ProtoMan = default!;
[Dependency] protected readonly IRobustRandom RandMan = default!;
[Dependency] protected readonly MetaDataSystem MetadataSys = default!;
[Dependency] protected readonly SharedTransformSystem XformSystem = default!;
/// <summary>
/// Default max range at which the sound can be heard.
@@ -92,7 +94,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
var currentPos = (entity.Comp.PauseTime ?? Timing.CurTime) - entity.Comp.AudioStart;
var timeOffset = TimeSpan.FromSeconds(position - currentPos.TotalSeconds);
DebugTools.Assert(currentPos > TimeSpan.Zero);
DebugTools.Assert(currentPos >= TimeSpan.Zero);
// Rounding.
if (Math.Abs(timeOffset.TotalSeconds) <= 0.01)
@@ -145,6 +147,33 @@ public abstract partial class SharedAudioSystem : EntitySystem
MetadataSys.AddFlag(audio.Value.Owner, MetaDataFlags.Undetachable);
}
public virtual void SetGridAudio(Entity<AudioComponent>? entity)
{
if (entity == null)
return;
entity.Value.Comp.Flags |= AudioFlags.GridAudio;
var gridUid = Transform(entity.Value).GridUid;
if (TryComp(gridUid, out PhysicsComponent? gridPhysics))
{
XformSystem.SetLocalPosition(entity.Value.Owner, gridPhysics.LocalCenter);
}
if (TryComp(gridUid, out MapGridComponent? mapGrid))
{
var extents = mapGrid.LocalAABB.Extents;
var minDistance = MathF.Max(extents.X, extents.Y);
entity.Value.Comp.Params = entity.Value.Comp.Params
.WithMaxDistance(minDistance + DefaultSoundRange)
.WithReferenceDistance(minDistance);
}
entity.Value.Comp.Flags |= AudioFlags.NoOcclusion;
Dirty(entity.Value);
}
/// <summary>
/// Sets the shared state for an audio entity.
/// </summary>
@@ -266,16 +295,16 @@ public abstract partial class SharedAudioSystem : EntitySystem
#region AudioParams
protected AudioComponent SetupAudio(EntityUid uid, string? fileName, AudioParams? audioParams, TimeSpan? length = null)
protected Entity<AudioComponent> SetupAudio(string? fileName, AudioParams? audioParams, bool initialize = true, TimeSpan? length = null)
{
DebugTools.Assert((!string.IsNullOrEmpty(fileName) || length is not null));
var uid = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace);
DebugTools.Assert(!string.IsNullOrEmpty(fileName) || length is not null);
audioParams ??= AudioParams.Default;
var comp = AddComp<AudioComponent>(uid);
comp.FileName = fileName ?? string.Empty;
comp.Params = audioParams.Value;
comp.AudioStart = Timing.CurTime;
if (!audioParams.Value.Loop)
{
length ??= GetAudioLength(fileName!);
@@ -290,7 +319,12 @@ public abstract partial class SharedAudioSystem : EntitySystem
comp.Params.Pitch *= (float) RandMan.NextGaussian(1, comp.Params.Variation.Value);
}
return comp;
if (initialize)
{
EntityManager.InitializeAndStartEntity(uid);
}
return new Entity<AudioComponent>(uid, comp);
}
public static float GainToVolume(float value)
@@ -597,7 +631,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
[return: NotNullIfNotNull("sound")]
public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
{
return sound == null ? null : PlayStatic(GetSound(sound), playerFilter, coordinates, recordReplay);
return sound == null ? null : PlayStatic(GetSound(sound), playerFilter, coordinates, recordReplay, audioParams);
}
/// <summary>

View File

@@ -1641,6 +1641,13 @@ namespace Robust.Shared
true,
CVar.SERVER | CVar.REPLICATED | CVar.ARCHIVE);
/// <summary>
/// How many milliseconds we will spend moving forward from the nearest checkpoint or current position.
/// We will spend this time when scrubbing the timeline per game tick. This limits CPU usage / locking up and
/// improves responsiveness
/// </summary>
public static readonly CVarDef<int> ReplayMaxScrubTime = CVarDef.Create("replay.max_scrub_time", 10);
/// <summary>
/// Determines the threshold before visual events (muzzle flashes, chat pop-ups, etc) are suppressed when
/// jumping forward in time. Jumps larger than this will simply skip directly to the target tick.

View File

@@ -357,7 +357,7 @@ public abstract class ComponentTreeSystem<TTreeComp, TComp> : EntitySystem
{
var (_, treeRot, matrix) = XformSystem.GetWorldPositionRotationInvMatrix(treeUid);
var relativeAngle = new Angle(-treeRot.Theta).RotateVec(ray.Direction);
var treeRay = new Ray(matrix.Transform(ray.Position), relativeAngle);
var treeRay = new Ray(Vector2.Transform(ray.Position, matrix), relativeAngle);
comp.Tree.QueryRay(ref queryState, QueryCallback, treeRay);
if (returnOnFirstHit && queryState.List.Count > 0)
break;

View File

@@ -140,4 +140,47 @@ namespace Robust.Shared.Configuration
throw new NotSupportedException();
}
}
internal sealed class CVarSubsCommand : LocalizedCommands
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
public override string Command => "cvar_subs";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 1)
{
shell.WriteError(Loc.GetString("cmd-cvar_subs-invalid-args"));
return;
}
var name = args[0];
var subs = ((ConfigurationManager)_cfg).GetSubs(name);
foreach (var @delegate in subs)
{
shell.WriteLine(ShowDelegateInfo(@delegate));
}
}
private static string ShowDelegateInfo(Delegate del)
{
return $"{del}: {del.Method} -> {del.Target}";
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
{
return CompletionResult.FromHintOptions(
_cfg.GetRegisteredCVars()
.Select(c => new CompletionOption(c))
.OrderBy(c => c.Value),
Loc.GetString("cmd-cvar_subs-arg-name"));
}
return CompletionResult.Empty;
}
}
}

View File

@@ -97,8 +97,23 @@ namespace Robust.Shared.Configuration
{
// overwrite the value with the saved one
var oldValue = GetConfigVarValue(cfgVar);
changedInvokes.Add(SetupInvokeValueChanged(cfgVar, value, oldValue));
cfgVar.Value = value;
var convertedValue = value;
if (cfgVar.Type != value.GetType())
{
try
{
convertedValue = ConvertToCVarType(value, cfgVar.Type!);
}
catch
{
_sawmill.Error($"TOML parsed cvar does not match registered cvar type. Name: {cvar}. Code Type: {cfgVar.Type}. Toml type: {value.GetType()}");
return;
}
}
changedInvokes.Add(SetupInvokeValueChanged(cfgVar, convertedValue, oldValue));
cfgVar.Value = convertedValue;
}
else
{
@@ -777,6 +792,24 @@ namespace Robust.Shared.Configuration
return Convert.ChangeType(value, cVar);
}
internal List<Delegate> GetSubs(string name)
{
using (Lock.ReadGuard())
{
var list = new List<Delegate>();
if (!_configVars.TryGetValue(name, out var cVar))
throw new InvalidConfigurationException($"Trying to get unregistered variable '{name}'");
foreach (var entry in cVar.ValueChanged.Entries)
{
list.Add((Delegate) entry.Equality!);
}
return list;
}
}
/// <summary>
/// Holds the data for a single configuration variable.
/// </summary>

View File

@@ -34,7 +34,7 @@ internal sealed class DumpEventTablesCommand : LocalizedCommands
{
shell.WriteLine($"{evType}:");
var idx = comps;
var idx = comps.Start;
while (idx != -1)
{
ref var entry = ref table.ComponentLists[idx];

View File

@@ -56,7 +56,7 @@ internal sealed class TeleportCommand : LocalizedCommands
if (_map.TryFindGridAt(mapId, position, out var gridUid, out var grid))
{
var gridPos = xformSystem.GetInvWorldMatrix(gridUid).Transform(position);
var gridPos = Vector2.Transform(position, xformSystem.GetInvWorldMatrix(gridUid));
xformSystem.SetCoordinates(entity, transform, new EntityCoordinates(gridUid, gridPos));
}
@@ -174,12 +174,7 @@ public sealed class TeleportToCommand : LocalizedCommands
var hint = args.Length == 1 ? "cmd-tpto-destination-hint" : "cmd-tpto-victim-hint";
hint = Loc.GetString(hint);
var opts = CompletionResult.FromHintOptions(users, hint);
if (last != string.Empty && !NetEntity.TryParse(last, out _))
return opts;
return CompletionResult.FromHintOptions(opts.Options.Concat(CompletionHelper.NetEntities(last, _entities)), hint);
return CompletionResult.FromHintOptions(users, hint);
}
}

View File

@@ -189,27 +189,45 @@ public static class CompletionHelper
return Components<MapComponent>(string.Empty, entManager);
}
public static IEnumerable<CompletionOption> NetEntities(string text, IEntityManager? entManager = null)
/// <summary>
/// Return all existing entities as possible completions. You should generally avoid using this unless you need to.
/// </summary>
public static IEnumerable<CompletionOption> NetEntities(string text, IEntityManager? entManager = null, int limit = 20)
{
return Components<MetaDataComponent>(text, entManager);
}
if (!NetEntity.TryParse(text, out _))
yield break;
public static IEnumerable<CompletionOption> Components<T>(string text, IEntityManager? entManager = null) where T : IComponent
{
IoCManager.Resolve(ref entManager);
var query = entManager.AllEntityQueryEnumerator<MetaDataComponent>();
var query = entManager.AllEntityQueryEnumerator<T, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
var i = 0;
while (i < limit && query.MoveNext(out var metadata))
{
if (!entManager.TryGetNetEntity(uid, out var netEntity, metadata: metadata))
continue;
var netString = netEntity.Value.ToString();
var netString = metadata.NetEntity.ToString();
if (!netString.StartsWith(text))
continue;
i++;
yield return new CompletionOption(netString, metadata.EntityName);
}
}
public static IEnumerable<CompletionOption> Components<T>(string text, IEntityManager? entManager = null, int limit = 20) where T : IComponent
{
if (!NetEntity.TryParse(text, out _))
yield break;
IoCManager.Resolve(ref entManager);
var query = entManager.AllEntityQueryEnumerator<T, MetaDataComponent>();
var i = 0;
while (i < limit && query.MoveNext(out _, out var metadata))
{
var netString = metadata.NetEntity.ToString();
if (!netString.StartsWith(text))
continue;
i++;
yield return new CompletionOption(netString, metadata.EntityName);
}
}

View File

@@ -49,14 +49,14 @@ public abstract partial class SharedContainerSystem
if (!TryComp(container.Owner, out MetaDataComponent? ownerMeta))
{
Log.Error($"Attempted to insert an entity {ToPrettyString(toInsert)} into a non-existent entity.");
Log.Error($"Attempted to insert an entity {ToPrettyString(toInsert)} into a non-existent entity. Trace: {Environment.StackTrace}");
QueueDel(toInsert);
return false;
}
if (ownerMeta.EntityLifeStage >= EntityLifeStage.Terminating)
{
Log.Error($"Attempted to insert an entity {ToPrettyString(toInsert)} into an entity that is terminating. Entity: {ToPrettyString(container.Owner)}.");
Log.Error($"Attempted to insert an entity {ToPrettyString(toInsert)} into an entity that is terminating. Entity: {ToPrettyString(container.Owner)}. Trace: {Environment.StackTrace}");
QueueDel(toInsert);
return false;
}
@@ -67,7 +67,7 @@ public abstract partial class SharedContainerSystem
if (meta.EntityLifeStage >= EntityLifeStage.Terminating)
{
Log.Error($"Attempted to insert a terminating entity {ToPrettyString(uid)} into a container {container.ID} in entity: {ToPrettyString(container.Owner)}.");
Log.Error($"Attempted to insert a terminating entity {ToPrettyString(uid)} into a container {container.ID} in entity: {ToPrettyString(container.Owner)}. Trace: {Environment.StackTrace}");
return false;
}

View File

@@ -32,7 +32,7 @@ namespace Robust.Shared.ContentPack
String("short").ThenReturn(PrimitiveTypeCode.Int16);
private static readonly Parser<char, PrimitiveTypeCode> UInt16TypeParser =
String("ushort").ThenReturn(PrimitiveTypeCode.UInt32);
String("ushort").ThenReturn(PrimitiveTypeCode.UInt16);
private static readonly Parser<char, PrimitiveTypeCode> Int32TypeParser =
String("int").ThenReturn(PrimitiveTypeCode.Int32);

View File

@@ -84,12 +84,146 @@ Types:
- "bool get_HasContents()"
Lidgren.Network:
NetBuffer:
All: True
Methods:
- "byte[] get_Data()"
- "void set_Data(byte[])"
- "int get_LengthBytes()"
- "void set_LengthBytes(int)"
- "int get_LengthBits()"
- "void set_LengthBits(int)"
- "long get_Position()"
- "void set_Position(long)"
- "int get_PositionInBytes()"
- "byte[] PeekDataBuffer()"
- "bool PeekBoolean()"
- "byte PeekByte()"
- "sbyte PeekSByte()"
- "byte PeekByte(int)"
- "System.Span`1<byte> PeekBytes(System.Span`1<byte>)"
- "byte[] PeekBytes(int)"
- "void PeekBytes(byte[], int, int)"
- "short PeekInt16()"
- "ushort PeekUInt16()"
- "int PeekInt32()"
- "int PeekInt32(int)"
- "uint PeekUInt32()"
- "uint PeekUInt32(int)"
- "ulong PeekUInt64()"
- "long PeekInt64()"
- "ulong PeekUInt64(int)"
- "long PeekInt64(int)"
- "float PeekFloat()"
- "System.Half PeekHalf()"
- "float PeekSingle()"
- "double PeekDouble()"
- "string PeekString()"
- "int PeekStringSize()"
- "bool ReadBoolean()"
- "byte ReadByte()"
- "bool ReadByte(ref byte)"
- "sbyte ReadSByte()"
- "byte ReadByte(int)"
- "System.Span`1<byte> ReadBytes(System.Span`1<byte>)"
- "byte[] ReadBytes(int)"
- "bool ReadBytes(int, ref byte[])"
- "bool TryReadBytes(System.Span`1<byte>)"
- "void ReadBytes(byte[], int, int)"
- "void ReadBits(System.Span`1<byte>, int)"
- "void ReadBits(byte[], int, int)"
- "short ReadInt16()"
- "ushort ReadUInt16()"
- "int ReadInt32()"
- "bool ReadInt32(ref int)"
- "int ReadInt32(int)"
- "uint ReadUInt32()"
- "bool ReadUInt32(ref uint)"
- "uint ReadUInt32(int)"
- "ulong ReadUInt64()"
- "long ReadInt64()"
- "ulong ReadUInt64(int)"
- "long ReadInt64(int)"
- "float ReadFloat()"
- "System.Half ReadHalf()"
- "float ReadSingle()"
- "bool ReadSingle(ref float)"
- "double ReadDouble()"
- "uint ReadVariableUInt32()"
- "bool ReadVariableUInt32(ref uint)"
- "int ReadVariableInt32()"
- "long ReadVariableInt64()"
- "ulong ReadVariableUInt64()"
- "float ReadSignedSingle(int)"
- "float ReadUnitSingle(int)"
- "float ReadRangedSingle(float, float, int)"
- "int ReadRangedInteger(int, int)"
- "long ReadRangedInteger(long, long)"
- "string ReadString()"
- "bool ReadString(ref string)"
- "double ReadTime(Lidgren.Network.NetConnection, bool)"
- "System.Net.IPEndPoint ReadIPEndPoint()"
- "void SkipPadBits()"
- "void ReadPadBits()"
- "void SkipPadBits(int)"
- "void EnsureBufferSize(int)"
- "void Write(bool)"
- "void Write(byte)"
- "void WriteAt(int, byte)"
- "void Write(sbyte)"
- "void Write(byte, int)"
- "void Write(byte[])"
- "void Write(System.ReadOnlySpan`1<byte>)"
- "void Write(byte[], int, int)"
- "void Write(ushort)"
- "void WriteAt(int, ushort)"
- "void Write(ushort, int)"
- "void Write(short)"
- "void WriteAt(int, short)"
- "void Write(int)"
- "void WriteAt(int, int)"
- "void Write(uint)"
- "void WriteAt(int, uint)"
- "void Write(uint, int)"
- "void Write(int, int)"
- "void Write(ulong)"
- "void WriteAt(int, ulong)"
- "void Write(ulong, int)"
- "void Write(long)"
- "void Write(long, int)"
- "void Write(System.Half)"
- "void Write(float)"
- "void Write(double)"
- "int WriteVariableUInt32(uint)"
- "int WriteVariableInt32(int)"
- "int WriteVariableInt64(long)"
- "int WriteVariableUInt64(ulong)"
- "void WriteSignedSingle(float, int)"
- "void WriteUnitSingle(float, int)"
- "void WriteRangedSingle(float, float, float, int)"
- "int WriteRangedInteger(int, int, int)"
- "int WriteRangedInteger(long, long, long)"
- "void Write(string)"
- "void Write(System.Net.IPEndPoint)"
- "void WriteTime(bool)"
- "void WriteTime(double, bool)"
- "void WritePadBits()"
- "void WritePadBits(int)"
- "void Write(Lidgren.Network.NetBuffer)"
- "void Zero(int)"
- "void .ctor()"
NetDeliveryMethod: { }
NetIncomingMessage:
All: True
Methods:
- "Lidgren.Network.NetIncomingMessageType get_MessageType()"
- "Lidgren.Network.NetDeliveryMethod get_DeliveryMethod()"
- "int get_SequenceChannel()"
- "System.Net.IPEndPoint get_SenderEndPoint()"
- "Lidgren.Network.NetConnection get_SenderConnection()"
- "double get_ReceiveTime()"
- "double ReadTime(bool)"
- "string ToString()"
NetOutgoingMessage:
All: True
Methods:
- "string ToString()"
Nett:
CommentLocation: { } # Enum
Toml:
@@ -450,6 +584,7 @@ Types:
Vector2: { All: True }
Vector3: { All: True }
Vector4: { All: True }
Matrix3x2: { All: True }
System.Reflection:
Assembly:
Methods:
@@ -831,6 +966,7 @@ Types:
- "object GetValue(long[])"
- "System.Collections.IEnumerator GetEnumerator()"
- "System.Collections.ObjectModel.ReadOnlyCollection`1<!!0> AsReadOnly<>(!!0[])"
- "void Clear(System.Array)"
- "void Clear(System.Array, int, int)"
- "void ConstrainedCopy(System.Array, int, System.Array, int, int)"
- "void Copy(System.Array, int, System.Array, int, int)"

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack
@@ -135,11 +136,37 @@ namespace Robust.Shared.ContentPack
path = path.Directory;
var fullPath = GetFullPath(path);
Process.Start(new ProcessStartInfo
if (OperatingSystem.IsWindows())
{
UseShellExecute = true,
FileName = fullPath,
});
Process.Start(new ProcessStartInfo
{
FileName = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\explorer.exe",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else if (OperatingSystem.IsMacOS())
{
Process.Start(new ProcessStartInfo
{
FileName = "open",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD())
{
Process.Start(new ProcessStartInfo
{
FileName = "xdg-open",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else
{
throw new NotSupportedException("Opening OS windows not supported on this OS");
}
}
#endregion

View File

@@ -9,36 +9,19 @@ namespace Robust.Shared.GameObjects;
public readonly struct CompIdx : IEquatable<CompIdx>
{
private static readonly ReaderWriterLockSlim SlowStoreLock = new();
private static readonly Dictionary<Type, CompIdx> SlowStore = new();
internal readonly int Value;
internal static CompIdx Index<T>() => Store<T>.Index;
internal static CompIdx Index(Type t)
{
using (SlowStoreLock.ReadGuard())
{
if (SlowStore.TryGetValue(t, out var idx))
return idx;
}
// Doesn't exist in the store, get a write lock and add it.
using (SlowStoreLock.WriteGuard())
{
var idx = (CompIdx)typeof(Store<>)
.MakeGenericType(t)
.GetField(nameof(Store<int>.Index), BindingFlags.Static | BindingFlags.Public)!
.GetValue(null)!;
SlowStore[t] = idx;
return idx;
}
}
internal static int ArrayIndex<T>() => Index<T>().Value;
internal static int ArrayIndex(Type type) => Index(type).Value;
internal static CompIdx GetIndex(Type type)
{
return (CompIdx)typeof(Store<>)
.MakeGenericType(type)
.GetField(nameof(Store<int>.Index), BindingFlags.Static | BindingFlags.Public)!
.GetValue(null)!;
}
internal static void AssignArray<T>(ref T[] array, CompIdx idx, T value)
{

View File

@@ -24,7 +24,6 @@ namespace Robust.Shared.GameObjects
// Bunch of dictionaries to allow lookups in all directions.
/// <summary>
/// <summary>
/// Mapping of component name to type.
/// </summary>
private FrozenDictionary<string, ComponentRegistration> _names
@@ -63,6 +62,11 @@ namespace Robust.Shared.GameObjects
private FrozenDictionary<CompIdx, Type> _idxToType
= FrozenDictionary<CompIdx, Type>.Empty;
/// <summary>
/// Slow-path for Type -> CompIdx mapping without generics.
/// </summary>
private FrozenDictionary<Type, CompIdx> _typeToIdx = FrozenDictionary<Type, CompIdx>.Empty;
/// <inheritdoc />
public event Action<ComponentRegistration[]>? ComponentsAdded;
@@ -78,6 +82,7 @@ namespace Robust.Shared.GameObjects
private IEnumerable<ComponentRegistration> AllRegistrations => _types.Values;
private ComponentRegistration Register(Type type,
CompIdx idx,
Dictionary<string, ComponentRegistration> names,
Dictionary<string, string> lowerCaseNames,
Dictionary<Type, ComponentRegistration> types,
@@ -123,8 +128,6 @@ namespace Robust.Shared.GameObjects
var unsaved = type.HasCustomAttribute<UnsavedComponentAttribute>();
var idx = CompIdx.Index(type);
var registration = new ComponentRegistration(name, type, idx, unsaved);
idxToType[idx] = type;
@@ -399,6 +402,20 @@ namespace Robust.Shared.GameObjects
RegisterTypesInternal(types, false);
}
/// <inheritdoc />
[Pure]
public CompIdx GetIndex(Type type)
{
return _typeToIdx[type];
}
/// <inheritdoc />
[Pure]
public int GetArrayIndex(Type type)
{
return _typeToIdx[type].Value;
}
private void RegisterTypesInternal(Type[] types, bool overwrite)
{
var names = _names.ToDictionary();
@@ -408,12 +425,19 @@ namespace Robust.Shared.GameObjects
var ignored = _ignored.ToHashSet();
var added = new ComponentRegistration[types.Length];
var typeToidx = _typeToIdx.ToDictionary();
for (int i = 0; i < types.Length; i++)
{
added[i] = Register(types[i], names, lowerCaseNames, typesDict, idxToType, ignored, overwrite);
var type = types[i];
var idx = CompIdx.GetIndex(type);
typeToidx[type] = idx;
added[i] = Register(type, idx, names, lowerCaseNames, typesDict, idxToType, ignored, overwrite);
}
var st = RStopwatch.StartNew();
_typeToIdx = typeToidx.ToFrozenDictionary();
_names = names.ToFrozenDictionary();
_lowerCaseNames = lowerCaseNames.ToFrozenDictionary();
_types = typesDict.ToFrozenDictionary();

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using JetBrains.Annotations;
using Robust.Shared.Animations;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
@@ -25,16 +26,26 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IGameTiming _gameTiming = default!;
// Currently this field just exists for VV. In future, it might become a real field
[ViewVariables]
[ViewVariables, PublicAPI]
private NetEntity NetParent => _entMan.GetNetEntity(_parent);
[DataField("parent")] internal EntityUid _parent;
[DataField("pos")] internal Vector2 _localPosition = Vector2.Zero; // holds offset from grid, or offset from parent
[DataField("pos")] internal Vector2 _localPosition = Vector2.Zero; // holds offset from parent
[DataField("rot")] internal Angle _localRotation; // local rotation
[DataField("noRot")] internal bool _noLocalRotation;
[DataField("anchored")]
internal bool _anchored;
/// <summary>
/// Indicates this entity can traverse grids.
/// </summary>
[DataField]
public bool GridTraversal = true;
/// <summary>
/// The broadphase that this entity is currently stored on, if any.
/// </summary>
@@ -46,12 +57,12 @@ namespace Robust.Shared.GameObjects
internal BroadphaseData? Broadphase;
internal bool MatricesDirty = true;
private Matrix3 _localMatrix = Matrix3.Identity;
private Matrix3 _invLocalMatrix = Matrix3.Identity;
private Matrix3x2 _localMatrix = Matrix3x2.Identity;
private Matrix3x2 _invLocalMatrix = Matrix3x2.Identity;
// these should just be system methods, but existing component functions like InvWorldMatrix still rely on
// getting these so those have to be fully ECS-ed first.
public Matrix3 LocalMatrix
public Matrix3x2 LocalMatrix
{
get
{
@@ -60,7 +71,7 @@ namespace Robust.Shared.GameObjects
return _localMatrix;
}
}
public Matrix3 InvLocalMatrix
public Matrix3x2 InvLocalMatrix
{
get
{
@@ -207,7 +218,7 @@ namespace Robust.Shared.GameObjects
/// Matrix for transforming points from local to world space.
/// </summary>
[Obsolete("Use the system method instead")]
public Matrix3 WorldMatrix
public Matrix3x2 WorldMatrix
{
get
{
@@ -221,7 +232,7 @@ namespace Robust.Shared.GameObjects
var parentMatrix = parentXform.LocalMatrix;
parent = parentXform.ParentUid;
Matrix3.Multiply(in myMatrix, in parentMatrix, out var result);
var result = Matrix3x2.Multiply(myMatrix, parentMatrix);
myMatrix = result;
}
@@ -233,7 +244,7 @@ namespace Robust.Shared.GameObjects
/// Matrix for transforming points from world to local space.
/// </summary>
[Obsolete("Use the system method instead")]
public Matrix3 InvWorldMatrix
public Matrix3x2 InvWorldMatrix
{
get
{
@@ -247,7 +258,7 @@ namespace Robust.Shared.GameObjects
var parentMatrix = parentXform.InvLocalMatrix;
parent = parentXform.ParentUid;
Matrix3.Multiply(in parentMatrix, in myMatrix, out var result);
var result = Matrix3x2.Multiply(parentMatrix, myMatrix);
myMatrix = result;
}
@@ -269,7 +280,7 @@ namespace Robust.Shared.GameObjects
if (_parent.IsValid())
{
// parent coords to world coords
return _entMan.GetComponent<TransformComponent>(ParentUid).WorldMatrix.Transform(_localPosition);
return Vector2.Transform(_localPosition, _entMan.GetComponent<TransformComponent>(ParentUid).WorldMatrix);
}
else
{
@@ -285,7 +296,7 @@ namespace Robust.Shared.GameObjects
}
// world coords to parent coords
var newPos = _entMan.GetComponent<TransformComponent>(ParentUid).InvWorldMatrix.Transform(value);
var newPos = Vector2.Transform(value, _entMan.GetComponent<TransformComponent>(ParentUid).InvWorldMatrix);
LocalPosition = newPos;
}
@@ -479,7 +490,7 @@ namespace Robust.Shared.GameObjects
/// Get the WorldPosition, WorldRotation, and WorldMatrix of this entity faster than each individually.
/// </summary>
[Obsolete("Use the system method instead")]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix) GetWorldPositionRotationMatrix(EntityQuery<TransformComponent> xforms)
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3x2 WorldMatrix) GetWorldPositionRotationMatrix(EntityQuery<TransformComponent> xforms)
{
var parent = _parent;
var worldRot = _localRotation;
@@ -491,12 +502,12 @@ namespace Robust.Shared.GameObjects
var xform = xforms.GetComponent(parent);
worldRot += xform.LocalRotation;
var parentMatrix = xform.LocalMatrix;
Matrix3.Multiply(in worldMatrix, in parentMatrix, out var result);
var result = Matrix3x2.Multiply(worldMatrix, parentMatrix);
worldMatrix = result;
parent = xform.ParentUid;
}
var worldPosition = new Vector2(worldMatrix.R0C2, worldMatrix.R1C2);
var worldPosition = worldMatrix.Translation;
return (worldPosition, worldRot, worldMatrix);
}
@@ -505,7 +516,7 @@ namespace Robust.Shared.GameObjects
/// Get the WorldPosition, WorldRotation, and WorldMatrix of this entity faster than each individually.
/// </summary>
[Obsolete("Use the system method instead")]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix) GetWorldPositionRotationMatrix()
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3x2 WorldMatrix) GetWorldPositionRotationMatrix()
{
var xforms = _entMan.GetEntityQuery<TransformComponent>();
return GetWorldPositionRotationMatrix(xforms);
@@ -515,7 +526,7 @@ namespace Robust.Shared.GameObjects
/// Get the WorldPosition, WorldRotation, and InvWorldMatrix of this entity faster than each individually.
/// </summary>
[Obsolete("Use the system method instead")]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 InvWorldMatrix) GetWorldPositionRotationInvMatrix()
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3x2 InvWorldMatrix) GetWorldPositionRotationInvMatrix()
{
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
return GetWorldPositionRotationInvMatrix(xformQuery);
@@ -525,7 +536,7 @@ namespace Robust.Shared.GameObjects
/// Get the WorldPosition, WorldRotation, and InvWorldMatrix of this entity faster than each individually.
/// </summary>
[Obsolete("Use the system method instead")]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 InvWorldMatrix) GetWorldPositionRotationInvMatrix(EntityQuery<TransformComponent> xformQuery)
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3x2 InvWorldMatrix) GetWorldPositionRotationInvMatrix(EntityQuery<TransformComponent> xformQuery)
{
var (worldPos, worldRot, _, invWorldMatrix) = GetWorldPositionRotationMatrixWithInv(xformQuery);
return (worldPos, worldRot, invWorldMatrix);
@@ -535,7 +546,7 @@ namespace Robust.Shared.GameObjects
/// Get the WorldPosition, WorldRotation, WorldMatrix, and InvWorldMatrix of this entity faster than each individually.
/// </summary>
[Obsolete("Use the system method instead")]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix, Matrix3 InvWorldMatrix) GetWorldPositionRotationMatrixWithInv()
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3x2 WorldMatrix, Matrix3x2 InvWorldMatrix) GetWorldPositionRotationMatrixWithInv()
{
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
return GetWorldPositionRotationMatrixWithInv(xformQuery);
@@ -545,7 +556,7 @@ namespace Robust.Shared.GameObjects
/// Get the WorldPosition, WorldRotation, WorldMatrix, and InvWorldMatrix of this entity faster than each individually.
/// </summary>
[Obsolete("Use the system method instead")]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix, Matrix3 InvWorldMatrix) GetWorldPositionRotationMatrixWithInv(EntityQuery<TransformComponent> xformQuery)
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3x2 WorldMatrix, Matrix3x2 InvWorldMatrix) GetWorldPositionRotationMatrixWithInv(EntityQuery<TransformComponent> xformQuery)
{
var parent = _parent;
var worldRot = _localRotation;
@@ -559,17 +570,17 @@ namespace Robust.Shared.GameObjects
worldRot += xform.LocalRotation;
var parentMatrix = xform.LocalMatrix;
Matrix3.Multiply(in worldMatrix, in parentMatrix, out var result);
var result = Matrix3x2.Multiply(worldMatrix, parentMatrix);
worldMatrix = result;
var parentInvMatrix = xform.InvLocalMatrix;
Matrix3.Multiply(in parentInvMatrix, in invMatrix, out var invResult);
var invResult = Matrix3x2.Multiply(parentInvMatrix, invMatrix);
invMatrix = invResult;
parent = xform.ParentUid;
}
var worldPosition = new Vector2(worldMatrix.R0C2, worldMatrix.R1C2);
var worldPosition = worldMatrix.Translation;
return (worldPosition, worldRot, worldMatrix, invMatrix);
}
@@ -580,12 +591,12 @@ namespace Robust.Shared.GameObjects
if (!_parent.IsValid()) // Root Node
{
_localMatrix = Matrix3.Identity;
_invLocalMatrix = Matrix3.Identity;
_localMatrix = Matrix3x2.Identity;
_invLocalMatrix = Matrix3x2.Identity;
}
_localMatrix = Matrix3.CreateTransform(_localPosition, _localRotation);
_invLocalMatrix = Matrix3.CreateInverseTransform(_localPosition, _localRotation);
_localMatrix = Matrix3Helpers.CreateTransform(_localPosition, _localRotation);
_invLocalMatrix = Matrix3Helpers.CreateInverseTransform(_localPosition, _localRotation);
}
public string GetDebugString()

View File

@@ -5,7 +5,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects;
[RegisterComponent, NetworkedComponent]
[RegisterComponent]
public sealed partial class ActiveUserInterfaceComponent : Component
{
}

View File

@@ -24,7 +24,7 @@ namespace Robust.Shared.GameObjects
/// Actors that currently have interfaces open.
/// </summary>
[DataField]
public Dictionary<Enum, List<EntityUid>> Actors = new();
public Dictionary<Enum, HashSet<EntityUid>> Actors = new();
/// <summary>
/// Legacy data, new BUIs should be using comp states.

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.GameObjects;
@@ -9,17 +7,13 @@ namespace Robust.Shared.GameObjects;
/// <summary>
/// Stores data about this entity and what BUIs they have open.
/// </summary>
[RegisterComponent, NetworkedComponent]
/// <remarks>
/// This component is implicitly networked via <see cref="UserInterfaceComponent"/>.
/// I.e., the other component is authoritative about what UIs are open
/// </remarks>
[RegisterComponent]
public sealed partial class UserInterfaceUserComponent : Component
{
public override bool SessionSpecific => true;
[DataField]
public Dictionary<EntityUid, List<Enum>> OpenInterfaces = new();
}
[Serializable, NetSerializable]
internal sealed class UserInterfaceUserComponentState : IComponentState
{
public Dictionary<NetEntity, List<Enum>> OpenInterfaces = new();
}

View File

@@ -1,12 +1,14 @@
using Robust.Shared.Utility;
using Robust.Shared.Localization;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
public record struct Entity<T>
public record struct Entity<T> : IFluentEntityUid
where T : IComponent?
{
public EntityUid Owner;
public T Comp;
EntityUid IFluentEntityUid.FluentOwner => Owner;
public Entity(EntityUid owner, T comp)
{
@@ -45,12 +47,13 @@ public record struct Entity<T>
public override int GetHashCode() => Owner.GetHashCode();
}
public record struct Entity<T1, T2>
public record struct Entity<T1, T2> : IFluentEntityUid
where T1 : IComponent? where T2 : IComponent?
{
public EntityUid Owner;
public T1 Comp1;
public T2 Comp2;
EntityUid IFluentEntityUid.FluentOwner => Owner;
public Entity(EntityUid owner, T1 comp1, T2 comp2)
{
@@ -110,13 +113,14 @@ public record struct Entity<T1, T2>
}
}
public record struct Entity<T1, T2, T3>
public record struct Entity<T1, T2, T3> : IFluentEntityUid
where T1 : IComponent? where T2 : IComponent? where T3 : IComponent?
{
public EntityUid Owner;
public T1 Comp1;
public T2 Comp2;
public T3 Comp3;
EntityUid IFluentEntityUid.FluentOwner => Owner;
public Entity(EntityUid owner, T1 comp1, T2 comp2, T3 comp3)
{
@@ -211,7 +215,7 @@ public record struct Entity<T1, T2, T3>
#endregion
}
public record struct Entity<T1, T2, T3, T4>
public record struct Entity<T1, T2, T3, T4> : IFluentEntityUid
where T1 : IComponent? where T2 : IComponent? where T3 : IComponent? where T4 : IComponent?
{
public EntityUid Owner;
@@ -219,6 +223,7 @@ public record struct Entity<T1, T2, T3, T4>
public T2 Comp2;
public T3 Comp3;
public T4 Comp4;
EntityUid IFluentEntityUid.FluentOwner => Owner;
public Entity(EntityUid owner, T1 comp1, T2 comp2, T3 comp3, T4 comp4)
{
@@ -336,7 +341,7 @@ public record struct Entity<T1, T2, T3, T4>
#endregion
}
public record struct Entity<T1, T2, T3, T4, T5>
public record struct Entity<T1, T2, T3, T4, T5> : IFluentEntityUid
where T1 : IComponent? where T2 : IComponent? where T3 : IComponent? where T4 : IComponent? where T5 : IComponent?
{
public EntityUid Owner;
@@ -345,6 +350,7 @@ public record struct Entity<T1, T2, T3, T4, T5>
public T3 Comp3;
public T4 Comp4;
public T5 Comp5;
EntityUid IFluentEntityUid.FluentOwner => Owner;
public Entity(EntityUid owner, T1 comp1, T2 comp2, T3 comp3, T4 comp4, T5 comp5)
{
@@ -485,7 +491,7 @@ public record struct Entity<T1, T2, T3, T4, T5>
#endregion
}
public record struct Entity<T1, T2, T3, T4, T5, T6>
public record struct Entity<T1, T2, T3, T4, T5, T6> : IFluentEntityUid
where T1 : IComponent? where T2 : IComponent? where T3 : IComponent? where T4 : IComponent? where T5 : IComponent? where T6 : IComponent?
{
public EntityUid Owner;
@@ -495,6 +501,7 @@ public record struct Entity<T1, T2, T3, T4, T5, T6>
public T4 Comp4;
public T5 Comp5;
public T6 Comp6;
EntityUid IFluentEntityUid.FluentOwner => Owner;
public Entity(EntityUid owner, T1 comp1, T2 comp2, T3 comp3, T4 comp4, T5 comp5, T6 comp6)
{
@@ -658,7 +665,7 @@ public record struct Entity<T1, T2, T3, T4, T5, T6>
#endregion
}
public record struct Entity<T1, T2, T3, T4, T5, T6, T7>
public record struct Entity<T1, T2, T3, T4, T5, T6, T7> : IFluentEntityUid
where T1 : IComponent? where T2 : IComponent? where T3 : IComponent? where T4 : IComponent? where T5 : IComponent? where T6 : IComponent? where T7 : IComponent?
{
public EntityUid Owner;
@@ -669,6 +676,7 @@ public record struct Entity<T1, T2, T3, T4, T5, T6, T7>
public T5 Comp5;
public T6 Comp6;
public T7 Comp7;
EntityUid IFluentEntityUid.FluentOwner => Owner;
public Entity(EntityUid owner, T1 comp1, T2 comp2, T3 comp3, T4 comp4, T5 comp5, T6 comp6, T7 comp7)
{
@@ -855,7 +863,7 @@ public record struct Entity<T1, T2, T3, T4, T5, T6, T7>
#endregion
}
public record struct Entity<T1, T2, T3, T4, T5, T6, T7, T8>
public record struct Entity<T1, T2, T3, T4, T5, T6, T7, T8> : IFluentEntityUid
where T1 : IComponent? where T2 : IComponent? where T3 : IComponent? where T4 : IComponent? where T5 : IComponent? where T6 : IComponent? where T7 : IComponent? where T8 : IComponent?
{
public EntityUid Owner;
@@ -867,6 +875,7 @@ public record struct Entity<T1, T2, T3, T4, T5, T6, T7, T8>
public T6 Comp6;
public T7 Comp7;
public T8 Comp8;
EntityUid IFluentEntityUid.FluentOwner => Owner;
public Entity(EntityUid owner, T1 comp1, T2 comp2, T3 comp3, T4 comp4, T5 comp5, T6 comp6, T7 comp7, T8 comp8)
{

View File

@@ -114,6 +114,15 @@ namespace Robust.Shared.GameObjects
private delegate void DirectedEventHandler<TEvent>(EntityUid uid, IComponent comp, ref TEvent args)
where TEvent : notnull;
/// <summary>
/// Max size of a components event subscription linked list.
/// Used to limit the stackalloc in <see cref="EntDispatch"/>
/// </summary>
/// <remarks>
/// SS14 currently requires only 18, I doubt it will ever need to exceed 256.
/// </remarks>
private const int MaxEventLinkedListSize = 256;
/// <summary>
/// Constructs a new instance of <see cref="EntityEventBus"/>.
/// </summary>
@@ -138,7 +147,7 @@ namespace Robust.Shared.GameObjects
DispatchComponent<TEvent>(
component.Owner,
component,
CompIdx.Index(component.GetType()),
_comFac.GetIndex(component.GetType()),
ref unitRef);
}
@@ -161,7 +170,7 @@ namespace Robust.Shared.GameObjects
DispatchComponent<TEvent>(
component.Owner,
component,
CompIdx.Index(component.GetType()),
_comFac.GetIndex(component.GetType()),
ref unitRef);
}
@@ -390,7 +399,7 @@ namespace Robust.Shared.GameObjects
public void OnComponentRemoved(in RemovedComponentEventArgs e)
{
EntRemoveComponent(e.BaseArgs.Owner, CompIdx.Index(e.BaseArgs.Component.GetType()));
EntRemoveComponent(e.BaseArgs.Owner, _comFac.GetIndex(e.BaseArgs.Component.GetType()));
}
private void EntAddSubscription(
@@ -488,7 +497,7 @@ namespace Robust.Shared.GameObjects
DebugTools.Assert(eventTable.Free >= 0);
ref var eventStartIdx = ref CollectionsMarshal.GetValueRefOrAddDefault(
ref var indices = ref CollectionsMarshal.GetValueRefOrAddDefault(
eventTable.EventIndices,
evType,
out var exists);
@@ -500,10 +509,13 @@ namespace Robust.Shared.GameObjects
// Set it up
entry.Component = compType;
entry.Next = exists ? eventStartIdx : -1;
entry.Next = exists ? indices.Start : -1;
// Assign new list entry to EventIndices dictionary.
eventStartIdx = entryIdx;
indices.Start = entryIdx;
indices.Count++;
if (indices.Count > MaxEventLinkedListSize)
throw new NotSupportedException($"Exceeded maximum event linked list size. Need to implement stackalloc fallback.");
}
}
@@ -541,40 +553,37 @@ namespace Robust.Shared.GameObjects
foreach (var evType in compSubs.Keys)
{
DebugTools.Assert(!_eventData[evType].ComponentEvent);
ref var dictIdx = ref CollectionsMarshal.GetValueRefOrNullRef(eventTable.EventIndices, evType);
if (Unsafe.IsNullRef(ref dictIdx))
ref var indices = ref CollectionsMarshal.GetValueRefOrNullRef(eventTable.EventIndices, evType);
if (Unsafe.IsNullRef(ref indices))
{
DebugTools.Assert("This should not be possible. Were the events for this component never added?");
continue;
}
ref var updateNext = ref dictIdx;
var entryIdx = indices.Start;
ref var entry = ref eventTable.ComponentLists[entryIdx];
// Go over linked list to find index of component.
var entryIdx = dictIdx;
ref var entry = ref Unsafe.NullRef<EventTableListEntry>();
while (true)
{
entry = ref eventTable.ComponentLists[entryIdx];
if (entry.Component == compType)
{
// Found
break;
}
entryIdx = entry.Next;
updateNext = ref entry.Next;
}
if (entry.Next == -1 && Unsafe.AreSame(ref dictIdx, ref updateNext))
if (indices.Count == 1)
{
// Last entry for this event type, remove from dict.
DebugTools.AssertEqual(entry.Next, -1);
eventTable.EventIndices.Remove(evType);
}
else
{
ref var updateNext = ref indices.Start;
// Go over linked list to find index of component.
while (entry.Component != compType)
{
updateNext = ref entry.Next;
entryIdx = entry.Next;
entry = ref eventTable.ComponentLists[entryIdx];
}
// Rewrite previous index to point to next in chain.
updateNext = entry.Next;
indices.Count--;
}
// Push entry back onto free list.
@@ -585,15 +594,33 @@ namespace Robust.Shared.GameObjects
private void EntDispatch(EntityUid euid, Type eventType, ref Unit args)
{
if (!EntTryGetSubscriptions(eventType, euid, out var enumerator))
if (!_entEventTables.TryGetValue(euid, out var eventTable))
return;
while (enumerator.MoveNext(out var component, out var reg))
{
if (component.Deleted)
continue;
if (!eventTable.EventIndices.TryGetValue(eventType, out var indices))
return;
reg.Handler(euid, component, ref args);
DebugTools.Assert(indices.Count > 0);
DebugTools.Assert(indices.Start >= 0);
// First, collect all subscribing components.
// This is to avoid infinite loops over the linked list if subscription handlers add or remove components.
Span<CompIdx> compIds = stackalloc CompIdx[indices.Count];
var idx = indices.Start;
for (var index = 0; index < compIds.Length; index++)
{
DebugTools.Assert(idx >= 0);
ref var entry = ref eventTable.ComponentLists[idx];
idx = entry.Next;
compIds[index] = entry.Component;
}
foreach (var compIdx in compIds)
{
if (!_entMan.TryGetComponent(euid, compIdx, out var comp))
continue;
var compSubs = _eventSubs[compIdx.Value];
compSubs[eventType].Handler(euid, comp, ref args);
}
}
@@ -602,16 +629,30 @@ namespace Robust.Shared.GameObjects
Type eventType,
ref ValueList<OrderedEventDispatch> found)
{
if (!EntTryGetSubscriptions(eventType, euid, out var enumerator))
if (!_entEventTables.TryGetValue(euid, out var eventTable))
return;
while (enumerator.MoveNext(out var component, out var reg))
if (!eventTable.EventIndices.TryGetValue(eventType, out var indices))
return;
DebugTools.Assert(indices.Count > 0);
DebugTools.Assert(indices.Start >= 0);
var idx = indices.Start;
while (idx != -1)
{
found.Add(new OrderedEventDispatch((ref Unit ev) =>
{
if (!component.Deleted)
reg.Handler(euid, component, ref ev);
}, reg.Order));
ref var entry = ref eventTable.ComponentLists[idx];
idx = entry.Next;
var comp = _entMan.GetComponentInternal(euid, entry.Component);
var compSubs = _eventSubs[entry.Component.Value];
var reg = compSubs[eventType];
found.Add(new OrderedEventDispatch(
(ref Unit ev) =>
{
if (!comp.Deleted)
reg.Handler(euid, comp, ref ev);
},
reg.Order));
}
}
@@ -626,28 +667,6 @@ namespace Robust.Shared.GameObjects
reg.Handler(euid, component, ref args);
}
/// <summary>
/// Enumerates all subscriptions for an event on a specific entity, returning the component instances and registrations.
/// </summary>
private bool EntTryGetSubscriptions(Type eventType, EntityUid euid, out SubscriptionsEnumerator enumerator)
{
if (!_entEventTables.TryGetValue(euid, out var eventTable))
{
enumerator = default!;
return false;
}
// No subscriptions to this event type, return null.
if (!eventTable.EventIndices.TryGetValue(eventType, out var startEntry))
{
enumerator = default;
return false;
}
enumerator = new(eventType, startEntry, eventTable.ComponentLists, _eventSubs, euid, _entMan);
return true;
}
public void ClearSubscriptions()
{
_subscriptionLock = false;
@@ -678,59 +697,6 @@ namespace Robust.Shared.GameObjects
_eventSubsInv = null!;
}
private struct SubscriptionsEnumerator
{
private readonly Type _eventType;
private readonly EntityUid _uid;
private readonly FrozenDictionary<Type, DirectedRegistration>[] _subscriptions;
private readonly IEntityManager _entityManager;
private readonly EventTableListEntry[] _list;
private int _idx;
public SubscriptionsEnumerator(
Type eventType,
int startEntry,
EventTableListEntry[] list,
FrozenDictionary<Type, DirectedRegistration>[] subscriptions,
EntityUid uid,
IEntityManager entityManager)
{
_eventType = eventType;
_list = list;
_subscriptions = subscriptions;
_idx = startEntry;
_entityManager = entityManager;
_uid = uid;
}
public bool MoveNext(
[NotNullWhen(true)] out IComponent? component,
[NotNullWhen(true)] out DirectedRegistration? registration)
{
if (_idx == -1)
{
component = null;
registration = null;
return false;
}
ref var entry = ref _list[_idx];
_idx = entry.Next;
var compType = entry.Component;
var compSubs = _subscriptions[compType.Value];
if (!compSubs.TryGetValue(_eventType, out registration))
{
component = default;
return false;
}
component = _entityManager.GetComponentInternal(_uid, compType);
return true;
}
}
internal sealed class DirectedRegistration : OrderedRegistration
{
public readonly Delegate Original;
@@ -760,7 +726,7 @@ namespace Robust.Shared.GameObjects
// Free contains the first free linked list node, or -1 if there is none.
// Free nodes form their own linked list.
// ComponentList is the actual region of memory containing linked list nodes.
public readonly Dictionary<Type, int> EventIndices = new();
public readonly Dictionary<Type, (int Start, int Count)> EventIndices = new();
public int Free;
public EventTableListEntry[] ComponentLists = new EventTableListEntry[InitialListSize];

View File

@@ -35,6 +35,9 @@ namespace Robust.Shared.GameObjects
if (broadcast)
CollectBroadcastOrdered(EventSource.Local, subs, ref found);
// TODO PERF
// consider ordering the linked list itself?
// Then make broadcast events always a lower priority and replace the valuelist with stackalloc?
EntCollectOrdered(uid, eventType, ref found);
DispatchOrderedEvents(ref unitRef, ref found);

View File

@@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using JetBrains.Annotations;
using Robust.Shared.GameStates;
using Robust.Shared.Log;
@@ -125,7 +126,7 @@ namespace Robust.Shared.GameObjects
foreach (var comp in comps)
{
if (comp is { LifeStage: ComponentLifeStage.Added })
LifeInitialize(comp, CompIdx.Index(comp.GetType()));
LifeInitialize(comp, _componentFactory.GetIndex(comp.GetType()));
}
#if DEBUG
@@ -998,7 +999,7 @@ namespace Robust.Shared.GameObjects
public EntityQuery<IComponent> GetEntityQuery(Type type)
{
var comps = _entTraitArray[CompIdx.ArrayIndex(type)];
var comps = _entTraitDict[type];
DebugTools.Assert(comps != null, $"Unknown component: {type.Name}");
return new EntityQuery<IComponent>(comps, _resolveSawmill);
}
@@ -1563,7 +1564,9 @@ namespace Robust.Shared.GameObjects
}
if (logMissing)
_sawmill.Error($"Can't resolve \"{typeof(TComp1)}\" on entity {uid}!\n{new StackTrace(1, true)}");
{
_sawmill.Error($"Can't resolve \"{typeof(TComp1)}\" on entity {uid}!\n{Environment.StackTrace}");
}
return false;
}

View File

@@ -73,12 +73,12 @@ public partial class EntityManager
return ents;
}
public virtual EntityUid SpawnAttachedTo(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
public virtual EntityUid SpawnAttachedTo(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
{
if (!coordinates.IsValid(this))
throw new InvalidOperationException($"Tried to spawn entity {protoName} on invalid coordinates {coordinates}.");
var entity = CreateEntityUninitialized(protoName, coordinates, overrides);
var entity = CreateEntityUninitialized(protoName, coordinates, overrides, rotation);
InitializeAndStartEntity(entity, coordinates.GetMapId(this));
return entity;
}

View File

@@ -1,14 +1,17 @@
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
namespace Robust.Shared.GameObjects;
public partial class EntityManager
{
[Pure]
public T System<T>() where T : IEntitySystem
{
return _entitySystemManager.GetEntitySystem<T>();
}
[Pure]
public T? SystemOrNull<T>() where T : IEntitySystem
{
return _entitySystemManager.GetEntitySystemOrNull<T>();

View File

@@ -296,10 +296,12 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
{
var newEntity = CreateEntity(prototypeName, out _, overrides);
_xforms.SetCoordinates(newEntity, TransformQuery.GetComponent(newEntity), coordinates, unanchor: false);
var xformComp = TransformQuery.GetComponent(newEntity);
_xforms.SetCoordinates(newEntity, xformComp, coordinates, rotation: rotation, unanchor: false);
return newEntity;
}

View File

@@ -8,7 +8,6 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using TerraFX.Interop.Windows;
namespace Robust.Shared.GameObjects;
@@ -76,6 +75,15 @@ public partial class EntitySystem
return LifeStage(uid, metaData) >= EntityLifeStage.Terminating;
}
/// <summary>
/// Checks whether the entity is being or has been deleted (or never existed in the first place).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool TerminatingOrDeleted(EntityUid? uid, MetaDataComponent? metaData = null)
{
return !uid.HasValue || TerminatingOrDeleted(uid.Value, metaData);
}
[Obsolete("Use override without the EntityQuery")]
protected bool Deleted(EntityUid uid, EntityQuery<MetaDataComponent> metaQuery) => Deleted(uid);

View File

@@ -1,3 +1,4 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
@@ -26,8 +27,10 @@ namespace Robust.Shared.GameObjects
var found = EntityManager.TryGetComponent(uid, out component);
if(logMissing && !found)
Log.Error($"Can't resolve \"{typeof(TComp)}\" on entity {ToPrettyString(uid)}!\n{new StackTrace(1, true)}");
if (logMissing && !found)
{
Log.Error($"Can't resolve \"{typeof(TComp)}\" on entity {ToPrettyString(uid)}!\n{Environment.StackTrace}");
}
return found;
}

View File

@@ -0,0 +1,7 @@
namespace Robust.Shared.GameObjects;
/// <summary>
/// Raised directed on an entity when its name is changed.
/// </summary>
[ByRefEvent]
public readonly record struct EntityRenamedEvent(string NewName);

View File

@@ -77,6 +77,18 @@ namespace Robust.Shared.GameObjects
/// <returns>The availability of the component.</returns>
ComponentAvailability GetComponentAvailability(string componentName, bool ignoreCase = false);
/// <summary>
/// Slow-path for Type -> CompIdx mapping without generics.
/// </summary>
[Pure]
CompIdx GetIndex(Type type);
/// <summary>
/// Slow-path to get the component index for a specified type.
/// </summary>
[Pure]
int GetArrayIndex(Type type);
/// <summary>
/// Registers a component class with the factory.
/// </summary>

View File

@@ -38,7 +38,7 @@ public partial interface IEntityManager
/// <summary>
/// Spawns an entity and then parents it to the entity that the given entity coordinates are relative to.
/// </summary>
EntityUid SpawnAttachedTo(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null);
EntityUid SpawnAttachedTo(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default);
/// <summary>
/// Resolves the given entity coordinates into world coordinates and spawns an entity at that location. The

View File

@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
namespace Robust.Shared.GameObjects;
@@ -9,6 +10,7 @@ public partial interface IEntityManager
/// </summary>
/// <typeparam name="T">The type of entity system to find.</typeparam>
/// <returns>The <see cref="IEntitySystem"/> instance matching the specified type.</returns>
[Pure]
T System<T>() where T : IEntitySystem;
/// <summary>
@@ -16,6 +18,7 @@ public partial interface IEntityManager
/// </summary>
/// <typeparam name="T">The type of entity system to find.</typeparam>
/// <returns>The <see cref="IEntitySystem"/> instance matching the specified type, or null.</returns>
[Pure]
T? SystemOrNull<T>() where T : IEntitySystem;
/// <summary>

View File

@@ -96,7 +96,7 @@ namespace Robust.Shared.GameObjects
/// <param name="coordinates">Coordinates to set position and parent of the newly spawned entity to.</param>
/// <param name="overrides"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
/// <returns><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></returns>
EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null);
EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default);
/// <summary>
/// Creates an uninitialized entity and puts it on the grid or map at the MapCoordinates provided.

View File

@@ -49,7 +49,7 @@ public readonly struct NetEntity : IEquatable<NetEntity>, IComparable<NetEntity>
public static NetEntity Parse(ReadOnlySpan<char> uid)
{
if (uid.Length == 0)
return default;
throw new FormatException($"An empty string is not a valid NetEntity");
if (uid[0] != 'c')
return new NetEntity(int.Parse(uid));

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