Compare commits

..

167 Commits

Author SHA1 Message Date
metalgearsloth
dc32549c75 Version: 0.14.7.0 2022-05-14 14:55:56 +10:00
metalgearsloth
17dd3af3c7 ECS metadata pausing (#2800) 2022-05-14 14:55:42 +10:00
metalgearsloth
32bdc19fe9 Final grid movement optimisation (#2711) 2022-05-14 14:54:31 +10:00
metalgearsloth
70fbefbe62 Fix joints crash (#2816) 2022-05-14 14:51:45 +10:00
Vera Aguilera Puerto
64cd6ea9be Adds GridUid field to TileRef. (#2815)
* Adds GridUid field to TileRef.

* actually compiles
2022-05-13 18:00:33 +10:00
metalgearsloth
aa92c2444d Version: 0.14.6.0 2022-05-13 12:07:59 +10:00
metalgearsloth
9b19626f1b Port mousejoint from box2d (#2814) 2022-05-13 00:21:39 +10:00
Moony
2bbe5193ee Moves that disable connect option nobody can use to somewhere people can use it. (#2812) 2022-05-12 16:02:12 +02:00
metalgearsloth
d11318f082 Make tile placement much less jank (#2785) 2022-05-12 22:27:06 +10:00
wrexbe
237f948d99 BinaryPrimitives > BitConverter (#2813)
* BinaryPrimitives > BitConverter

* Forgot a :
2022-05-12 07:57:21 +02:00
Kara D
f3449be1e9 Version: 0.14.5.0 2022-05-11 13:43:58 -07:00
Vera Aguilera Puerto
b95ffda711 EntityCoordinates methods for getting grid/map EntityUid.
Uses the new Transform properties that return grid/map EntityUids.
2022-05-11 14:24:46 +02:00
Moony
0932fa0058 Add the concurrent collections and BitConverter to the allow list. (#2810) 2022-05-11 00:11:32 +02:00
Pieter-Jan Briers
da2e46b81b Increase command timeout on benchmark SSh action 2022-05-10 16:05:53 +02:00
Pieter-Jan Briers
d4a1fcc9c6 Fix database code for benchmarks. 2022-05-10 15:44:25 +02:00
Pieter-Jan Briers
c7e7f4f28f make run_benchmarks.py handle errors better. 2022-05-10 14:44:53 +02:00
Pieter-Jan Briers
87e191b8d7 Benchmarks CI attempt 6 2022-05-10 14:35:12 +02:00
Pieter-Jan Briers
05b21fcfcc Benchmark CI attempt 5 2022-05-10 11:54:25 +02:00
Pieter-Jan Briers
48e4ae87d8 Benchmark CI attempt 4(?) 2022-05-10 11:18:04 +02:00
Pieter-Jan Briers
8b090034f1 Benchmarks CI fix attempt 3 2022-05-10 11:14:17 +02:00
Pieter-Jan Briers
41c183ac09 Fix GITHUB_SHA? 2022-05-10 11:10:57 +02:00
Pieter-Jan Briers
4380b95451 Fix benchmarks attempt 1 2022-05-10 11:06:57 +02:00
Pieter-Jan Briers
90fc486a20 Allow manual dispatch of benchmarks 2022-05-10 11:00:52 +02:00
Paul Ritter
f254153962 benchmark script (#2745)
* benchmark script

* more changes

* oopsie

* adjust workflow

* Put a concurrency limit on the benchmarks action

* Update run_benchmarks.py

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2022-05-10 10:58:42 +02:00
Moony
a2538d1905 Add the setter for images to the sandbox list (#2802) 2022-05-10 09:35:55 +02:00
metalgearsloth
5d7a2879a7 Fix scaling consistency between sprite and bounds (#2803) 2022-05-10 00:34:07 -07:00
20kdc
986adf6494 RobustToolbox side of Redial API. (#2804)
* RobustToolbox side of Redial API.

Please be aware that as far as I know testing this code is nigh-impossible until it ships.

* a smidge of paranoia

* paranoia on the paranoia please
2022-05-10 09:24:46 +02:00
Pieter-Jan Briers
15932fb9aa DebugTools.Assert(bool, string) does not allocate if string is a format string anymore. 2022-05-10 00:59:36 +02:00
Pieter-Jan Briers
3ae4bb03f5 Enable NetManager latency sim on release, add no-bye to INetChannel.
A minute amount of tomfoolery.
2022-05-09 15:43:30 +02:00
metalgearsloth
7f47478997 Make physics testbed slightly less annoying to use (#2794) 2022-05-08 23:28:54 +10:00
metalgearsloth
a2923450ea Optimise occluder dirtying (#2801)
Mostly when stuff spawning in.
2022-05-08 15:16:56 +10:00
metalgearsloth
5366b9a35b Cleanup collisionmanager (#2792)
1. Split it into partials because my scroll wheel was hating me.
2. Removed AABB shapetypes because contacts were made fast enough it doesn't matter.
2022-05-08 14:41:36 +10:00
metalgearsloth
5ca37c68e2 Optimise physics init (#2786) 2022-05-08 14:39:54 +10:00
Vera Aguilera Puerto
22aa274d03 Add GridUid and MapUid properties to TransformComponent. (#2798) 2022-05-07 22:43:00 +10:00
metalgearsloth
54bbe55bb9 Version: 0.14.4.0 2022-05-07 21:42:57 +10:00
metalgearsloth
9706576e66 Fix grid splits for empty chunks (#2797) 2022-05-07 13:26:26 +10:00
metalgearsloth
b2ccd65da4 Fix showgridnodes colours (#2796) 2022-05-07 11:17:28 +10:00
Paul
c587cd2e12 hm 2022-05-06 18:36:50 +02:00
Paul Ritter
ba23a989a9 make pvs account for lastack properly (#2733) 2022-05-06 18:09:30 +02:00
metalgearsloth
c6c69668c8 Fix polyshape centroids (#2793) 2022-05-05 22:42:35 +10:00
Leon Friedrich
b0a4593f44 Speed up map init, and unpause map on init (#2781)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-05-05 19:21:03 +10:00
metalgearsloth
2d6e699e2c Mark component name as obsolete (#2791) 2022-05-05 18:42:38 +10:00
metalgearsloth
4307509402 ECS even more transform (#2742)
Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
2022-05-05 13:20:23 +10:00
Paul
ed165b1e4c removes some iocresolves in mapmanager 2022-05-04 23:17:47 +02:00
Paul
3503300a23 minor serv3 optimization 2022-05-04 23:05:28 +02:00
Paul
3ab3d255ed fix seed benchmark 2022-05-04 18:43:17 +02:00
metalgearsloth
fd625e0b30 Fix scale command (#2784)
* Fix scale command

Everything will be visualizers.

* comment for future coders
2022-05-04 22:49:42 +10:00
ElectroJr
53af37cd56 Version: 0.14.3.1 2022-04-30 01:51:51 +12:00
Leon Friedrich
6bf4945181 Allow int uniforms to specify precision (#2779) 2022-04-29 23:48:08 +10:00
metalgearsloth
868320c513 Fix VV lineedit crash 2022-04-29 00:41:14 +10:00
Leon Friedrich
577b836420 Fix grid-traversal parenting (#2777) 2022-04-28 18:08:56 +10:00
metalgearsloth
4a36b52657 Nuke an xform log
Probably not worth it.
2022-04-27 00:35:38 +10:00
metalgearsloth
8dc944d22d Stop invalid broadphase change from crashing the game (#2717) 2022-04-26 23:25:28 +10:00
metalgearsloth
0a10170155 Fix map change crash (#2716)
* Fix map change crash

Follower really out here exposing all the ordering bugs.

* Log AttachToGridOrMap
2022-04-26 23:21:58 +10:00
metalgearsloth
dfa2c222d1 Version: 0.14.3.0 2022-04-26 01:18:07 +10:00
metalgearsloth
e288af162c Add event for post-grid-split (#2774) 2022-04-26 01:17:22 +10:00
Kara
6280820c86 Merge pull request #2768 from ShadowCommander/errorcheck-vv-lineedit
Add null check to VV string parsing
2022-04-24 22:51:49 -07:00
ShadowCommander
102b31f83d Add null check to VV string parsing 2022-04-24 02:16:42 -07:00
metalgearsloth
37ca9ca19a Version: 0.14.2.4 2022-04-24 16:40:10 +10:00
metalgearsloth
0c151ad456 Fix grid deletion for SetTiles (#2767) 2022-04-24 16:39:57 +10:00
metalgearsloth
55e3f749b4 Version: 0.14.2.3 2022-04-24 16:07:19 +10:00
metalgearsloth
16902d7fbc Hotfix anchoring (#2764) 2022-04-24 16:06:50 +10:00
metalgearsloth
c37f53e083 Issue re-parent event on split (#2765) 2022-04-24 15:25:55 +10:00
metalgearsloth
4cc499afdf Version: 0.14.2.2 2022-04-24 13:22:39 +10:00
20kdc
48895efad7 Flexible object pool for ZStd compression contexts rather than the array-based version (#2763) 2022-04-23 20:17:59 +02:00
metalgearsloth
eb46b04c0e Version: 0.14.2.1 2022-04-24 00:58:38 +10:00
metalgearsloth
442de12b99 Grid splitting (#2743)
Co-authored-by: Vera Aguilera Puerto <gradientvera@outlook.com>
2022-04-24 00:57:56 +10:00
Vera Aguilera Puerto
712c1f3559 Adds "ressys" and IEntitySystemManager dependency to scripting globals. (#2762)
"ressys" resolves an `EntitySystem`.
2022-04-24 00:55:31 +10:00
Vera Aguilera Puerto
5a5cfa1eba Adds GetGridOrMapTilePosition to SharedTransformSystem. (#2759)
A little helper method that would be VERY useful for many content things... Such as atmos.
2022-04-24 00:55:11 +10:00
metalgearsloth
7988386bc3 Version: 0.14.1.1 2022-04-24 00:53:41 +10:00
Leon Friedrich
76bfe2905d Add support for float[] and vec2[] in shaders (#2751)
* huh it works.

* vector arrays
2022-04-22 22:43:17 +02:00
ShadowCommander
23893bcacd Change LineEdit to keep cursor position (#2752)
* Change LineEdit to keep cursor position

* Revert console changes
2022-04-22 22:42:24 +02:00
Pieter-Jan Briers
c1da159e8f Rely on SharpZstd -Bsymbolic 2022-04-22 22:39:59 +02:00
20kdc
13889acb37 Fix crash caused by passing uninitialized structure to ZSTD_inBuffer (#2760) 2022-04-22 14:10:20 +02:00
Vera Aguilera Puerto
ffb3de3f2c Fix loading soundfonts from filesystem on Linux 2022-04-21 17:52:27 +02:00
Flipp Syder
87cd45fc64 Adds custom midi soundfont support (sandboxed) (#2746) 2022-04-21 10:05:57 +02:00
Leon Friedrich
1cf914813c Add PVSOverrideSystem (#2747)
* Add PVSOverrideSystem

* add session and clear functions
2022-04-20 16:00:18 +02:00
Pieter-Jan Briers
86d61f8d03 Version: 0.14.0.1 2022-04-20 15:39:32 +02:00
Pieter-Jan Briers
8f638fbf9e HOTFIX ParallelManager out of bounds
I could have sworn I made this -1.

Completely untested
2022-04-20 15:21:17 +02:00
metalgearsloth
95ac134a07 Comment out msgstate logger (#2754)
Getting spammed and I don't think we want to log here long-term anyway.
2022-04-20 13:13:15 +02:00
metalgearsloth
4e65f9b2a1 Version: 0.14.0.0 2022-04-20 18:48:06 +10:00
Paul
d60bbe9fe9 misc client optimizations 2022-04-18 22:34:49 +02:00
Pieter-Jan Briers
dec1495e1e Remove reference to ManagedHttpListener.
How did this ever compile?
2022-04-17 19:13:24 +02:00
Paul
dc72c6fe22 fix typo 2022-04-17 18:15:41 +02:00
Paul
141b1205c6 removes some todos 2022-04-17 18:09:46 +02:00
Pieter-Jan Briers
65f4a09ad5 Try to fix ZStd stuff for servers. 2022-04-17 18:02:35 +02:00
Pieter-Jan Briers
3d1545c0b9 Fix incorrect unsubscription to AssemblyLoadContext.Default.Resolving.
+= instead of -=.
2022-04-17 18:02:35 +02:00
metalgearsloth
ec26dd622b DetachParentToNull tweaks (#2741)
* DetachParentToNull tweaks

The other parent change message already has the mapid and gridid updated when issuing the event.

We'll also guard the event by checking if they're already in nullspace.

* woops
2022-04-17 17:28:07 +10:00
metalgearsloth
0ab3131964 ECS transform states (#2710)
* ECS transform states

Turns out it also saves us a GetComponent on the parent too which is nice.

* Fix test
2022-04-17 14:55:29 +10:00
Pieter-Jan Briers
588a9e9f63 ZSTD game states + other improvements (#2737) 2022-04-16 14:36:59 +02:00
Pieter-Jan Briers
f2fa930edd Block explicit layout types in sandboxing. 2022-04-16 14:17:37 +02:00
Kara D
ec47229a37 Version: 0.13.0.0 2022-04-15 14:36:50 -07:00
mirrorcult
bf5d1d58a8 Merge pull request #2715 from moonheart08/better-map-loading 2022-04-15 14:34:48 -07:00
mirrorcult
8b4da24ee7 Merge pull request #2722 from PaulRitter/2022_04_12_abstract_validation 2022-04-15 14:34:22 -07:00
mirrorcult
3fba108d70 Merge pull request #2734 from PJB3005/22-04-14-spacewizards-http 2022-04-15 14:34:01 -07:00
mirrorcult
35029f0eed Merge pull request #2692 from vulppine/color-sliders 2022-04-15 14:33:21 -07:00
mirrorcult
b66ab9d7c6 Merge pull request #2727 from ElectroJr/update-mapping-except 2022-04-15 14:31:30 -07:00
mirrorcult
5e0b745ba9 Merge pull request #2739 from PJB3005/22-04-15-remove-createnetmsg 2022-04-15 14:30:32 -07:00
Pieter-Jan Briers
45d906ba7e Deprecate CreateNetMessage<T>. 2022-04-15 18:47:42 +02:00
Pieter-Jan Briers
24b124fb17 Fix client reconnect 2022-04-15 15:28:45 +02:00
Vera Aguilera Puerto
7cb0978468 Revert "Adds custom MIDI soundfont support (#2720)"
This reverts commit 9ff46b9aad.

Sandbox violation, oops.
2022-04-15 12:46:30 +02:00
mirrorcult
44cb135a1d Even shorter pretty-print type abbreviations for VV (#2735) 2022-04-15 09:15:10 +02:00
Pieter-Jan Briers
146b673203 Move string serializer to ZStd and BLAKE2b.
Faster and smaller
2022-04-15 01:16:35 +02:00
Pieter-Jan Briers
b0d23c5665 Remove zstd & libsodium natives from client publishes.
Save a good megabyte.
2022-04-15 00:39:56 +02:00
Pieter-Jan Briers
68f89c8958 SkipIfSandboxedAttribute
Should allow something like OpenDream to provide unsandboxed-only DLLs easily.
2022-04-15 00:31:17 +02:00
Pieter-Jan Briers
1327d6bf25 Replace ManagedHttpListener submodule in favor of NuGet package.
It's SpaceWizards.HttpListener now!
2022-04-14 23:53:57 +02:00
Pieter-Jan Briers
237e37ff30 Update Lidgren submodule 2022-04-14 23:47:17 +02:00
Pieter-Jan Briers
4d707c86cb Version: 0.12.1.0 2022-04-14 17:17:17 +02:00
Pieter-Jan Briers
c7027c6e00 ACZ manifest delta downloads (#2698) 2022-04-14 17:15:54 +02:00
Paul Ritter
81ec61bcc8 overflowqueue (#2721)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2022-04-14 10:57:28 +02:00
DrSmugleaf
649178fa54 Rename engine prototypes folder to EnginePrototypes (#2723) 2022-04-14 05:46:29 +02:00
Flipp Syder
9ff46b9aad Adds custom MIDI soundfont support (#2720)
Co-authored-by: Vera Aguilera Puerto <6766154+Zumorica@users.noreply.github.com>
2022-04-13 11:59:43 +02:00
ElectroJr
cdcbb60ca7 more comments 2022-04-13 21:46:40 +12:00
ElectroJr
d8cdb2b312 comments 2022-04-13 21:43:47 +12:00
ElectroJr
fa1c1b8e6e better equality 2022-04-13 21:39:48 +12:00
ElectroJr
2ded835602 Make MappingDataNode.Except() not recursive 2022-04-13 21:08:59 +12:00
moonheart08
a43f04818d fix map commands. 2022-04-12 09:48:45 -05:00
moonheart08
278dc60119 use a query for xform. 2022-04-12 09:43:59 -05:00
moonheart08
8a202be0cf naming. 2022-04-12 09:34:18 -05:00
moonheart08
58940a3cd7 bp/map load now take arguments. 2022-04-11 23:54:15 -05:00
moonheart08
aa9721d146 fix tests 2022-04-11 23:15:37 -05:00
Paul
79f114ad5b work 2022-04-12 02:04:41 +02:00
Leon Friedrich
197a6ddd9d Fix sprite bb calculation (#2714) 2022-04-11 18:13:32 +02:00
metalgearsloth
4a4fb15e06 Add xform comp ref to parent change message (#2709) 2022-04-11 17:01:07 +10:00
metalgearsloth
e7e83ce6e8 Optimise fixture init slightly (#2707) 2022-04-11 16:58:48 +10:00
Leon Friedrich
a82b293452 Make RemoveComp not error on missing component (#2689) 2022-04-11 16:57:35 +10:00
Vera Aguilera Puerto
bb483a3a38 Add "midipanic" command. (#2701) 2022-04-11 16:55:12 +10:00
moonheart08
2ca3d0e395 Fix map loading once and for all. 2022-04-11 00:44:28 -05:00
metalgearsloth
bda79b1e82 Cache serialized types (#2706) 2022-04-11 12:50:34 +10:00
metalgearsloth
4f4b754e2d Don't allow velocity to be set where an obj can't collide (#2691) 2022-04-11 12:49:35 +10:00
mirrorcult
214cabac43 Merge pull request #2713 from oappiah/minor_config_update 2022-04-10 13:35:00 -07:00
Appiah
7b6229c222 Corrected the map example 2022-04-10 22:24:21 +02:00
Paul
1a14a75897 nullspace viewer crash fix 2022-04-10 17:52:12 +02:00
metalgearsloth
76b75fd9b3 Remove LINQ from sprite bounds (#2708) 2022-04-10 17:48:45 +10:00
Pieter-Jan Briers
b969fd22f7 Version: 0.12.0.1 2022-04-09 21:02:56 +02:00
Profane McBane
8d27d091af Hotfix - change PVS pools to use MaxVisPoolSize (#2704) 2022-04-09 20:54:41 +02:00
DrSmugleaf
1eb7393a60 Revert "pvsrange vec2 + eyezoom (#2676)" (#2703)
This reverts commit 582d8a5587.
2022-04-09 19:41:11 +02:00
Vera Aguilera Puerto
4cf88507c2 Version: 0.11.0.1 2022-04-09 13:21:38 +02:00
Vera Aguilera Puerto
3565d8b321 Fix synth state getting reset every MIDI player loop. 2022-04-09 13:21:09 +02:00
Vera Aguilera Puerto
7094c29b2e Version: 0.11.0.0 2022-04-08 16:07:42 +02:00
Vera Aguilera Puerto
63004b270f Cleans up and improves MIDI code significantly. (#2666) 2022-04-08 15:58:15 +02:00
metalgearsloth
6714a99b38 EntityLookup anchor flag test (#2699) 2022-04-08 13:36:24 +10:00
metalgearsloth
4c3b8df1e7 Fix anchor query (#2697) 2022-04-08 09:56:30 +10:00
metalgearsloth
4bb695121f Version: 0.10.0.0 2022-04-06 19:34:47 +10:00
metalgearsloth
09fd47c421 Don't store contained entities on entitylookup (#2662) 2022-04-06 19:31:34 +10:00
vulppine
d201d9c688 adds color sliders, fixes issues with RGB->HSL/V 2022-04-05 12:21:59 -07:00
Paul
fd1e25c584 Version: 0.9.3.2 2022-04-05 18:51:03 +02:00
Paul Ritter
6bb66ae70e fixes loc (#2685) 2022-04-05 18:50:27 +02:00
Paul
cc82d6b1d9 Version: 0.9.3.1 2022-04-05 18:36:19 +02:00
Paul
956be749b6 fix prototype reload 2022-04-05 18:36:09 +02:00
Paul Ritter
6585a00608 readds expandpvsevent (#2684) 2022-04-05 19:47:14 +10:00
metalgearsloth
c0525f710f Sprite subscription shutdown (#2688) 2022-04-05 19:45:23 +10:00
Vera Aguilera Puerto
d3672807d2 Improves SpriteSystem.GetPrototypeIcon method significantly. (#2680) 2022-04-05 16:36:16 +10:00
ElectroJr
60f18d5f36 Version: 0.9.3.0 2022-04-05 18:06:43 +12:00
mirrorcult
e72d3de256 Local event for BUI opening (#2687) 2022-04-05 15:52:02 +10:00
Moony
ba9846b9c4 Fix vector2 CVars (#2686) 2022-04-05 15:03:10 +10:00
Paul
09586284dc Version: 0.9.2.0 2022-04-04 20:28:53 +02:00
Paul
a1ee4374b2 add a check for major version == 0 2022-04-04 20:28:26 +02:00
Paul Ritter
4de6f25f11 updates version-script to use the new version format (#2683) 2022-04-04 20:25:12 +02:00
Paul Ritter
582d8a5587 pvsrange vec2 + eyezoom (#2676)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2022-04-04 20:20:13 +02:00
Leon Friedrich
ec53b04f99 Add NotNullWhen attribute to TryComp (#2681) 2022-04-04 11:29:02 +02:00
metalgearsloth
950fc94408 Physicsmap tweaks (#2663) 2022-04-04 17:10:15 +10:00
Leon Friedrich
58d12e6e09 Remove Component.OnAdd() (#2660) 2022-04-04 16:11:47 +10:00
ElectroJr
94323005c4 Version: 0.9.1 2022-04-04 17:43:01 +12:00
metalgearsloth
4989842057 Remove IgnorePause (#2649) 2022-04-04 15:41:38 +10:00
Paul
80172636a8 version 0.9 - serv3 refactor 2022-04-03 02:00:41 +02:00
Paul Ritter
8491f7be24 New Serv3 api just dropped (#2605)
Co-authored-by: Paul Ritter <ritter.paul1@gmail.com>
2022-04-03 01:59:48 +02:00
366 changed files with 13640 additions and 6895 deletions

View File

@@ -1,11 +1,13 @@
name: Benchmarks
#on:
# push
#schedule:
# - cron: '0 5 * * *'
#push:
# tags:
# - 'v*'
on:
workflow_dispatch:
schedule:
- cron: '0 5 * * *'
push:
tags:
- 'v*'
concurrency: benchmarks
env:
ROBUST_BENCHMARKS_ENABLE_SQL: 1
@@ -20,14 +22,14 @@ jobs:
name: Run Benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
- name: Install dependencies
run: dotnet restore
- name: Run benchmark
run: cd Robust.Benchmarks && sudo dotnet run --filter '*' --configuration Release
- name: Run script on centcomm
uses: appleboy/ssh-action@master
with:
host: centcomm.spacestation14.io
username: robust-benchmark-runner
key: ${{ secrets.CENTCOMM_ROBUST_BENCHMARK_RUNNER_KEY }}
command_timeout: 100000m
script: |
wget https://raw.githubusercontent.com/space-wizards/RobustToolbox/${{ github.sha }}/Tools/run_benchmarks.py
python3 run_benchmarks.py "${{ secrets.BENCHMARKS_WRITE_ADDRESS }}" "${{ secrets.BENCHMARKS_WRITE_PORT }}" "${{ secrets.BENCHMARKS_WRITE_USER }}" "${{ secrets.BENCHMARKS_WRITE_PASSWORD }}" "${{ github.sha }}"
rm run_benchmarks.py

3
.gitmodules vendored
View File

@@ -10,9 +10,6 @@
[submodule "Robust.LoaderApi"]
path = Robust.LoaderApi
url = https://github.com/space-wizards/Robust.LoaderApi.git
[submodule "ManagedHttpListener"]
path = ManagedHttpListener
url = https://github.com/space-wizards/ManagedHttpListener.git
[submodule "cefglue"]
path = cefglue
url = https://github.com/space-wizards/cefglue.git

View File

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

View File

@@ -0,0 +1,10 @@
color-selector-sliders-red = R
color-selector-sliders-green = G
color-selector-sliders-blue = B
color-selector-sliders-hue = H
color-selector-sliders-saturation = S
color-selector-sliders-value = V
color-selector-sliders-alpha = A
color-selector-sliders-rgb = RGB
color-selector-sliders-hsv = HSV

View File

@@ -0,0 +1 @@
midi-panic-command-description = Turns off every note for every active MIDI renderer.

View File

@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Mathematics;
@@ -11,6 +12,9 @@ using BenchmarkDotNet.Reports;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Npgsql;
using Npgsql.Internal;
using Npgsql.Internal.TypeHandlers;
using Npgsql.Internal.TypeHandling;
namespace Robust.Benchmarks.Exporters;
@@ -68,6 +72,13 @@ public sealed class SQLExporter : IExporter
using var ctx = new BenchmarkContext(builder.Options);
try
{
ctx.Database.OpenConnection();
var con = (NpgsqlConnection) ctx.Database.GetDbConnection();
con.TypeMapper.AddTypeResolverFactory(new JsonOverrideTypeHandlerResolverFactory(new JsonSerializerOptions
{
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
}));
ctx.Database.Migrate();
ctx.BenchmarkRuns.Add(BenchmarkRun.FromSummary(summary, gitHash));
ctx.SaveChanges();
@@ -81,6 +92,45 @@ public sealed class SQLExporter : IExporter
public string Name => "sql";
}
// https://github.com/npgsql/efcore.pg/issues/1107#issuecomment-945126627
class JsonOverrideTypeHandlerResolverFactory : TypeHandlerResolverFactory
{
private readonly JsonSerializerOptions _options;
public JsonOverrideTypeHandlerResolverFactory(JsonSerializerOptions options)
=> _options = options;
public override TypeHandlerResolver Create(NpgsqlConnector connector)
=> new JsonOverrideTypeHandlerResolver(connector, _options);
public override string? GetDataTypeNameByClrType(Type clrType)
=> null;
public override TypeMappingInfo? GetMappingByDataTypeName(string dataTypeName)
=> null;
class JsonOverrideTypeHandlerResolver : TypeHandlerResolver
{
readonly JsonHandler _jsonbHandler;
internal JsonOverrideTypeHandlerResolver(NpgsqlConnector connector, JsonSerializerOptions options)
=> _jsonbHandler ??= new JsonHandler(
connector.DatabaseInfo.GetPostgresTypeByName("jsonb"),
connector.TextEncoding,
isJsonb: true,
options);
public override NpgsqlTypeHandler? ResolveByDataTypeName(string typeName)
=> typeName == "jsonb" ? _jsonbHandler : null;
public override NpgsqlTypeHandler? ResolveByClrType(Type type)
// You can add any user-defined CLR types which you want mapped to jsonb
=> type == typeof(JsonDocument) ? _jsonbHandler : null;
public override TypeMappingInfo? GetMappingByDataTypeName(string dataTypeName)
=> null; // Let the built-in resolver do this
}
}
public sealed class DesignTimeContextFactoryPostgres : IDesignTimeDbContextFactory<BenchmarkContext>
{
public BenchmarkContext CreateDbContext(string[] args)
@@ -101,12 +151,14 @@ public class BenchmarkContext : DbContext
public class BenchmarkRun
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public ulong Id { get; set; }
public int Id { get; set; }
public string GitHash { get; set; } = string.Empty;
[Column(TypeName = "timestamptz")]
public DateTime RunDate { get; set; }
public string Name { get; set; } = string.Empty;
[Column(TypeName = "jsonb")]
public BenchmarkRunReport[] Reports { get; set; } = Array.Empty<BenchmarkRunReport>();

View File

@@ -0,0 +1,57 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Robust.Benchmarks.Exporters;
#nullable disable
namespace Robust.Benchmarks.Migrations
{
[DbContext(typeof(BenchmarkContext))]
[Migration("20220510131430_fix-pk")]
partial class fixpk
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Robust.Benchmarks.Exporters.BenchmarkRun", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("GitHash")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<BenchmarkRunReport[]>("Reports")
.IsRequired()
.HasColumnType("jsonb");
b.Property<DateTime>("RunDate")
.HasColumnType("timestamptz");
b.HasKey("Id");
b.ToTable("BenchmarkRuns");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Robust.Benchmarks.Migrations
{
public partial class fixpk : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTime>(
name: "RunDate",
table: "BenchmarkRuns",
type: "timestamptz",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "Date");
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "BenchmarkRuns",
type: "integer",
nullable: false,
oldClrType: typeof(decimal),
oldType: "numeric(20,0)")
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<DateTime>(
name: "RunDate",
table: "BenchmarkRuns",
type: "Date",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamptz");
migrationBuilder.AlterColumn<decimal>(
name: "Id",
table: "BenchmarkRuns",
type: "numeric(20,0)",
nullable: false,
oldClrType: typeof(int),
oldType: "integer")
.OldAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
}
}
}

View File

@@ -24,9 +24,11 @@ namespace Robust.Benchmarks.Migrations
modelBuilder.Entity("Robust.Benchmarks.Exporters.BenchmarkRun", b =>
{
b.Property<decimal>("Id")
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("numeric(20,0)");
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("GitHash")
.IsRequired()
@@ -41,7 +43,7 @@ namespace Robust.Benchmarks.Migrations
.HasColumnType("jsonb");
b.Property<DateTime>("RunDate")
.HasColumnType("Date");
.HasColumnType("timestamptz");
b.HasKey("Id");

View File

@@ -6,6 +6,7 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>../bin/Benchmarks</OutputPath>
<OutputType>Exe</OutputType>
<NoWarn>RA0003</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Robust.Server\Robust.Server.csproj" />
@@ -18,7 +19,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.0-rc.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
</ItemGroup>
<Import Project="..\MSBuild\Robust.Engine.targets" />
</Project>

View File

@@ -1,7 +1,6 @@
using System.Globalization;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
@@ -19,10 +18,10 @@ namespace Robust.Benchmarks.Serialization
: new ErrorNode(node, $"Failed parsing int value: {node.Value}");
}
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
public int Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, int _ = default)
{
return new DeserializedValue<int>(int.Parse(node.Value, CultureInfo.InvariantCulture));
return int.Parse(node.Value, CultureInfo.InvariantCulture);
}
public DataNode Write(ISerializationManager serializationManager, int value, bool alwaysWrite = false,

View File

@@ -28,7 +28,7 @@ namespace Robust.Benchmarks.Serialization.Copy
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
Seed = SerializationManager.Read<SeedDataDefinition>(seedMapping);
}
private const string String = "ABC";

View File

@@ -19,6 +19,7 @@ namespace Robust.Benchmarks.Serialization.Definitions
name: tobacco
seedName: tobacco
displayName: tobacco plant
plantRsi: Objects/Specific/Hydroponics/tobacco.rsi
productPrototypes:
- LeavesTobacco
harvestRepeat: Repeat
@@ -36,7 +37,7 @@ namespace Robust.Benchmarks.Serialization.Definitions
Max: 10
PotencyDivisor: 10";
[DataField("id", required: true)] public string ID { get; set; } = default!;
[IdDataFieldAttribute] public string ID { get; set; } = default!;
#region Tracking
[DataField("name")] public string Name { get; set; } = string.Empty;

View File

@@ -2,7 +2,6 @@
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Analyzers;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
@@ -42,32 +41,32 @@ namespace Robust.Benchmarks.Serialization.Read
private ValueDataNode FlagThirtyOne { get; } = new("ThirtyOne");
[Benchmark]
public string? ReadString()
public string ReadString()
{
return SerializationManager.ReadValue<string>(StringNode);
return SerializationManager.Read<string>(StringNode);
}
[Benchmark]
public int? ReadInteger()
public int ReadInteger()
{
return SerializationManager.ReadValue<int>(IntNode);
return SerializationManager.Read<int>(IntNode);
}
[Benchmark]
public DataDefinitionWithString? ReadDataDefinitionWithString()
public DataDefinitionWithString ReadDataDefinitionWithString()
{
return SerializationManager.ReadValue<DataDefinitionWithString>(StringDataDefNode);
return SerializationManager.Read<DataDefinitionWithString>(StringDataDefNode);
}
[Benchmark]
public SeedDataDefinition? ReadSeedDataDefinition()
public SeedDataDefinition ReadSeedDataDefinition()
{
return SerializationManager.ReadValue<SeedDataDefinition>(SeedNode);
return SerializationManager.Read<SeedDataDefinition>(SeedNode);
}
[Benchmark]
[BenchmarkCategory("flag")]
public DeserializationResult ReadFlagZero()
public object? ReadFlagZero()
{
return SerializationManager.ReadWithTypeSerializer(
typeof(int),
@@ -77,7 +76,7 @@ namespace Robust.Benchmarks.Serialization.Read
[Benchmark]
[BenchmarkCategory("flag")]
public DeserializationResult ReadThirtyOne()
public object? ReadThirtyOne()
{
return SerializationManager.ReadWithTypeSerializer(
typeof(int),
@@ -87,7 +86,7 @@ namespace Robust.Benchmarks.Serialization.Read
[Benchmark]
[BenchmarkCategory("customTypeSerializer")]
public DeserializationResult ReadIntegerCustomSerializer()
public object? ReadIntegerCustomSerializer()
{
return SerializationManager.ReadWithTypeSerializer(
typeof(int),

View File

@@ -46,84 +46,84 @@ namespace Robust.Benchmarks.Serialization
[BenchmarkCategory("read")]
public string[]? ReadEmptyString()
{
return SerializationManager.ReadValue<string[]>(EmptyNode);
return SerializationManager.Read<string[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public string[]? ReadOneString()
{
return SerializationManager.ReadValue<string[]>(OneIntNode);
return SerializationManager.Read<string[]>(OneIntNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public string[]? ReadTenStrings()
{
return SerializationManager.ReadValue<string[]>(TenIntsNode);
return SerializationManager.Read<string[]>(TenIntsNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public int[]? ReadEmptyInt()
{
return SerializationManager.ReadValue<int[]>(EmptyNode);
return SerializationManager.Read<int[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public int[]? ReadOneInt()
{
return SerializationManager.ReadValue<int[]>(OneIntNode);
return SerializationManager.Read<int[]>(OneIntNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public int[]? ReadTenInts()
{
return SerializationManager.ReadValue<int[]>(TenIntsNode);
return SerializationManager.Read<int[]>(TenIntsNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public DataDefinitionWithString[]? ReadEmptyStringDataDef()
{
return SerializationManager.ReadValue<DataDefinitionWithString[]>(EmptyNode);
return SerializationManager.Read<DataDefinitionWithString[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public DataDefinitionWithString[]? ReadOneStringDataDef()
{
return SerializationManager.ReadValue<DataDefinitionWithString[]>(OneStringDefNode);
return SerializationManager.Read<DataDefinitionWithString[]>(OneStringDefNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public DataDefinitionWithString[]? ReadTenStringDataDefs()
{
return SerializationManager.ReadValue<DataDefinitionWithString[]>(TenStringDefsNode);
return SerializationManager.Read<DataDefinitionWithString[]>(TenStringDefsNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public SealedDataDefinitionWithString[]? ReadEmptySealedStringDataDef()
{
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(EmptyNode);
return SerializationManager.Read<SealedDataDefinitionWithString[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public SealedDataDefinitionWithString[]? ReadOneSealedStringDataDef()
{
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(OneStringDefNode);
return SerializationManager.Read<SealedDataDefinitionWithString[]>(OneStringDefNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public SealedDataDefinitionWithString[]? ReadTenSealedStringDataDefs()
{
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(TenStringDefsNode);
return SerializationManager.Read<SealedDataDefinitionWithString[]>(TenStringDefsNode);
}
}
}

View File

@@ -28,7 +28,7 @@ namespace Robust.Benchmarks.Serialization.Write
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
Seed = SerializationManager.Read<SeedDataDefinition>(seedMapping);
}
private const string String = "ABC";

View File

@@ -0,0 +1,22 @@
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Robust.Client.Audio.Midi.Commands;
public sealed class MidiPanicCommand : IConsoleCommand
{
[Dependency] private readonly IMidiManager _midiManager = default!;
public string Command => "midipanic";
public string Description => Loc.GetString("midi-panic-command-description");
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
foreach (var renderer in _midiManager.Renderers)
{
renderer.StopAllNotes();
}
}
}

View File

@@ -0,0 +1,62 @@
using System.Collections.Generic;
using NFluidsynth;
using Robust.Shared.Audio.Midi;
namespace Robust.Client.Audio.Midi;
public interface IMidiManager
{
/// <summary>
/// A read-only list of all existing MIDI Renderers.
/// </summary>
IReadOnlyList<IMidiRenderer> Renderers { get; }
/// <summary>
/// If true, MIDI support is available.
/// </summary>
bool IsAvailable { get; }
/// <summary>
/// Volume, in db.
/// </summary>
float Volume { get; set; }
public int OcclusionCollisionMask { get; set; }
/// <summary>
/// This method tries to return a midi renderer ready to be used.
/// You only need to set the <see cref="IMidiRenderer.MidiProgram"/> afterwards.
/// </summary>
/// <remarks>
/// This method can fail if MIDI support is not available.
/// </remarks>
/// <returns>
/// <c>null</c> if MIDI support is not available.
/// </returns>
IMidiRenderer? GetNewRenderer(bool mono = true);
/// <summary>
/// Creates a <see cref="RobustMidiEvent"/> given a <see cref="MidiEvent"/> and a sequencer tick.
/// </summary>
RobustMidiEvent FromFluidEvent(MidiEvent midiEvent, uint tick);
/// <summary>
/// Creates a <see cref="SequencerEvent"/> given a <see cref="RobustMidiEvent"/>.
/// Be sure to dispose of the result after you've used it.
/// </summary>
SequencerEvent ToSequencerEvent(RobustMidiEvent midiEvent);
/// <summary>
/// Creates a <see cref="RobustMidiEvent"/> given a <see cref="SequencerEvent"/> and a sequencer tick.
/// </summary>
RobustMidiEvent FromSequencerEvent(SequencerEvent midiEvent, uint tick);
/// <summary>
/// Method called every frame.
/// Should be used to update positional audio.
/// </summary>
/// <param name="frameTime"></param>
void FrameUpdate(float frameTime);
void Shutdown();
}

View File

@@ -0,0 +1,174 @@
using System;
using Robust.Client.Graphics;
using Robust.Shared.Audio.Midi;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
namespace Robust.Client.Audio.Midi;
public enum MidiRendererStatus : byte
{
None,
Input,
File,
}
public interface IMidiRenderer : IDisposable
{
/// <summary>
/// The buffered audio source of this renderer.
/// </summary>
internal IClydeBufferedAudioSource Source { get; }
/// <summary>
/// Whether this renderer has been disposed or not.
/// </summary>
bool Disposed { get; }
/// <summary>
/// This controls whether the midi file being played will loop or not.
/// </summary>
bool LoopMidi { get; set; }
/// <summary>
/// This increases all note on velocities to 127.
/// </summary>
bool VolumeBoost { get; set; }
/// <summary>
/// The midi program (instrument) the renderer is using.
/// </summary>
byte MidiProgram { get; set; }
/// <summary>
/// The instrument bank the renderer is using.
/// </summary>
byte MidiBank { get; set; }
/// <summary>
/// The soundfont currently selected by the renderer.
/// </summary>
uint MidiSoundfont { get; set; }
/// <summary>
/// The current status of the renderer.
/// "None" if the renderer isn't playing from input or a midi file.
/// "Input" if the renderer is playing from midi input.
/// "File" if the renderer is playing from a midi file.
/// </summary>
MidiRendererStatus Status { get; }
/// <summary>
/// Whether the sound will play in stereo or mono.
/// </summary>
bool Mono { get; set; }
/// <summary>
/// Whether to drop messages on the percussion channel.
/// </summary>
bool DisablePercussionChannel { get; set; }
/// <summary>
/// Whether to drop messages for program change events.
/// </summary>
bool DisableProgramChangeEvent { get; set; }
/// <summary>
/// Gets the total number of ticks possible for the MIDI player.
/// </summary>
int PlayerTotalTick { get; }
/// <summary>
/// Gets or sets (seeks) the current tick of the MIDI player.
/// </summary>
int PlayerTick { get; set; }
/// <summary>
/// Gets the current tick of the sequencer.
/// </summary>
uint SequencerTick { get; }
/// <summary>
/// Gets the Time Scale of the sequencer in ticks per second. Default is 1000 for 1 tick per millisecond.
/// </summary>
double SequencerTimeScale { get; }
/// <summary>
/// Start listening for midi input.
/// </summary>
bool OpenInput();
/// <summary>
/// Start playing a midi file.
/// </summary>
/// <param name="buffer">Bytes of the midi file</param>
bool OpenMidi(ReadOnlySpan<byte> buffer);
/// <summary>
/// Stops listening for midi input.
/// </summary>
bool CloseInput();
/// <summary>
/// Stops playing midi files.
/// </summary>
bool CloseMidi();
/// <summary>
/// Stops all notes being played currently.
/// </summary>
void StopAllNotes();
/// <summary>
/// Render and play MIDI to the audio source.
/// </summary>
internal void Render();
/// <summary>
/// Loads a new soundfont into the renderer.
/// </summary>
void LoadSoundfont(string filename, bool resetPresets = false);
/// <summary>
/// Invoked whenever a new midi event is registered.
/// </summary>
event Action<RobustMidiEvent> OnMidiEvent;
/// <summary>
/// Invoked when the midi player finishes playing a song.
/// </summary>
event Action OnMidiPlayerFinished;
/// <summary>
/// The entity whose position will be used for positional audio.
/// This is only used if <see cref="Mono"/> is set to True.
/// </summary>
EntityUid? TrackingEntity { get; set; }
/// <summary>
/// The position that will be used for positional audio.
/// This is only used if <see cref="Mono"/> is set to True
/// and <see cref="TrackingEntity"/> is null.
/// </summary>
EntityCoordinates? TrackingCoordinates { get; set; }
/// <summary>
/// Send a midi event for the renderer to play.
/// </summary>
/// <param name="midiEvent">The midi event to be played</param>
void SendMidiEvent(RobustMidiEvent midiEvent);
/// <summary>
/// Schedule a MIDI event to be played at a later time.
/// </summary>
/// <param name="midiEvent">the midi event in question</param>
/// <param name="time"></param>
/// <param name="absolute"></param>
void ScheduleMidiEvent(RobustMidiEvent midiEvent, uint time, bool absolute);
/// <summary>
/// Actually disposes of this renderer. Do NOT use outside the MIDI thread.
/// </summary>
internal void InternalDispose();
}

View File

@@ -0,0 +1,155 @@
using NFluidsynth;
using Robust.Shared.Audio.Midi;
namespace Robust.Client.Audio.Midi;
internal sealed partial class MidiManager
{
public RobustMidiEvent FromFluidEvent(MidiEvent midiEvent, uint tick)
{
var status = RobustMidiEvent.MakeStatus((byte) midiEvent.Channel, (byte) midiEvent.Type);
// Control is always the first data byte. Value is always the second data byte. Fluidsynth's API ain't great.
var data1 = (byte) midiEvent.Control;
var data2 = (byte) midiEvent.Value;
// PitchBend is handled specially.
if (midiEvent.Type == (int) RobustMidiCommand.PitchBend)
{
// We pack pitch into both data values.
var pitch = (ushort) midiEvent.Pitch;
data1 = (byte) pitch;
data2 = (byte) (pitch >> 8);
}
return new RobustMidiEvent(status, data1, data2, tick);
}
public SequencerEvent ToSequencerEvent(RobustMidiEvent midiEvent)
{
var sequencerEvent = new SequencerEvent();
switch (midiEvent.MidiCommand)
{
case RobustMidiCommand.NoteOff:
sequencerEvent.NoteOff(midiEvent.Channel, midiEvent.Key);
break;
case RobustMidiCommand.NoteOn:
sequencerEvent.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
break;
case RobustMidiCommand.AfterTouch:
sequencerEvent.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);
break;
case RobustMidiCommand.ControlChange:
sequencerEvent.ControlChange(midiEvent.Channel, midiEvent.Control, midiEvent.Value);
break;
case RobustMidiCommand.ProgramChange:
sequencerEvent.ProgramChange(midiEvent.Channel, midiEvent.Program);
break;
case RobustMidiCommand.ChannelPressure:
sequencerEvent.ChannelPressure(midiEvent.Channel, midiEvent.Value);
break;
case RobustMidiCommand.PitchBend:
sequencerEvent.PitchBend(midiEvent.Channel, midiEvent.Pitch);
break;
case RobustMidiCommand.SystemMessage:
switch (midiEvent.Control)
{
case 0x0 when midiEvent.Status == 0xFF:
sequencerEvent.SystemReset();
break;
case 0x0B:
sequencerEvent.AllNotesOff(midiEvent.Channel);
break;
default:
_midiSawmill.Warning($"Tried to convert unsupported event to sequencer event:\n{midiEvent}");
break;
}
break;
default:
_midiSawmill.Warning($"Tried to convert unsupported event to sequencer event:\n{midiEvent}");
break;
}
return sequencerEvent;
}
public RobustMidiEvent FromSequencerEvent(SequencerEvent midiEvent, uint tick)
{
byte channel = (byte) midiEvent.Channel;
RobustMidiCommand command = 0x0;
byte data1 = 0;
byte data2 = 0;
switch (midiEvent.Type)
{
case FluidSequencerEventType.NoteOn:
command = RobustMidiCommand.NoteOn;
data1 = (byte) midiEvent.Key;
data2 = (byte) midiEvent.Velocity;
break;
case FluidSequencerEventType.NoteOff:
command = RobustMidiCommand.NoteOff;
data1 = (byte) midiEvent.Key;
break;
case FluidSequencerEventType.PitchBend:
command = RobustMidiCommand.PitchBend;
// We pack pitch into both data values
var pitch = (ushort) midiEvent.Pitch;
data1 = (byte) pitch;
data2 = (byte) (pitch >> 8);
break;
case FluidSequencerEventType.ProgramChange:
command = RobustMidiCommand.ProgramChange;
data1 = (byte) midiEvent.Program;
break;
case FluidSequencerEventType.KeyPressure:
command = RobustMidiCommand.AfterTouch;
data1 = (byte) midiEvent.Key;
data2 = (byte) midiEvent.Value;
break;
case FluidSequencerEventType.ControlChange:
command = RobustMidiCommand.ControlChange;
data1 = (byte) midiEvent.Control;
data2 = (byte) midiEvent.Value;
break;
case FluidSequencerEventType.ChannelPressure:
command = RobustMidiCommand.ChannelPressure;
data1 = (byte) midiEvent.Value;
break;
case FluidSequencerEventType.AllNotesOff:
command = RobustMidiCommand.SystemMessage;
data1 = 0x0B;
break;
case FluidSequencerEventType.SystemReset:
command = RobustMidiCommand.SystemMessage;
channel = 0x0F;
break;
default:
_midiSawmill.Error($"Unsupported Sequencer Event: {tick:D8}: {SequencerEventToString(midiEvent)}");
break;
}
return new RobustMidiEvent(RobustMidiEvent.MakeStatus(channel, (byte)command), data1, data2, tick);
}
}

View File

@@ -7,6 +7,7 @@ using NFluidsynth;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
@@ -15,404 +16,435 @@ using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using Logger = Robust.Shared.Log.Logger;
namespace Robust.Client.Audio.Midi
namespace Robust.Client.Audio.Midi;
internal sealed partial class MidiManager : IMidiManager
{
public interface IMidiManager
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly ILogManager _logger = default!;
private SharedPhysicsSystem _broadPhaseSystem = default!;
public IReadOnlyList<IMidiRenderer> Renderers
{
/// <summary>
/// This method tries to return a midi renderer ready to be used.
/// You only need to set the <see cref="IMidiRenderer.MidiProgram"/> afterwards.
/// </summary>
/// <remarks>
/// This method can fail if MIDI support is not available.
/// </remarks>
/// <returns>
/// <c>null</c> if MIDI support is not available.
/// </returns>
IMidiRenderer? GetNewRenderer();
/// <summary>
/// Method called every frame.
/// Should be used to update positional audio.
/// </summary>
/// <param name="frameTime"></param>
void FrameUpdate(float frameTime);
/// <summary>
/// Volume, in db.
/// </summary>
float Volume { get; set; }
/// <summary>
/// If true, MIDI support is available.
/// </summary>
bool IsAvailable { get; }
public int OcclusionCollisionMask { get; set; }
void Shutdown();
get
{
lock (_renderers)
{
// Perform a copy. Sadly, we can't return a reference to the original list due to threading concerns.
return _renderers.ToArray();
}
}
}
internal sealed class MidiManager : IMidiManager
[ViewVariables]
public bool IsAvailable
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
private SharedPhysicsSystem _broadPhaseSystem = default!;
[ViewVariables]
public bool IsAvailable
get
{
get
{
InitializeFluidsynth();
InitializeFluidsynth();
return FluidsynthInitialized;
}
return FluidsynthInitialized;
}
}
[ViewVariables]
private readonly List<IMidiRenderer> _renderers = new();
private bool _alive = true;
private Settings? _settings;
private Thread? _midiThread;
private ISawmill _midiSawmill = default!;
private float _volume = 0f;
private bool _volumeDirty = true;
// Not reliable until Fluidsynth is initialized!
[ViewVariables(VVAccess.ReadWrite)]
public float Volume
{
get => _volume;
set
{
if (MathHelper.CloseToPercent(_volume, value))
return;
_cfgMan.SetCVar(CVars.MidiVolume, value);
_volumeDirty = true;
}
}
private static readonly string[] LinuxSoundfonts =
{
"/usr/share/soundfonts/default.sf2",
"/usr/share/soundfonts/FluidR3_GM.sf2",
"/usr/share/soundfonts/freepats-general-midi.sf2",
"/usr/share/sounds/sf2/default.sf2",
"/usr/share/sounds/sf2/FluidR3_GM.sf2",
"/usr/share/sounds/sf2/TimGM6mb.sf2",
};
private const string WindowsSoundfont = @"C:\WINDOWS\system32\drivers\gm.dls";
private const string OsxSoundfont =
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
private const string FallbackSoundfont = "/Midi/fallback.sf2";
private static ResourcePath CustomSoundfontDirectory = new ResourcePath("/soundfonts/");
private readonly ResourceLoaderCallbacks _soundfontLoaderCallbacks = new();
private bool FluidsynthInitialized;
private bool _failedInitialize;
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
private ISawmill _sawmill = default!;
[ViewVariables(VVAccess.ReadWrite)]
public int OcclusionCollisionMask { get; set; }
private void InitializeFluidsynth()
{
if (FluidsynthInitialized || _failedInitialize) return;
_volume = _cfgMan.GetCVar(CVars.MidiVolume);
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
{
_volume = value;
_volumeDirty = true;
}, true);
_midiSawmill = _logger.GetSawmill("midi");
_midiSawmill.Level = LogLevel.Info;
_sawmill = _logger.GetSawmill("midi.fluidsynth");
_loggerDelegate = LoggerDelegate;
if (!_resourceManager.UserData.Exists(CustomSoundfontDirectory))
{
_resourceManager.UserData.CreateDir(CustomSoundfontDirectory);
}
// not a directory, preserve the old file and create an actual directory
else if (!_resourceManager.UserData.IsDir(CustomSoundfontDirectory))
{
_resourceManager.UserData.Rename(CustomSoundfontDirectory, CustomSoundfontDirectory.WithName(CustomSoundfontDirectory.Filename + ".old"));
_resourceManager.UserData.CreateDir(CustomSoundfontDirectory);
}
[ViewVariables]
private readonly List<IMidiRenderer> _renderers = new();
private bool _alive = true;
private Settings? _settings;
private Thread? _midiThread;
private ISawmill _midiSawmill = default!;
private float _volume = 0f;
private bool _volumeDirty = true;
// Not reliable until Fluidsynth is initialized!
[ViewVariables(VVAccess.ReadWrite)]
public float Volume
try
{
get => _volume;
set
{
if (MathHelper.CloseToPercent(_volume, value))
return;
NFluidsynth.Logger.SetLoggerMethod(_loggerDelegate); // Will cause a safe DllNotFoundException if not available.
_cfgMan.SetCVar(CVars.MidiVolume, value);
_volumeDirty = true;
}
_settings = new Settings();
_settings["synth.sample-rate"].DoubleValue = 44100;
_settings["player.timing-source"].StringValue = "sample";
_settings["synth.lock-memory"].IntValue = 0;
_settings["synth.threadsafe-api"].IntValue = 1;
_settings["synth.gain"].DoubleValue = 1.0d;
_settings["synth.polyphony"].IntValue = 1024;
_settings["synth.cpu-cores"].IntValue = 2;
_settings["synth.midi-channels"].IntValue = 16;
_settings["synth.overflow.age"].DoubleValue = 3000;
_settings["audio.driver"].StringValue = "file";
_settings["audio.periods"].IntValue = 8;
_settings["audio.period-size"].IntValue = 4096;
_settings["midi.autoconnect"].IntValue = 1;
_settings["player.reset-synth"].IntValue = 0;
_settings["synth.midi-bank-select"].StringValue = "gm";
//_settings["synth.verbose"].IntValue = 1; // Useful for debugging.
}
catch (Exception e)
{
_midiSawmill.Warning(
"Failed to initialize fluidsynth due to exception, disabling MIDI support:\n{0}", e);
_failedInitialize = true;
return;
}
private static readonly string[] LinuxSoundfonts =
{
"/usr/share/soundfonts/default.sf2",
"/usr/share/soundfonts/FluidR3_GM.sf2",
"/usr/share/soundfonts/freepats-general-midi.sf2",
"/usr/share/sounds/sf2/default.sf2",
"/usr/share/sounds/sf2/FluidR3_GM.sf2",
"/usr/share/sounds/sf2/TimGM6mb.sf2",
_midiThread = new Thread(ThreadUpdate);
_midiThread.Start();
_broadPhaseSystem = EntitySystem.Get<SharedPhysicsSystem>();
FluidsynthInitialized = true;
}
private void LoggerDelegate(NFluidsynth.Logger.LogLevel level, string message, IntPtr data)
{
var rLevel = level switch {
NFluidsynth.Logger.LogLevel.Panic => LogLevel.Error,
NFluidsynth.Logger.LogLevel.Error => LogLevel.Error,
NFluidsynth.Logger.LogLevel.Warning => LogLevel.Warning,
NFluidsynth.Logger.LogLevel.Information => LogLevel.Info,
NFluidsynth.Logger.LogLevel.Debug => LogLevel.Debug,
_ => LogLevel.Debug
};
_sawmill.Log(rLevel, message);
}
private const string WindowsSoundfont = @"C:\WINDOWS\system32\drivers\gm.dls";
private const string OsxSoundfont =
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
private const string FallbackSoundfont = "/Midi/fallback.sf2";
private readonly ResourceLoaderCallbacks _soundfontLoaderCallbacks = new();
private bool FluidsynthInitialized;
private bool _failedInitialize;
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
private ISawmill _sawmill = default!;
[ViewVariables(VVAccess.ReadWrite)]
public int OcclusionCollisionMask { get; set; }
private void InitializeFluidsynth()
public IMidiRenderer? GetNewRenderer(bool mono = true)
{
if (!FluidsynthInitialized)
{
if (FluidsynthInitialized || _failedInitialize) return;
InitializeFluidsynth();
_volume = _cfgMan.GetCVar(CVars.MidiVolume);
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
if (!FluidsynthInitialized) // init failed
{
_volume = value;
_volumeDirty = true;
}, true);
_midiSawmill = Logger.GetSawmill("midi");
_sawmill = Logger.GetSawmill("midi.fluidsynth");
_loggerDelegate = LoggerDelegate;
try
{
NFluidsynth.Logger.SetLoggerMethod(_loggerDelegate); // Will cause a safe DllNotFoundException if not available.
_settings = new Settings();
_settings["synth.sample-rate"].DoubleValue = 44100;
_settings["player.timing-source"].StringValue = "sample";
_settings["synth.lock-memory"].IntValue = 0;
_settings["synth.threadsafe-api"].IntValue = 1;
_settings["synth.gain"].DoubleValue = 1.0d;
_settings["synth.polyphony"].IntValue = 1024;
_settings["synth.cpu-cores"].IntValue = 2;
_settings["synth.overflow.age"].DoubleValue = 3000;
_settings["audio.driver"].StringValue = "file";
_settings["audio.periods"].IntValue = 8;
_settings["audio.period-size"].IntValue = 4096;
_settings["midi.autoconnect"].IntValue = 1;
_settings["player.reset-synth"].IntValue = 0;
_settings["synth.midi-bank-select"].StringValue = "gm";
}
catch (Exception e)
{
Logger.WarningS("midi",
"Failed to initialize fluidsynth due to exception, disabling MIDI support:\n{0}", e);
_failedInitialize = true;
return;
}
_midiThread = new Thread(ThreadUpdate);
_midiThread.Start();
_broadPhaseSystem = EntitySystem.Get<SharedPhysicsSystem>();
FluidsynthInitialized = true;
}
private void LoggerDelegate(NFluidsynth.Logger.LogLevel level, string message, IntPtr data)
{
var rLevel = level switch {
NFluidsynth.Logger.LogLevel.Panic => LogLevel.Error,
NFluidsynth.Logger.LogLevel.Error => LogLevel.Error,
NFluidsynth.Logger.LogLevel.Warning => LogLevel.Warning,
NFluidsynth.Logger.LogLevel.Information => LogLevel.Info,
NFluidsynth.Logger.LogLevel.Debug => LogLevel.Debug,
_ => LogLevel.Debug
};
_sawmill.Log(rLevel, message);
}
public IMidiRenderer? GetNewRenderer()
{
if (!FluidsynthInitialized)
{
InitializeFluidsynth();
if (!FluidsynthInitialized) // init failed
{
return null;
}
}
var soundfontLoader = SoundFontLoader.NewDefaultSoundFontLoader(_settings);
// Just making double sure these don't get GC'd.
// They shouldn't, MidiRenderer keeps a ref, but making sure...
var handle = GCHandle.Alloc(soundfontLoader);
try
{
soundfontLoader.SetCallbacks(_soundfontLoaderCallbacks);
var renderer = new MidiRenderer(_settings!, soundfontLoader);
foreach (var file in _resourceManager.ContentFindFiles(("/Audio/MidiCustom/")))
{
if (file.Extension != "sf2" && file.Extension != "dls") continue;
renderer.LoadSoundfont(file.ToString());
}
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
renderer.LoadSoundfont(FallbackSoundfont);
if (OperatingSystem.IsLinux())
{
foreach (var filepath in LinuxSoundfonts)
{
if (!File.Exists(filepath) || !SoundFont.IsSoundFont(filepath)) continue;
try
{
renderer.LoadSoundfont(filepath, true);
}
catch (Exception)
{
continue;
}
break;
}
}
else if (OperatingSystem.IsMacOS())
{
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
renderer.LoadSoundfont(OsxSoundfont, true);
}
else if (OperatingSystem.IsWindows())
{
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
renderer.LoadSoundfont(WindowsSoundfont, true);
}
renderer.Source.SetVolume(Volume);
lock (_renderers)
{
_renderers.Add(renderer);
}
return renderer;
}
finally
{
handle.Free();
return null;
}
}
public void FrameUpdate(float frameTime)
var soundfontLoader = SoundFontLoader.NewDefaultSoundFontLoader(_settings);
// Just making double sure these don't get GC'd.
// They shouldn't, MidiRenderer keeps a ref, but making sure...
var handle = GCHandle.Alloc(soundfontLoader);
try
{
if (!FluidsynthInitialized)
soundfontLoader.SetCallbacks(_soundfontLoaderCallbacks);
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _clydeAudio, _taskManager, _midiSawmill);
foreach (var file in _resourceManager.ContentFindFiles(("/Audio/MidiCustom/")))
{
return;
if (file.Extension != "sf2" && file.Extension != "dls") continue;
renderer.LoadSoundfont(file.ToString());
}
// Update positions of streams every frame.
foreach (var renderer in _renderers)
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
renderer.LoadSoundfont(FallbackSoundfont);
if (OperatingSystem.IsLinux())
{
if (renderer.Disposed)
continue;
if(_volumeDirty)
renderer.Source.SetVolume(Volume);
if (!renderer.Mono)
foreach (var filepath in LinuxSoundfonts)
{
renderer.Source.SetGlobal();
continue;
}
if (!File.Exists(filepath) || !SoundFont.IsSoundFont(filepath)) continue;
MapCoordinates? mapPos = null;
var trackingEntity = renderer.TrackingEntity != null && !_entityManager.Deleted(renderer.TrackingEntity);
if (trackingEntity)
{
renderer.TrackingCoordinates = _entityManager.GetComponent<TransformComponent>(renderer.TrackingEntity!.Value).Coordinates;
}
if (renderer.TrackingCoordinates != null)
{
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
}
if (mapPos != null && mapPos.Value.MapId == _eyeManager.CurrentMap)
{
var pos = mapPos.Value;
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
var occlusion = 0f;
if (sourceRelative.Length > 0)
try
{
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
pos.Position,
sourceRelative.Normalized,
OcclusionCollisionMask),
sourceRelative.Length,
renderer.TrackingEntity);
renderer.LoadSoundfont(filepath, true);
}
catch (Exception)
{
continue;
}
renderer.Source.SetOcclusion(occlusion);
if (!renderer.Source.SetPosition(pos.Position))
{
return;
}
if (trackingEntity)
{
renderer.Source.SetVelocity(renderer.TrackingEntity!.Value.GlobalLinearVelocity());
}
}
else
{
renderer.Source.SetOcclusion(float.MaxValue);
break;
}
}
_volumeDirty = false;
}
/// <summary>
/// Main method for the thread rendering the midi audio.
/// </summary>
private void ThreadUpdate()
{
while (_alive)
else if (OperatingSystem.IsMacOS())
{
lock (_renderers)
{
for (var i = 0; i < _renderers.Count; i++)
{
var renderer = _renderers[i];
if (!renderer.Disposed)
renderer.Render();
else
{
renderer.InternalDispose();
_renderers.Remove(renderer);
}
}
}
Thread.Sleep(1);
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
renderer.LoadSoundfont(OsxSoundfont, true);
}
else if (OperatingSystem.IsWindows())
{
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
renderer.LoadSoundfont(WindowsSoundfont, true);
}
}
public void Shutdown()
{
_alive = false;
_midiThread?.Join();
_settings?.Dispose();
// load every soundfont from the user data directory
_midiSawmill.Debug($"loading soundfonts from {CustomSoundfontDirectory.ToRelativePath().ToString()}/*");
var enumerator = _resourceManager.UserData.Find($"{CustomSoundfontDirectory.ToRelativePath().ToString()}/*").Item1;
foreach (var soundfont in enumerator)
{
if (soundfont.Extension != "sf2" && soundfont.Extension != "dls") continue;
_midiSawmill.Debug($"loading soundfont {soundfont}");
renderer.LoadSoundfont(soundfont.ToString());
}
renderer.Source.SetVolume(Volume);
lock (_renderers)
{
foreach (var renderer in _renderers)
{
renderer?.Dispose();
}
_renderers.Add(renderer);
}
return renderer;
}
finally
{
handle.Free();
}
}
public void FrameUpdate(float frameTime)
{
if (!FluidsynthInitialized)
{
return;
}
// Update positions of streams every frame.
foreach (var renderer in _renderers)
{
if (renderer.Disposed)
continue;
if(_volumeDirty)
renderer.Source.SetVolume(Volume);
if (!renderer.Mono)
{
renderer.Source.SetGlobal();
continue;
}
if (FluidsynthInitialized && !_failedInitialize)
MapCoordinates? mapPos = null;
var trackingEntity = renderer.TrackingEntity != null && !_entityManager.Deleted(renderer.TrackingEntity);
if (trackingEntity)
{
NFluidsynth.Logger.SetLoggerMethod(null);
renderer.TrackingCoordinates = _entityManager.GetComponent<TransformComponent>(renderer.TrackingEntity!.Value).Coordinates;
}
if (renderer.TrackingCoordinates != null)
{
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
}
if (mapPos != null && mapPos.Value.MapId == _eyeManager.CurrentMap)
{
var pos = mapPos.Value;
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
var occlusion = 0f;
if (sourceRelative.Length > 0)
{
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
pos.Position,
sourceRelative.Normalized,
OcclusionCollisionMask),
sourceRelative.Length,
renderer.TrackingEntity);
}
renderer.Source.SetOcclusion(occlusion);
if (!renderer.Source.SetPosition(pos.Position))
{
return;
}
if (trackingEntity)
{
renderer.Source.SetVelocity(renderer.TrackingEntity!.Value.GlobalLinearVelocity());
}
}
else
{
renderer.Source.SetOcclusion(float.MaxValue);
}
}
/// <summary>
/// This class is used to load soundfonts.
/// </summary>
private sealed class ResourceLoaderCallbacks : SoundFontLoaderCallbacks
_volumeDirty = false;
}
/// <summary>
/// Main method for the thread rendering the midi audio.
/// </summary>
private void ThreadUpdate()
{
while (_alive)
{
private readonly Dictionary<int, Stream> _openStreams = new();
private int _nextStreamId = 1;
public override IntPtr Open(string filename)
lock (_renderers)
{
if (string.IsNullOrEmpty(filename))
for (var i = 0; i < _renderers.Count; i++)
{
return IntPtr.Zero;
var renderer = _renderers[i];
if (!renderer.Disposed)
renderer.Render();
else
{
renderer.InternalDispose();
_renderers.Remove(renderer);
}
}
}
Stream? stream;
var resourceCache = IoCManager.Resolve<IResourceCache>();
var resourcePath = new ResourcePath(filename);
Thread.Sleep(1);
}
}
if (resourcePath.IsRooted && resourceCache.ContentFileExists(filename))
public void Shutdown()
{
_alive = false;
_midiThread?.Join();
_settings?.Dispose();
lock (_renderers)
{
foreach (var renderer in _renderers)
{
renderer?.Dispose();
}
}
if (FluidsynthInitialized && !_failedInitialize)
{
NFluidsynth.Logger.SetLoggerMethod(null);
}
}
/// <summary>
/// Internal method to get a human-readable representation of a <see cref="SequencerEvent"/>.
/// </summary>
internal static string SequencerEventToString(SequencerEvent midiEvent)
{
// ReSharper disable once UseStringInterpolation
return string.Format(
"{0} chan:{1:D2} key:{2:D5} bank:{3:D2} ctrl:{4:D5} dur:{5:D5} pitch:{6:D5} prog:{7:D3} val:{8:D5} vel:{9:D5}",
midiEvent.Type.ToString().PadLeft(22),
midiEvent.Channel,
midiEvent.Key,
midiEvent.Bank,
midiEvent.Control,
midiEvent.Duration,
midiEvent.Pitch,
midiEvent.Program,
midiEvent.Value,
midiEvent.Velocity);
}
/// <summary>
/// This class is used to load soundfonts.
/// </summary>
private sealed class ResourceLoaderCallbacks : SoundFontLoaderCallbacks
{
private readonly Dictionary<int, Stream> _openStreams = new();
private int _nextStreamId = 1;
public override IntPtr Open(string filename)
{
if (string.IsNullOrEmpty(filename))
{
return IntPtr.Zero;
}
Stream? stream;
var resourceCache = IoCManager.Resolve<IResourceCache>();
var resourcePath = new ResourcePath(filename);
if (resourcePath.IsRooted)
{
// is it in content?
if (resourceCache.ContentFileExists(filename))
{
if (!resourceCache.TryContentFileRead(filename, out stream))
return IntPtr.Zero;
}
// is it in userdata?
else if (resourceCache.UserData.Exists(resourcePath))
{
stream = resourceCache.UserData.OpenRead(resourcePath);
}
else if (File.Exists(filename))
{
stream = File.OpenRead(filename);
@@ -421,73 +453,81 @@ namespace Robust.Client.Audio.Midi
{
return IntPtr.Zero;
}
var id = _nextStreamId++;
_openStreams.Add(id, stream);
return (IntPtr) id;
}
else if (File.Exists(filename))
{
stream = File.OpenRead(filename);
}
else
{
return IntPtr.Zero;
}
public override unsafe int Read(IntPtr buf, long count, IntPtr sfHandle)
{
var length = (int) count;
var span = new Span<byte>(buf.ToPointer(), length);
var stream = _openStreams[(int) sfHandle];
var id = _nextStreamId++;
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
try
_openStreams.Add(id, stream);
return (IntPtr) id;
}
public override unsafe int Read(IntPtr buf, long count, IntPtr sfHandle)
{
var length = (int) count;
var span = new Span<byte>(buf.ToPointer(), length);
var stream = _openStreams[(int) sfHandle];
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
try
{
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
if (count < 1024)
{
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
if (count < 1024)
{
// ReSharper disable once SuggestVarOrType_Elsewhere
Span<byte> buffer = stackalloc byte[(int)count];
// ReSharper disable once SuggestVarOrType_Elsewhere
Span<byte> buffer = stackalloc byte[(int)count];
stream.ReadExact(buffer);
stream.ReadExact(buffer);
buffer.CopyTo(span);
}
else
{
var buffer = stream.ReadExact(length);
buffer.CopyTo(span);
}
buffer.CopyTo(span);
}
catch (EndOfStreamException)
else
{
return -1;
var buffer = stream.ReadExact(length);
buffer.CopyTo(span);
}
return 0;
}
public override int Seek(IntPtr sfHandle, int offset, SeekOrigin origin)
catch (EndOfStreamException)
{
var stream = _openStreams[(int) sfHandle];
stream.Seek(offset, origin);
return 0;
return -1;
}
public override int Tell(IntPtr sfHandle)
{
var stream = _openStreams[(int) sfHandle];
return 0;
}
return (int) stream.Position;
}
public override int Seek(IntPtr sfHandle, int offset, SeekOrigin origin)
{
var stream = _openStreams[(int) sfHandle];
public override int Close(IntPtr sfHandle)
{
if (!_openStreams.Remove((int) sfHandle, out var stream))
return -1;
stream.Seek(offset, origin);
stream.Dispose();
return 0;
return 0;
}
public override int Tell(IntPtr sfHandle)
{
var stream = _openStreams[(int) sfHandle];
return (int) stream.Position;
}
public override int Close(IntPtr sfHandle)
{
if (!_openStreams.Remove((int) sfHandle, out var stream))
return -1;
stream.Dispose();
return 0;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -74,7 +74,7 @@ namespace Robust.Client
{
if (RunLevel == ClientRunLevel.Connecting)
{
_net.Shutdown("Client mashing that connect button.");
_net.Reset("Client mashing that connect button.");
Reset();
}

View File

@@ -134,7 +134,7 @@ namespace Robust.Client.Console
if (!NetManager.IsConnected) // we don't care about session on client
return;
var msg = NetManager.CreateNetMessage<MsgConCmd>();
var msg = new MsgConCmd();
msg.Text = command;
NetManager.ClientSendMessage(msg);
}
@@ -198,7 +198,7 @@ namespace Robust.Client.Console
if (!NetManager.IsConnected)
return;
var msg = NetManager.CreateNetMessage<MsgConCmdReg>();
var msg = new MsgConCmdReg();
NetManager.ClientSendMessage(msg);
_requestedCommands = true;

View File

@@ -8,12 +8,12 @@ namespace Robust.Client.Console.Commands
{
public string Command => "physics";
public string Description => $"Shows a debug physics overlay. The arg supplied specifies the overlay.";
public string Help => $"{Command} <aabbs / contactnormals / contactpoints / joints / shapeinfo / shapes>";
public string Help => $"{Command} <aabbs / com / contactnormals / contactpoints / joints / shapeinfo / shapes>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteLine($"Invalid number of args supplied");
shell.WriteError($"Invalid number of args supplied");
return;
}
@@ -43,7 +43,7 @@ namespace Robust.Client.Console.Commands
system.Flags ^= PhysicsDebugFlags.Shapes;
break;
default:
shell.WriteLine($"{args[0]} is not a recognised overlay");
shell.WriteError($"{args[0]} is not a recognised overlay");
return;
}

View File

@@ -15,7 +15,7 @@ namespace Robust.Client.Console.Commands
// MsgStringTableEntries is registered as NetMessageAccept.Client so the server will immediately deny it.
// And kick us.
var net = IoCManager.Resolve<IClientNetManager>();
var msg = net.CreateNetMessage<MsgStringTableEntries>();
var msg = new MsgStringTableEntries();
msg.Entries = new MsgStringTableEntries.Entry[0];
net.ClientSendMessage(msg);
}

View File

@@ -35,7 +35,7 @@ namespace Robust.Client.Console
RunButton.Disabled = true;
var msg = _client._netManager.CreateNetMessage<MsgScriptEval>();
var msg = new MsgScriptEval();
msg.ScriptSession = _session;
msg.Code = _lastEnteredText = InputBar.Text;
@@ -48,7 +48,7 @@ namespace Robust.Client.Console
protected override void Complete()
{
var msg = _client._netManager.CreateNetMessage<MsgScriptCompletion>();
var msg = new MsgScriptCompletion();
msg.ScriptSession = _session;
msg.Code = InputBar.Text;
msg.Cursor = InputBar.CursorPosition;

View File

@@ -65,7 +65,7 @@ namespace Robust.Client.Console
throw new InvalidOperationException("We do not have scripting permission.");
}
var msg = _netManager.CreateNetMessage<MsgScriptStart>();
var msg = new MsgScriptStart();
msg.ScriptSession = _nextSessionId++;
_netManager.ClientSendMessage(msg);
}
@@ -74,7 +74,7 @@ namespace Robust.Client.Console
{
_activeConsoles.Remove(session);
var msg = _netManager.CreateNetMessage<MsgScriptStop>();
var msg = new MsgScriptStop();
msg.ScriptSession = session;
_netManager.ClientSendMessage(msg);
}

View File

@@ -52,6 +52,7 @@ using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
@@ -88,6 +89,7 @@ namespace Robust.Client.Debugging
EntityManager,
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IInputManager>(),
IoCManager.Resolve<IMapManager>(),
IoCManager.Resolve<IResourceCache>(),
this,
Get<SharedPhysicsSystem>()));
@@ -151,10 +153,12 @@ namespace Robust.Client.Debugging
/// Shows the world point for each contact in the viewport.
/// </summary>
ContactPoints = 1 << 0,
/// <summary>
/// Shows the world normal for each contact in the viewport.
/// </summary>
ContactNormals = 1 << 1,
/// <summary>
/// Shows all physics shapes in the viewport.
/// </summary>
@@ -162,6 +166,10 @@ namespace Robust.Client.Debugging
ShapeInfo = 1 << 3,
Joints = 1 << 4,
AABBs = 1 << 5,
/// <summary>
/// Shows Center of Mass for all bodies in the viewport.
/// </summary>
COM = 1 << 6,
}
@@ -170,6 +178,7 @@ namespace Robust.Client.Debugging
private IEntityManager _entityManager = default!;
private IEyeManager _eyeManager = default!;
private IInputManager _inputManager = default!;
private IMapManager _mapManager = default!;
private DebugPhysicsSystem _debugPhysicsSystem = default!;
private SharedPhysicsSystem _physicsSystem = default!;
@@ -181,11 +190,12 @@ namespace Robust.Client.Debugging
private HashSet<Joint> _drawnJoints = new();
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IResourceCache cache, DebugPhysicsSystem system, SharedPhysicsSystem physicsSystem)
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IMapManager mapManager, IResourceCache cache, DebugPhysicsSystem system, SharedPhysicsSystem physicsSystem)
{
_entityManager = entityManager;
_eyeManager = eyeManager;
_inputManager = inputManager;
_mapManager = mapManager;
_debugPhysicsSystem = system;
_physicsSystem = physicsSystem;
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
@@ -240,26 +250,21 @@ namespace Robust.Client.Debugging
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.COM) != 0)
{
const float Alpha = 0.25f;
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
{
Color color;
const float Alpha = 0.25f;
float size;
if (_entityManager.HasComponent<MapGridComponent>(physBody.Owner))
{
color = Color.Orange.WithAlpha(Alpha);
size = 1f;
}
else
{
color = Color.Purple.WithAlpha(Alpha);
size = 0.2f;
}
var color = Color.Purple.WithAlpha(Alpha);
var transform = physBody.GetTransform();
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), 0.2f, color);
}
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), size, color);
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, viewBounds))
{
var physBody = _entityManager.GetComponent<PhysicsComponent>(grid.GridEntityId);
var color = Color.Orange.WithAlpha(Alpha);
var transform = physBody.GetTransform();
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), 1f, color);
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Threading;
using Robust.LoaderApi;
using Robust.Shared;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client;
internal partial class GameController
{
// A little paranoia goes a long way.
// This is static on purpose - the whole process shouldn't redial more than once, ever.
private static int _hasRedialled = 0;
public void Redial(string address, string? text = null)
{
// -- ATTENTION, YE WOULD-BE TRESPASSERS! --
// This code is the least testable code ever (because it's in-engine code that requires the Launcher), so don't make it do too much.
// This checks for some obvious requirements and then forwards to RedialApi which does the actual work.
// Testing of RedialApi is doable in the SS14.Launcher project, this is why it has a fake RobustToolbox build.
// -- THANK YE, NOW KINDLY MOVE ALONG! --
// We don't ever want to successfully redial more than once, and we want to shutdown once we've redialled.
// Otherwise abuse could happen.
DebugTools.AssertNotNull(_mainLoop);
Logger.Info($"Attempting redial of {address}: {text ?? "no reason given"}");
if (!_mainLoop!.Running)
{
throw new Exception("Attempted a redial during shutdown, this is not acceptable - redial first and if you succeed it'll shutdown anyway.");
}
if (_loaderArgs == null)
{
throw new Exception("Attempted a redial when the game was not run from the launcher (_loaderArgs == null)");
}
if (_loaderArgs!.RedialApi == null)
{
throw new Exception("Attempted a redial when redialling was not supported by the loader (Outdated launcher?)");
}
if (Interlocked.Increment(ref _hasRedialled) != 1)
{
// Don't let it overflow or anything
Interlocked.Decrement(ref _hasRedialled);
throw new Exception("Attempted a redial after one already succeeded or while one is in progress, this is never acceptable");
}
try
{
_loaderArgs!.RedialApi!.Redial(new Uri(address), text ?? "");
}
catch (Exception ex)
{
Interlocked.Decrement(ref _hasRedialled);
throw ex;
}
Shutdown("Redial");
}
}

View File

@@ -21,7 +21,6 @@ using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
@@ -29,6 +28,7 @@ using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Threading;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
@@ -66,6 +66,7 @@ namespace Robust.Client
[Dependency] private readonly IAuthManager _authManager = default!;
[Dependency] private readonly IMidiManager _midiManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IParallelManagerInternal _parallelMgr = default!;
private IWebViewManagerHook? _webViewHook;
@@ -131,8 +132,9 @@ namespace Robust.Client
_inputManager.Initialize();
_console.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(new ResourcePath("/EnginePrototypes/"));
_prototypeManager.LoadDirectory(Options.PrototypeDirectory);
_prototypeManager.Resync();
_prototypeManager.ResolveResults();
_entityManager.Initialize();
_mapManager.Initialize();
_gameStateManager.Initialize();
@@ -197,7 +199,7 @@ namespace Robust.Client
_clyde.Ready();
if (!Options.DisableCommandLineConnect &&
if (_resourceManifest!.AutoConnect &&
(_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
&& LaunchState.ConnectEndpoint != null)
{
@@ -213,7 +215,7 @@ namespace Robust.Client
{
// Parses /manifest.yml for game-specific settings that cannot be exclusively set up by content code.
if (!_resourceCache.TryContentFileRead("/manifest.yml", out var stream))
return new ResourceManifestData(Array.Empty<string>(), null, null, null, null);
return new ResourceManifestData(Array.Empty<string>(), null, null, null, null, true);
var yamlStream = new YamlStream();
using (stream)
@@ -223,7 +225,7 @@ namespace Robust.Client
}
if (yamlStream.Documents.Count == 0)
return new ResourceManifestData(Array.Empty<string>(), null, null, null, null);
return new ResourceManifestData(Array.Empty<string>(), null, null, null, null, true);
if (yamlStream.Documents.Count != 1 || yamlStream.Documents[0].RootNode is not YamlMappingNode mapping)
{
@@ -258,7 +260,11 @@ namespace Robust.Client
if (mapping.TryGetNode("splashLogo", out var splashNode))
splashLogo = splashNode.AsString();
return new ResourceManifestData(modules, assemblyPrefix, defaultWindowTitle, windowIconSet, splashLogo);
bool autoConnect = true;
if (mapping.TryGetNode("autoConnect", out var autoConnectNode))
autoConnect = autoConnectNode.AsBool();
return new ResourceManifestData(modules, assemblyPrefix, defaultWindowTitle, windowIconSet, splashLogo, autoConnect);
}
internal bool StartupSystemSplash(GameControllerOptions options, Func<ILogHandler>? logHandlerFactory)
@@ -326,6 +332,8 @@ namespace Robust.Client
ProfileOptSetup.Setup(_configurationManager);
_parallelMgr.Initialize();
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
var mountOptions = _commandLineArgs != null
@@ -584,7 +592,8 @@ namespace Robust.Client
string? AssemblyPrefix,
string? DefaultWindowTitle,
string? WindowIconSet,
string? SplashLogo
string? SplashLogo,
bool AutoConnect
);
}
}

View File

@@ -82,10 +82,5 @@ namespace Robust.Client
/// Whether to load config and user data.
/// </summary>
public bool LoadConfigAndUserData { get; init; } = true;
/// <summary>
/// Whether to disable command line args server auto-connecting.
/// </summary>
public bool DisableCommandLineConnect { get; init; } = false;
}
}

View File

@@ -37,9 +37,9 @@ namespace Robust.Client.GameObjects
return base.CreateEntity(prototypeName, uid);
}
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity)
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity, MetaDataComponent? meta = null)
{
base.InitializeEntity(entity);
base.InitializeEntity(entity, meta);
}
void IClientEntityManagerInternal.StartEntity(EntityUid entity)
@@ -86,7 +86,7 @@ namespace Robust.Client.GameObjects
public void SendSystemNetworkMessage(EntityEventArgs message, uint sequence)
{
var msg = _networkManager.CreateNetMessage<MsgEntity>();
var msg = new MsgEntity();
msg.Type = EntityMessageType.SystemMessage;
msg.SystemMessage = message;
msg.SourceTick = _gameTiming.CurTick;

View File

@@ -1,3 +1,4 @@
using System;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
@@ -32,11 +33,13 @@ namespace Robust.Client.GameObjects
public const string LogCategory = "go.comp.icon";
const string SerializationCache = "icon";
[Obsolete("Use SpriteSystem instead.")]
private static IRsiStateLike TextureForConfig(IconComponent compData, IResourceCache resourceCache)
{
return compData.Icon?.Default ?? resourceCache.GetFallback<TextureResource>().Texture;
}
[Obsolete("Use SpriteSystem instead.")]
public static IRsiStateLike? GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
{
if (!prototype.Components.TryGetValue("Icon", out var compData))

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(SharedPointLightComponent))]
public sealed class PointLightComponent : SharedPointLightComponent, ISerializationHooks
public sealed class PointLightComponent : SharedPointLightComponent
{
[Dependency] private readonly IEntityManager _entityManager = default!;

View File

@@ -601,8 +601,10 @@ namespace Robust.Client.GameObjects
private void RebuildBounds()
{
_bounds = new Box2();
foreach (var layer in Layers.Where(layer => layer.Visible))
foreach (var layer in Layers)
{
if (!layer.Visible) continue;
_bounds = _bounds.Union(layer.CalculateBoundingBox());
}
}
@@ -1520,6 +1522,7 @@ namespace Robust.Client.GameObjects
}
}
[Obsolete("Use SpriteSystem instead.")]
internal static RSI.State GetFallbackState(IResourceCache cache)
{
var rsi = cache.GetResource<RSIResource>("/Textures/error.rsi").RSI;
@@ -1968,7 +1971,7 @@ namespace Robust.Client.GameObjects
/// <inheritdoc/>
public Box2 CalculateBoundingBox()
{
var textureSize = PixelSize / EyeManager.PixelsPerMeter;
var textureSize = (Vector2) PixelSize / EyeManager.PixelsPerMeter;
// If the parent has locked rotation and we don't have any rotation,
// we can take the quick path of just making a box the size of the texture.

View File

@@ -75,6 +75,10 @@ namespace Robust.Client.GameObjects
(BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[] {this, wrapped.UiKey});
boundInterface.Open();
_openInterfaces[wrapped.UiKey] = boundInterface;
var playerSession = _playerManager.LocalPlayer?.Session;
if(playerSession != null)
_entityManager.EventBus.RaiseLocalEvent(Owner, new BoundUIOpenedEvent(wrapped.UiKey, Owner, playerSession));
}
internal void Close(object uiKey, bool remoteCall)

View File

@@ -1,12 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Robust.Client.Physics;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
@@ -33,9 +31,10 @@ namespace Robust.Client.GameObjects
UpdatesAfter.Add(typeof(TransformSystem));
UpdatesAfter.Add(typeof(PhysicsSystem));
SubscribeLocalEvent<OccluderDirtyEvent>(HandleDirtyEvent);
SubscribeLocalEvent<OccluderDirtyEvent>(OnOccluderDirty);
SubscribeLocalEvent<ClientOccluderComponent, AnchorStateChangedEvent>(HandleAnchorChanged);
SubscribeLocalEvent<ClientOccluderComponent, AnchorStateChangedEvent>(OnAnchorChanged);
SubscribeLocalEvent<ClientOccluderComponent, ReAnchorEvent>(OnReAnchor);
}
public override void FrameUpdate(float frameTime)
@@ -62,51 +61,58 @@ namespace Robust.Client.GameObjects
}
}
private static void HandleAnchorChanged(EntityUid uid, ClientOccluderComponent component, ref AnchorStateChangedEvent args)
private static void OnAnchorChanged(EntityUid uid, ClientOccluderComponent component, ref AnchorStateChangedEvent args)
{
component.AnchorStateChanged();
}
private void HandleDirtyEvent(OccluderDirtyEvent ev)
private void OnReAnchor(EntityUid uid, ClientOccluderComponent component, ref ReAnchorEvent args)
{
component.AnchorStateChanged();
}
private void OnOccluderDirty(OccluderDirtyEvent ev)
{
var sender = ev.Sender;
IMapGrid? grid;
var occluderQuery = GetEntityQuery<ClientOccluderComponent>();
if (EntityManager.EntityExists(sender) &&
EntityManager.TryGetComponent(sender, out ClientOccluderComponent? iconSmooth)
&& iconSmooth.Initialized)
occluderQuery.HasComponent(sender))
{
var xform = EntityManager.GetComponent<TransformComponent>(sender);
if (!_mapManager.TryGetGrid(xform.GridID, out var grid1))
if (!_mapManager.TryGetGrid(xform.GridID, out grid))
return;
var coords = xform.Coordinates;
var localGrid = grid.TileIndicesFor(coords);
_dirtyEntities.Enqueue(sender);
AddValidEntities(grid1.GetInDir(coords, Direction.North));
AddValidEntities(grid1.GetInDir(coords, Direction.South));
AddValidEntities(grid1.GetInDir(coords, Direction.East));
AddValidEntities(grid1.GetInDir(coords, Direction.West));
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(localGrid + new Vector2i(0, 1)), occluderQuery);
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(localGrid + new Vector2i(0, -1)), occluderQuery);
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(localGrid + new Vector2i(1, 0)), occluderQuery);
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(localGrid + new Vector2i(-1, 0)), occluderQuery);
}
// Entity is no longer valid, update around the last position it was at.
else if (ev.LastPosition.HasValue && _mapManager.TryGetGrid(ev.LastPosition.Value.grid, out var grid))
else if (ev.LastPosition.HasValue && _mapManager.TryGetGrid(ev.LastPosition.Value.grid, out grid))
{
var pos = ev.LastPosition.Value.pos;
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(1, 0)));
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(-1, 0)));
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(0, 1)));
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(0, -1)));
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(0, 1)), occluderQuery);
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(0, -1)), occluderQuery);
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(1, 0)), occluderQuery);
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(-1, 0)), occluderQuery);
}
}
private void AddValidEntities(IEnumerable<EntityUid> candidates)
private void AddValidEntities(AnchoredEntitiesEnumerator enumerator, EntityQuery<ClientOccluderComponent> occluderQuery)
{
foreach (var entity in candidates)
while (enumerator.MoveNext(out var entity))
{
if (EntityManager.HasComponent<ClientOccluderComponent>(entity))
{
_dirtyEntities.Enqueue(entity);
}
if (!occluderQuery.HasComponent(entity.Value)) continue;
_dirtyEntities.Enqueue(entity.Value);
}
}
}

View File

@@ -0,0 +1,105 @@
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.Containers;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
using Color = Robust.Shared.Maths.Color;
namespace Robust.Client.GameObjects;
public sealed class DebugEntityLookupCommand : IConsoleCommand
{
public string Command => "togglelookup";
public string Description => "Shows / hides entitylookup bounds via an overlay";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugEntityLookupSystem>().Enabled ^= true;
}
}
public sealed class DebugEntityLookupSystem : EntitySystem
{
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
if (_enabled)
{
IoCManager.Resolve<IOverlayManager>().AddOverlay(
new EntityLookupOverlay(
EntityManager,
Get<EntityLookupSystem>()));
}
else
{
IoCManager.Resolve<IOverlayManager>().RemoveOverlay<EntityLookupOverlay>();
}
}
}
private bool _enabled;
}
public sealed class EntityLookupOverlay : Overlay
{
private IEntityManager _entityManager = default!;
private EntityLookupSystem _lookup = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityLookupOverlay(IEntityManager entManager, EntityLookupSystem lookup)
{
_entityManager = entManager;
_lookup = lookup;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
var worldHandle = args.WorldHandle;
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
foreach (var lookup in _lookup.FindLookupsIntersecting(args.MapId, args.WorldBounds))
{
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var (_, rotation, matrix, invMatrix) = lookupXform.GetWorldPositionRotationMatrixWithInv();
worldHandle.SetTransform(matrix);
var lookupAABB = invMatrix.TransformBox(args.WorldBounds);
var ents = new List<EntityUid>();
// Gonna allocate a lot but debug overlay sooo
lookup.Tree._b2Tree.FastQuery(ref lookupAABB, (ref EntityUid data) =>
{
ents.Add(data);
});
foreach (var ent in ents)
{
if (_entityManager.Deleted(ent)) continue;
var xform = xformQuery.GetComponent(ent);
//DebugTools.Assert(!ent.IsInContainer(_entityManager));
var (entPos, entRot) = xform.GetWorldPositionRotation();
var lookupPos = invMatrix.Transform(entPos);
var lookupRot = entRot - rotation;
var aabb = _lookup.GetAABB(ent, lookupPos, lookupRot, xform, xformQuery);
worldHandle.DrawRect(aabb, Color.Blue.WithAlpha(0.2f));
}
}
}
}

View File

@@ -76,7 +76,7 @@ namespace Robust.Client.GameObjects
worldHandle.SetTransform(worldMatrix);
var transform = new Transform(Vector2.Zero, Angle.Zero);
gridInternal.GetMapChunks(viewport, out var chunkEnumerator);
var chunkEnumerator = gridInternal.GetMapChunks(viewport);
while (chunkEnumerator.MoveNext(out var chunk))
{

View File

@@ -1,9 +0,0 @@
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
internal sealed class GridFixtureSystem : SharedGridFixtureSystem
{
}
}

View File

@@ -0,0 +1,24 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects;
public sealed class ScaleVisualsSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AppearanceChangeEvent>(OnChangeData);
}
private void OnChangeData(ref AppearanceChangeEvent ev)
{
if (!ev.AppearanceData.TryGetValue(ScaleVisuals.Scale, out var scale) ||
!TryComp<SpriteComponent>(ev.Component.Owner, out var spriteComponent)) return;
var vecScale = (Vector2)scale;
// Set it directly because prediction may call this multiple times.
spriteComponent.Scale = vecScale;
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
@@ -17,13 +18,13 @@ public sealed partial class SpriteSystem
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Pure]
private readonly Dictionary<string, IRsiStateLike> _cachedPrototypeIcons = new();
public Texture Frame0(SpriteSpecifier specifier)
{
return RsiStateLike(specifier).Default;
}
[Pure]
public IRsiStateLike RsiStateLike(SpriteSpecifier specifier)
{
switch (specifier)
@@ -35,38 +36,71 @@ public sealed partial class SpriteSystem
return GetState(rsi);
case SpriteSpecifier.EntityPrototype prototypeIcon:
if (!_proto.TryIndex<EntityPrototype>(prototypeIcon.EntityPrototypeId, out var prototype))
{
Logger.Error("Failed to load PrototypeIcon {0}", prototypeIcon.EntityPrototypeId);
return SpriteComponent.GetFallbackState(_resourceCache);
}
return SpriteComponent.GetPrototypeIcon(prototype, _resourceCache);
return GetPrototypeIcon(prototypeIcon.EntityPrototypeId);
default:
throw new NotSupportedException();
}
}
[Pure]
public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
/// <summary>
/// Returns an icon for a given <see cref="EntityPrototype"/> ID, or a fallback in case of an error.
/// This method caches the result based on the prototype identifier.
/// </summary>
public IRsiStateLike GetPrototypeIcon(string prototype)
{
var icon = IconComponent.GetPrototypeIcon(prototype, _resourceCache);
if (icon != null) return icon;
// Check if this prototype has been cached before, and if so return the result.
if (_cachedPrototypeIcons.TryGetValue(prototype, out var cachedResult))
return cachedResult;
if (!prototype.Components.ContainsKey("Sprite"))
if (!_proto.TryIndex<EntityPrototype>(prototype, out var entityPrototype))
{
return SpriteComponent.GetFallbackState(resourceCache);
// The specified prototype doesn't exist, return the fallback "error" sprite.
Logger.Error("Failed to load PrototypeIcon {0}", prototype);
return GetFallbackState();
}
// Generate the icon and cache it in case it's ever needed again.
var result = GetPrototypeIcon(entityPrototype);
_cachedPrototypeIcons[prototype] = result;
return result;
}
/// <summary>
/// Returns an icon for a given <see cref="EntityPrototype"/> ID, or a fallback in case of an error.
/// This method does NOT cache the result.
/// </summary>
public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype)
{
// IconComponent takes precedence. If it has a valid icon, return that. Otherwise, continue as normal.
if (prototype.Components.TryGetValue("Icon", out var compData)
&& compData is IconComponent {Icon: {} icon})
{
return icon.Default;
}
// If the prototype doesn't have a SpriteComponent, then there's nothing we can do but return the fallback.
if (!prototype.Components.ContainsKey("Sprite"))
{
return GetFallbackState();
}
// Finally, we use spawn a dummy entity to get its icon.
var dummy = Spawn(prototype.ID, MapCoordinates.Nullspace);
var spriteComponent = EnsureComp<SpriteComponent>(dummy);
var result = spriteComponent.Icon ?? SpriteComponent.GetFallbackState(resourceCache);
var result = spriteComponent.Icon ?? GetFallbackState();
Del(dummy);
return result;
}
[Pure]
public RSI.State GetFallbackState()
{
return _resourceCache.GetFallback<RSIResource>().RSI["error"];
}
[Pure]
public RSI.State GetState(SpriteSpecifier.Rsi rsiSpecifier)
{
@@ -79,6 +113,20 @@ public sealed partial class SpriteSystem
}
Logger.Error("Failed to load RSI {0}", rsiSpecifier.RsiPath);
return SpriteComponent.GetFallbackState(_resourceCache);
return GetFallbackState();
}
private void OnPrototypesReloaded(PrototypesReloadedEventArgs protoReloaded)
{
// Check if any EntityPrototype has been changed.
if (!protoReloaded.ByType.TryGetValue(typeof(EntityPrototype), out var changedSet))
return;
// Remove all changed prototypes from the cache, if they're there.
foreach (var (prototype, _) in changedSet.Modified)
{
// Let's be lazy and not regenerate them until something needs them again.
_cachedPrototypeIcons.Remove(prototype);
}
}
}

View File

@@ -23,9 +23,16 @@ namespace Robust.Client.GameObjects
{
base.Initialize();
_proto.PrototypesReloaded += OnPrototypesReloaded;
SubscribeLocalEvent<SpriteUpdateInertEvent>(QueueUpdateInert);
}
public override void Shutdown()
{
base.Shutdown();
_proto.PrototypesReloaded -= OnPrototypesReloaded;
}
private void QueueUpdateInert(SpriteUpdateInertEvent ev)
{
_inertUpdateQueue.Enqueue(ev.Sprite);

View File

@@ -8,7 +8,7 @@ namespace Robust.Client.GameObjects
EntityUid CreateEntity(string? prototypeName, EntityUid uid = default);
void InitializeEntity(EntityUid entity);
void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null);
void StartEntity(EntityUid entity);
}

View File

@@ -409,7 +409,7 @@ namespace Robust.Client.GameStates
private void AckGameState(GameTick sequence)
{
var msg = _network.CreateNetMessage<MsgStateAck>();
var msg = new MsgStateAck();
msg.Sequence = sequence;
_network.ClientSendMessage(msg);
}
@@ -429,7 +429,7 @@ namespace Robust.Client.GameStates
private List<EntityUid> ApplyEntityStates(ReadOnlySpan<EntityState> curEntStates, ReadOnlySpan<EntityUid> deletions,
ReadOnlySpan<EntityState> nextEntStates)
{
var toApply = new Dictionary<EntityUid, (EntityState?, EntityState?)>();
var toApply = new Dictionary<EntityUid, (EntityState?, EntityState?)>(curEntStates.Length);
var toInitialize = new List<EntityUid>();
var created = new List<EntityUid>();
@@ -548,6 +548,8 @@ namespace Robust.Client.GameStates
if (curState != null)
{
compStateWork.EnsureCapacity(curState.ComponentChanges.Span.Length);
foreach (var compChange in curState.ComponentChanges.Span)
{
if (compChange.Deleted)
@@ -580,6 +582,8 @@ namespace Robust.Client.GameStates
if (nextState != null)
{
compStateWork.EnsureCapacity(compStateWork.Count + nextState.ComponentChanges.Span.Length);
foreach (var compState in nextState.ComponentChanges.Span)
{
if (compStateWork.TryGetValue(compState.NetID, out var state))

View File

@@ -50,7 +50,7 @@ namespace Robust.Client.Graphics.Clyde
var transform = _entityManager.GetComponent<TransformComponent>(grid.GridEntityId);
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
grid.GetMapChunks(worldBounds, out var enumerator);
var enumerator = grid.GetMapChunks(worldBounds);
while (enumerator.MoveNext(out var chunk))
{

View File

@@ -144,7 +144,7 @@ namespace Robust.Client.Graphics.Clyde
var worldAABB = CalcWorldAABB(vp);
var worldBounds = CalcWorldBounds(vp);
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, worldAABB, worldBounds);
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, vp.Eye!.Position.MapId, worldAABB, worldBounds);
foreach (var overlay in list)
{

View File

@@ -415,9 +415,15 @@ namespace Robust.Client.Graphics.Clyde
case float f:
program.SetUniform(name, f);
break;
case float[] fArr:
program.SetUniform(name, fArr);
break;
case Vector2 vector2:
program.SetUniform(name, vector2);
break;
case Vector2[] vector2Arr:
program.SetUniform(name, vector2Arr);
break;
case Vector3 vector3:
program.SetUniform(name, vector3);
break;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@@ -395,12 +395,24 @@ namespace Robust.Client.Graphics.Clyde
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, float[] value)
{
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, Vector2 value)
{
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, Vector2[] value)
{
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, Vector3 value)
{
var data = Parent._shaderInstances[Handle];

View File

@@ -412,10 +412,18 @@ namespace Robust.Client.Graphics.Clyde
{
}
private protected override void SetParameterImpl(string name, float[] value)
{
}
private protected override void SetParameterImpl(string name, Vector2 value)
{
}
private protected override void SetParameterImpl(string name, Vector2[] value)
{
}
private protected override void SetParameterImpl(string name, Vector3 value)
{
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using OpenToolkit.Graphics.OpenGL4;
@@ -234,6 +234,19 @@ namespace Robust.Client.Graphics.Clyde
GL.Uniform1(uniformId, single);
}
public void SetUniform(string uniformName, float[] singles)
{
var uniformId = GetUniform(uniformName);
GL.Uniform1(uniformId, singles.Length, singles);
_clyde.CheckGlError();
}
public void SetUniform(int uniformName, float[] singles)
{
var uniformId = GetUniform(uniformName);
GL.Uniform1(uniformId, singles.Length, singles);
}
public void SetUniform(string uniformName, in Matrix3 matrix)
{
var uniformId = GetUniform(uniformName);
@@ -375,6 +388,31 @@ namespace Robust.Client.Graphics.Clyde
}
}
public void SetUniform(string uniformName, Vector2[] vector)
{
var uniformId = GetUniform(uniformName);
SetUniformDirect(uniformId, vector);
}
public void SetUniform(int uniformName, Vector2[] vector)
{
var uniformId = GetUniform(uniformName);
SetUniformDirect(uniformId, vector);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetUniformDirect(int slot, Vector2[] vectors)
{
unsafe
{
fixed (Vector2* ptr = &vectors[0])
{
GL.Uniform2(slot, vectors.Length, (float*)ptr);
_clyde.CheckGlError();
}
}
}
public void SetUniformTexture(string uniformName, TextureUnit textureUnit)
{
var uniformId = GetUniform(uniformName);

View File

@@ -98,7 +98,7 @@ namespace Robust.Client.Graphics
handle = renderHandle.DrawingHandleWorld;
}
var args = new OverlayDrawArgs(currentSpace, vpControl, vp, handle, screenBox, worldBox, worldBounds);
var args = new OverlayDrawArgs(currentSpace, vpControl, vp, handle, screenBox, vp.Eye!.Position.MapId, worldBox, worldBounds);
Draw(args);
}

View File

@@ -1,6 +1,7 @@
using JetBrains.Annotations;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
@@ -38,6 +39,11 @@ namespace Robust.Client.Graphics
/// </summary>
public readonly UIBox2i ViewportBounds;
/// <summary>
/// <see cref="MapId"/> of the viewport's eye.
/// </summary>
public readonly MapId MapId;
/// <summary>
/// AABB enclosing the area visible in the viewport.
/// </summary>
@@ -57,6 +63,7 @@ namespace Robust.Client.Graphics
IClydeViewport viewport,
DrawingHandleBase drawingHandle,
in UIBox2i viewportBounds,
in MapId mapId,
in Box2 worldAabb,
in Box2Rotated worldBounds)
{
@@ -65,6 +72,7 @@ namespace Robust.Client.Graphics
Viewport = viewport;
DrawingHandle = drawingHandle;
ViewportBounds = viewportBounds;
MapId = mapId;
WorldAABB = worldAabb;
WorldBounds = worldBounds;
}

View File

@@ -135,36 +135,42 @@ namespace Robust.Client.Graphics
internal sealed class ShaderDataTypeFull
{
public ShaderDataTypeFull(ShaderDataType type, ShaderPrecisionQualifier prec)
public ShaderDataTypeFull(ShaderDataType type, ShaderPrecisionQualifier prec, int? count = null)
{
Type = type;
Precision = prec;
Count = count;
}
public ShaderDataType Type { get; }
public ShaderPrecisionQualifier Precision { get; }
public int? Count;
public bool IsArray => Count != null;
public string GetNativeType()
{
if (Precision == ShaderPrecisionQualifier.Low)
string? precision = Precision switch
{
return "lowp " + Type.GetNativeType();
}
else if (Precision == ShaderPrecisionQualifier.Medium)
{
return "mediump " + Type.GetNativeType();
}
else if (Precision == ShaderPrecisionQualifier.High)
{
return "highp " + Type.GetNativeType();
}
return Type.GetNativeType();
ShaderPrecisionQualifier.Low => "lowp ",
ShaderPrecisionQualifier.Medium => "mediump ",
ShaderPrecisionQualifier.High => "highp ",
_ => null,
};
return IsArray ? $"{precision}{Type.GetNativeType()}[{Count}]" : $"{precision}{Type.GetNativeType()}";
}
public bool TypePrecisionConsistent()
{
return Type.TypeHasPrecision() == (Precision != ShaderPrecisionQualifier.None);
}
public bool TypeCountConsistent()
{
return Count == null || Type.TypeSupportsArrays();
}
}
internal static class ShaderEnumExt
@@ -195,6 +201,7 @@ namespace Robust.Client.Graphics
{
return
(type == ShaderDataType.Float) ||
(type == ShaderDataType.Int) ||
(type == ShaderDataType.Vec2) ||
(type == ShaderDataType.Vec3) ||
(type == ShaderDataType.Vec4) ||
@@ -203,6 +210,14 @@ namespace Robust.Client.Graphics
(type == ShaderDataType.Mat4);
}
public static bool TypeSupportsArrays(this ShaderDataType type)
{
// TODO: add support for int, and vec3/4 arrays
return
(type == ShaderDataType.Float) ||
(type == ShaderDataType.Vec2);
}
[SuppressMessage("ReSharper", "StringLiteralTypo")]
private static readonly Dictionary<ShaderDataType, string> _nativeTypes = new()
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
@@ -119,6 +119,13 @@ namespace Robust.Client.Graphics
SetParameterImpl(name, value);
}
public void SetParameter(string name, float[] value)
{
EnsureAlive();
EnsureMutable();
SetParameterImpl(name, value);
}
public void SetParameter(string name, Vector2 value)
{
EnsureAlive();
@@ -126,6 +133,13 @@ namespace Robust.Client.Graphics
SetParameterImpl(name, value);
}
public void SetParameter(string name, Vector2[] value)
{
EnsureAlive();
EnsureMutable();
SetParameterImpl(name, value);
}
public void SetParameter(string name, Vector3 value)
{
EnsureAlive();
@@ -244,7 +258,9 @@ namespace Robust.Client.Graphics
private protected abstract ShaderInstance DuplicateImpl();
private protected abstract void SetParameterImpl(string name, float value);
private protected abstract void SetParameterImpl(string name, float[] value);
private protected abstract void SetParameterImpl(string name, Vector2 value);
private protected abstract void SetParameterImpl(string name, Vector2[] value);
private protected abstract void SetParameterImpl(string name, Vector3 value);
private protected abstract void SetParameterImpl(string name, Vector4 value);
private protected abstract void SetParameterImpl(string name, Color value);

View File

@@ -504,17 +504,33 @@ namespace Robust.Client.Graphics
continue;
}
if (_shaderTypeMap.TryGetValue(wordType.Word, out var ret))
if (!_shaderTypeMap.TryGetValue(wordType.Word, out var ret))
throw new ShaderParseException("Expected type", wordType.Position);
var result = new ShaderDataTypeFull(ret, precision);
if (!result.TypePrecisionConsistent())
{
var result = new ShaderDataTypeFull(ret, precision);
if (!result.TypePrecisionConsistent())
{
throw new ShaderParseException($"Type {ret} cannot accept precision {precision}", wordType.Position);
}
return result;
throw new ShaderParseException($"Type {ret} cannot accept precision {precision}", wordType.Position);
}
throw new ShaderParseException("Expected type or precision", wordType.Position);
// is this type meant to be an array?
if (_peekToken() is TokenSymbol bracketOpen && bracketOpen.Symbol == Symbols.BracketOpen)
{
_takeToken();
if (_takeToken() is not TokenNumber number || !int.TryParse(number.Number, out var count))
throw new ShaderParseException($"Failed to parse array length", bracketOpen.Position);
if (_takeToken() is not TokenSymbol bracketClose || bracketClose.Symbol != Symbols.BracketClosed)
throw new ShaderParseException($"Array length definition missing closing bracket", number.Position);
result.Count = count;
// are arrays supported by this type?
if (!result.TypeCountConsistent())
throw new ShaderParseException($"Type {ret} does not support arrays", wordType.Position);
}
return result;
}
}

View File

@@ -19,7 +19,7 @@ namespace Robust.Client.Graphics
[Dependency] private readonly IResourceCache _resourceCache = default!;
[ViewVariables]
[DataField("id", required: true)]
[IdDataFieldAttribute]
public string ID { get; } = default!;
private ShaderKind Kind;

View File

@@ -1,9 +1,19 @@
namespace Robust.Client
{
public interface IGameController
{
InitialLaunchState LaunchState { get; }
namespace Robust.Client;
public interface IGameController
{
InitialLaunchState LaunchState { get; }
void Shutdown(string? reason=null);
/// <summary>
/// Try to cause the launcher to either reconnect to the same server or connect to a new server.
/// *The engine will shutdown on success.*
/// Will throw an exception if contacting the launcher failed (success indicates it is now the launcher's responsibility).
/// To redial the same server, retrieve the server's address from `LaunchState.Ss14Address`.
/// </summary>
/// <param name="address">The server address, such as "ss14://localhost:1212/".</param>
/// <param name="text">Informational text on the cause of the reconnect. Empty or null gives a default reason.</param>
void Redial(string address, string? text = null);
}
void Shutdown(string? reason=null);
}
}

View File

@@ -497,7 +497,7 @@ namespace Robust.Client.Input
if (robustMapping.TryGet("binds", out var BaseKeyRegsNode))
{
var baseKeyRegs = serializationManager.ReadValueOrThrow<KeyBindingRegistration[]>(BaseKeyRegsNode);
var baseKeyRegs = serializationManager.Read<KeyBindingRegistration[]>(BaseKeyRegsNode);
foreach (var reg in baseKeyRegs)
{
@@ -526,7 +526,7 @@ namespace Robust.Client.Input
if (userData && robustMapping.TryGet("leaveEmpty", out var node))
{
var leaveEmpty = serializationManager.ReadValueOrThrow<BoundKeyFunction[]>(node);
var leaveEmpty = serializationManager.Read<BoundKeyFunction[]>(node);
if (leaveEmpty.Length > 0)
{

View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Client.Physics
{
internal sealed class GridFixtureSystem : SharedGridFixtureSystem
{
public bool EnableDebug
{
get => _enableDebug;
set
{
if (_enableDebug == value) return;
_enableDebug = value;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (_enableDebug)
{
var overlay = new GridSplitNodeOverlay(EntityManager, IoCManager.Resolve<IMapManager>(), this);
overlayManager.AddOverlay(overlay);
RaiseNetworkEvent(new RequestGridNodesMessage());
}
else
{
overlayManager.RemoveOverlay<GridSplitNodeOverlay>();
RaiseNetworkEvent(new StopGridNodesMessage());
}
}
}
private bool _enableDebug = false;
private readonly Dictionary<EntityUid, Dictionary<Vector2i, List<List<Vector2i>>>> _nodes = new();
private readonly Dictionary<EntityUid, List<(Vector2, Vector2)>> _connections = new();
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<ChunkSplitDebugMessage>(OnDebugMessage);
}
public override void Shutdown()
{
base.Shutdown();
_nodes.Clear();
_connections.Clear();
}
private void OnDebugMessage(ChunkSplitDebugMessage ev)
{
if (!_enableDebug) return;
_nodes[ev.Grid] = ev.Nodes;
_connections[ev.Grid] = ev.Connections;
}
private sealed class GridSplitNodeOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private IEntityManager _entManager;
private IMapManager _mapManager;
private GridFixtureSystem _system;
public GridSplitNodeOverlay(IEntityManager entManager, IMapManager mapManager, GridFixtureSystem system)
{
_entManager = entManager;
_mapManager = mapManager;
_system = system;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
var worldHandle = args.WorldHandle;
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
foreach (var iGrid in _mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds))
{
// May not have received nodes yet.
if (!_system._nodes.TryGetValue(iGrid.GridEntityId, out var nodes)) continue;
var gridXform = xformQuery.GetComponent(iGrid.GridEntityId);
worldHandle.SetTransform(gridXform.WorldMatrix);
var grid = (MapGrid)iGrid;
var chunkEnumerator = grid.GetMapChunks(args.WorldBounds);
while (chunkEnumerator.MoveNext(out var chunk))
{
if (!nodes.TryGetValue(chunk.Indices, out var chunkNodes)) continue;
for (var i = 0; i < chunkNodes.Count; i++)
{
var group = chunkNodes[i];
var offset = chunk.Indices * chunk.ChunkSize;
var color = GetColor(chunk, i);
foreach (var index in group)
{
worldHandle.DrawRect(new Box2(offset + index, offset + index + 1).Enlarged(-0.1f), color);
}
}
}
var connections = _system._connections[iGrid.GridEntityId];
foreach (var (start, end) in connections)
{
worldHandle.DrawLine(start, end, Color.Aquamarine);
}
}
}
private Color GetColor(MapChunk chunk, int index)
{
// Just want something that doesn't give similar indices at 0,0 but is also deterministic.
// Add an offset to yIndex so we at least have some colour that isn't grey at 0,0
var actualIndex = chunk.Indices.X * 20 + (chunk.Indices.Y + 20) * 35 + index * 50;
var red = (byte) (actualIndex % 255);
var green = (byte) (actualIndex * 20 % 255);
var blue = (byte) (actualIndex * 30 % 255);
return new Color(red, green, blue, 85);
}
}
}
}

View File

@@ -0,0 +1,18 @@
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Client.Physics;
public sealed class GridSplitVisualCommand : IConsoleCommand
{
public string Command => SharedGridFixtureSystem.ShowGridNodesCommand;
public string Description => "Shows the nodes for grid split purposes";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GridFixtureSystem>();
system.EnableDebug ^= true;
shell.WriteLine($"Toggled gridsplit node visuals");
}
}

View File

@@ -49,7 +49,7 @@ namespace Robust.Client.Physics
// Also need to suss out having the client build the island anyway and just... not solving it?
foreach (var body in AwakeBodies)
{
if (body.Island || body.LinearVelocity.Length > _linSleepTolerance / 2f || body.AngularVelocity > _angSleepTolerance / 2f) continue;
if (body.LinearVelocity.Length > _linSleepTolerance / 2f || body.AngularVelocity > _angSleepTolerance / 2f) continue;
body.SleepTime += frameTime;
if (body.SleepTime > _timeToSleep)
{

View File

@@ -418,7 +418,7 @@ namespace Robust.Client.Placement
if (!IsActive || !Eraser) return;
if (Hijack != null && Hijack.HijackDeletion(entity)) return;
var msg = NetworkManager.CreateNetMessage<MsgPlacement>();
var msg = new MsgPlacement();
msg.PlaceType = PlacementManagerMessage.RequestEntRemove;
msg.EntityUid = entity;
NetworkManager.ClientSendMessage(msg);
@@ -426,7 +426,7 @@ namespace Robust.Client.Placement
public void HandleRectDeletion(EntityCoordinates start, Box2 rect)
{
var msg = NetworkManager.CreateNetMessage<MsgPlacement>();
var msg = new MsgPlacement();
msg.PlaceType = PlacementManagerMessage.RequestRectRemove;
msg.EntityCoordinates = new EntityCoordinates(StartPoint.EntityId, rect.BottomLeft);
msg.RectSize = rect.Size;
@@ -748,7 +748,7 @@ namespace Robust.Client.Placement
_pendingTileChanges.Add(tuple);
}
var message = NetworkManager.CreateNetMessage<MsgPlacement>();
var message = new MsgPlacement();
message.PlaceType = PlacementManagerMessage.RequestPlacement;
message.Align = CurrentMode.ModeName;

View File

@@ -170,10 +170,10 @@ namespace Robust.Client.Placement
/// </summary>
public TileRef GetTileRef(EntityCoordinates coordinates)
{
var mapCoords = coordinates.ToMap(pManager.EntityManager);
var gridId = coordinates.GetGridId(pManager.EntityManager);
var gridUid = coordinates.GetGridUid(pManager.EntityManager);
return gridId.IsValid() ? pManager.MapManager.GetGrid(gridId).GetTileRef(MouseCoords)
: new TileRef(mapCoords.MapId, gridId,
: new TileRef(gridId, gridUid.GetValueOrDefault(),
MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager), Tile.Empty);
}

View File

@@ -89,7 +89,7 @@ namespace Robust.Client.Player
{
LocalPlayer = new LocalPlayer();
var msgList = _network.CreateNetMessage<MsgPlayerListReq>();
var msgList = new MsgPlayerListReq();
// message is empty
_network.ClientSendMessage(msgList);
}

View File

@@ -57,7 +57,7 @@ namespace Robust.Client.Prototypes
#if !FULL_RELEASE
var sw = Stopwatch.StartNew();
var msg = _netManager.CreateNetMessage<MsgReloadPrototypes>();
var msg = new MsgReloadPrototypes();
msg.Paths = _reloadQueue.ToArray();
_netManager.ClientSendMessage(msg);

View File

@@ -22,6 +22,8 @@ namespace Robust.Client.ResourceManagement
{
private static readonly float[] OneArray = {1};
public override ResourcePath? Fallback => new("/Textures/error.rsi");
private static readonly JsonSerializerOptions SerializerOptions =
new JsonSerializerOptions(JsonSerializerDefaults.Web)
{

View File

@@ -1,10 +1,10 @@
using System;
using Robust.Client.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Validation;
@@ -16,26 +16,40 @@ namespace Robust.Client.Serialization
[TypeSerializer]
public sealed class AppearanceVisualizerSerializer : ITypeSerializer<AppearanceVisualizer, MappingDataNode>
{
public DeserializationResult Read(ISerializationManager serializationManager, MappingDataNode node,
public AppearanceVisualizer Read(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context = null)
ISerializationContext? context = null, AppearanceVisualizer? value = null)
{
Type? type = null;
if (!node.TryGet("type", out var typeNode))
throw new InvalidMappingException("No type specified for AppearanceVisualizer!");
{
if (value == null)
throw new InvalidMappingException("No type specified for AppearanceVisualizer!");
if (typeNode is not ValueDataNode typeValueDataNode)
throw new InvalidMappingException("Type node not a value node for AppearanceVisualizer!");
type = value.GetType();
}
else
{
if (typeNode is not ValueDataNode typeValueDataNode)
throw new InvalidMappingException("Type node not a value node for AppearanceVisualizer!");
var type = IoCManager.Resolve<IReflectionManager>()
.YamlTypeTagLookup(typeof(AppearanceVisualizer), typeValueDataNode.Value);
if (type == null)
throw new InvalidMappingException(
$"Invalid type {typeValueDataNode.Value} specified for AppearanceVisualizer!");
type = IoCManager.Resolve<IReflectionManager>()
.YamlTypeTagLookup(typeof(AppearanceVisualizer), typeValueDataNode.Value);
if (type == null)
throw new InvalidMappingException(
$"Invalid type {typeValueDataNode.Value} specified for AppearanceVisualizer!");
if(value != null && !type.IsInstanceOfType(value))
{
throw new InvalidMappingException(
$"Specified Type does not match type of provided Value for AppearanceVisualizer! (TypeOfValue: {value.GetType()}, ProvidedValue: {type})");
}
}
var newNode = node.Copy();
newNode.Remove("type");
return serializationManager.Read(type, newNode, context, skipHook);
return (AppearanceVisualizer) serializationManager.Read(type, newNode, context, skipHook, value)!;
}
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,

View File

@@ -0,0 +1,395 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Log;
namespace Robust.Client.UserInterface.Controls;
// condensed version of the original ColorSlider set
public sealed class ColorSelectorSliders : Control
{
public Color Color
{
get => _currentColor;
set
{
_updating = true;
_currentColor = value;
Update();
_updating = false;
}
}
public ColorSelectorType SelectorType
{
get => _currentType;
set
{
_updating = true;
_currentType = value;
UpdateType();
Update();
_updating = false;
}
}
public bool IsAlphaVisible
{
get => _isAlphaVisible;
set
{
_isAlphaVisible = value;
_alphaSliderBox.Visible = _isAlphaVisible;
}
}
public Action<Color>? OnColorChanged;
private bool _updating = false;
private Color _currentColor = Color.White;
private ColorSelectorType _currentType = ColorSelectorType.Rgb;
private bool _isAlphaVisible = false;
private ColorableSlider _topColorSlider;
private ColorableSlider _middleColorSlider;
private ColorableSlider _bottomColorSlider;
private Slider _alphaSlider;
private BoxContainer _alphaSliderBox = new();
private FloatSpinBox _topInputBox;
private FloatSpinBox _middleInputBox;
private FloatSpinBox _bottomInputBox;
private FloatSpinBox _alphaInputBox;
private Label _topSliderLabel = new();
private Label _middleSliderLabel = new();
private Label _bottomSliderLabel = new();
private Label _alphaSliderLabel = new();
private OptionButton _typeSelector;
private List<ColorSelectorType> _types = new();
public ColorSelectorSliders()
{
_topColorSlider = new ColorableSlider
{
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
MaxValue = 1.0f
};
_middleColorSlider = new ColorableSlider
{
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
MaxValue = 1.0f
};
_bottomColorSlider = new ColorableSlider
{
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
MaxValue = 1.0f
};
_alphaSlider = new Slider
{
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
MaxValue = 1.0f,
};
_topColorSlider.OnValueChanged += _ => { OnColorSet(); };
_middleColorSlider.OnValueChanged += _ => { OnColorSet(); };
_bottomColorSlider.OnValueChanged += _ => { OnColorSet(); };
_alphaSlider.OnValueChanged += _ => { OnColorSet(); };
_topInputBox = new FloatSpinBox(1f, 2)
{
IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Top)
};
_middleInputBox = new FloatSpinBox(1f, 2)
{
IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Middle)
};
_bottomInputBox = new FloatSpinBox(1f, 2)
{
IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Bottom)
};
_alphaInputBox = new FloatSpinBox(1f, 2)
{
IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Alpha)
};
_topInputBox.OnValueChanged += value =>
{
_topColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Top);
};
_middleInputBox.OnValueChanged += value =>
{
_middleColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Middle);
};
_bottomInputBox.OnValueChanged += value =>
{
_bottomColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Bottom);
};
_alphaInputBox.OnValueChanged += value =>
{
_alphaSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Alpha);
};
_alphaSliderLabel.Text = Loc.GetString("color-selector-sliders-alpha");
_typeSelector = new OptionButton();
foreach (var ty in Enum.GetValues<ColorSelectorType>())
{
_typeSelector.AddItem(Loc.GetString($"color-selector-sliders-{ty.ToString().ToLower()}"));
_types.Add(ty);
}
_typeSelector.OnItemSelected += args =>
{
SelectorType = _types[args.Id];
_typeSelector.Select(args.Id);
};
// TODO: Maybe some engine widgets could be laid out in XAML?
var rootBox = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical
};
AddChild(rootBox);
var headerBox = new BoxContainer();
rootBox.AddChild(headerBox);
headerBox.AddChild(_typeSelector);
var bodyBox = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Vertical
};
// pita
var topSliderBox = new BoxContainer();
topSliderBox.AddChild(_topSliderLabel);
topSliderBox.AddChild(_topColorSlider);
topSliderBox.AddChild(_topInputBox);
var middleSliderBox = new BoxContainer();
middleSliderBox.AddChild(_middleSliderLabel);
middleSliderBox.AddChild(_middleColorSlider);
middleSliderBox.AddChild(_middleInputBox);
var bottomSliderBox = new BoxContainer();
bottomSliderBox.AddChild(_bottomSliderLabel);
bottomSliderBox.AddChild(_bottomColorSlider);
bottomSliderBox.AddChild(_bottomInputBox);
_alphaSliderBox.Visible = IsAlphaVisible;
_alphaSliderBox.AddChild(_alphaSliderLabel);
_alphaSliderBox.AddChild(_alphaSlider);
_alphaSliderBox.AddChild(_alphaInputBox);
bodyBox.AddChild(topSliderBox);
bodyBox.AddChild(middleSliderBox);
bodyBox.AddChild(bottomSliderBox);
bodyBox.AddChild(_alphaSliderBox);
rootBox.AddChild(bodyBox);
_updating = true;
UpdateType();
Update();
_updating = false;
}
private void UpdateType()
{
(string topLabel, string middleLabel, string bottomLabel) labels = GetSliderLabels();
_topSliderLabel.Text = labels.topLabel;
_middleSliderLabel.Text = labels.middleLabel;
_bottomSliderLabel.Text = labels.bottomLabel;
}
private void Update()
{
_topColorSlider.SetColor(_currentColor);
_middleColorSlider.SetColor(_currentColor);
_bottomColorSlider.SetColor(_currentColor);
switch (SelectorType)
{
case ColorSelectorType.Rgb:
_topColorSlider.Value = Color.R;
_middleColorSlider.Value = Color.G;
_bottomColorSlider.Value = Color.B;
_topInputBox.Value = Color.R * 255.0f;
_middleInputBox.Value = Color.G * 255.0f;
_bottomInputBox.Value = Color.B * 255.0f;
break;
case ColorSelectorType.Hsv:
Vector4 color = Color.ToHsv(Color);
// dumb workaround because the formula for
// HSV calculation results in a negative
// number in any value past 300 degrees
if (color.X > 0)
{
_topColorSlider.Value = color.X;
_topInputBox.Value = color.X * 360.0f;
}
else
{
_topInputBox.Value = _topColorSlider.Value * 360.0f;
}
_middleColorSlider.Value = color.Y;
_bottomColorSlider.Value = color.Z;
_middleInputBox.Value = color.Y * 100.0f;
_bottomInputBox.Value = color.Z * 100.0f;
break;
}
_alphaSlider.Value = Color.A;
_alphaInputBox.Value = Color.A * 100.0f;
}
private bool IsSpinBoxValid(float value, ColorSliderOrder ordering)
{
if (value < 0)
{
return false;
}
if (ordering == ColorSliderOrder.Alpha)
{
return value <= 100.0f;
}
switch (SelectorType)
{
case ColorSelectorType.Rgb:
return value <= byte.MaxValue;
case ColorSelectorType.Hsv:
switch (ordering)
{
case ColorSliderOrder.Top:
return value <= 360.0f;
default:
return value <= 100.0f;
}
}
return false;
}
private (string, string, string) GetSliderLabels()
{
switch (SelectorType)
{
case ColorSelectorType.Rgb:
return (
Loc.GetString("color-selector-sliders-red"),
Loc.GetString("color-selector-sliders-green"),
Loc.GetString("color-selector-sliders-blue")
);
case ColorSelectorType.Hsv:
return (
Loc.GetString("color-selector-sliders-hue"),
Loc.GetString("color-selector-sliders-saturation"),
Loc.GetString("color-selector-sliders-value")
);
}
return ("ERR", "ERR", "ERR");
}
private float GetColorValueDivisor(ColorSliderOrder order)
{
if (order == ColorSliderOrder.Alpha)
{
return 100.0f;
}
switch (SelectorType)
{
case ColorSelectorType.Rgb:
return 255.0f;
case ColorSelectorType.Hsv:
switch (order)
{
case ColorSliderOrder.Top:
return 360.0f;
default:
return 100.0f;
}
}
return 0.0f;
}
private void OnColorSet()
{
// stack overflow otherwise due to value sets
if (_updating)
{
return;
}
switch (SelectorType)
{
case ColorSelectorType.Rgb:
Color rgbColor = new Color(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value);
_currentColor = rgbColor;
Update();
OnColorChanged!(rgbColor);
break;
case ColorSelectorType.Hsv:
Color hsvColor = Color.FromHsv(new Vector4(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value));
_currentColor = hsvColor;
Update();
OnColorChanged!(hsvColor);
break;
}
}
private enum ColorSliderOrder
{
Top,
Middle,
Bottom,
Alpha
}
public enum ColorSelectorType
{
Rgb,
Hsv,
}
}

View File

@@ -0,0 +1,81 @@
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Log;
namespace Robust.Client.UserInterface.Controls;
public sealed class ColorableSlider : Slider
{
public const string StylePropertyFillWhite = "fillWhite"; // needs to be filled with white
public const string StylePropertyBackgroundWhite = "backgroundWhite"; // also needs to be filled with white
public Color Color { get; private set; } = Color.White;
public PartSelector Part
{
get => _currentPartSelector;
set
{
_currentPartSelector = value;
UpdateStyleBoxes();
}
}
private PartSelector _currentPartSelector = PartSelector.Background;
public void SetColor(Color color)
{
Color = color;
switch (Part)
{
case PartSelector.Fill:
_fillPanel.Modulate = Color;
break;
case PartSelector.Background:
_backgroundPanel.Modulate = Color;
break;
}
}
protected override void UpdateStyleBoxes()
{
StyleBox? GetStyleBox(string name)
{
if (TryGetStyleProperty<StyleBox>(name, out var box))
{
return box;
}
return null;
}
string backBox = StylePropertyBackground;
string fillBox = StylePropertyFill;
switch (Part)
{
case PartSelector.Fill:
fillBox = StylePropertyFillWhite;
_fillPanel.Modulate = Color;
break;
case PartSelector.Background:
backBox = StylePropertyBackgroundWhite;
_fillPanel.Modulate = Color.Transparent; // make this transparent
_backgroundPanel.Modulate = Color;
break;
}
_backgroundPanel.PanelOverride = BackgroundStyleBoxOverride ?? GetStyleBox(backBox);
_foregroundPanel.PanelOverride = ForegroundStyleBoxOverride ?? GetStyleBox(StylePropertyForeground);
_fillPanel.PanelOverride = FillStyleBoxOverride ?? GetStyleBox(fillBox);
_grabber.PanelOverride = GrabberStyleBoxOverride ?? GetStyleBox(StylePropertyGrabber);
}
public enum PartSelector
{
Fill,
Background
}
}

View File

@@ -65,18 +65,22 @@ namespace Robust.Client.UserInterface.Controls
get => _text;
set
{
if (value == null)
{
value = "";
}
// Save cursor position or -1 for end
var cursorTarget = CursorPosition == _text.Length ? -1 : CursorPosition;
if (!SetText(value))
if (!InternalSetText(value))
{
return;
}
_cursorPosition = 0;
_selectionStart = 0;
var clamped = MathHelper.Clamp(cursorTarget == -1 ? _text.Length : cursorTarget, 0, _text.Length);
while (clamped < _text.Length && !Rune.TryGetRuneAt(_text, clamped, out _))
{
clamped++;
}
_cursorPosition = clamped;
_selectionStart = _cursorPosition;
_updatePseudoClass();
}
}
@@ -203,7 +207,7 @@ namespace Robust.Client.UserInterface.Controls
var lower = SelectionLower;
var newContents = Text[..lower] + text + Text[SelectionUpper..];
if (!SetText(newContents))
if (!InternalSetText(newContents))
{
return;
}
@@ -216,7 +220,7 @@ namespace Robust.Client.UserInterface.Controls
/// <remarks>
/// Does not fix cursor positions, those will have to be adjusted manually.
/// </remarks>>
protected bool SetText(string newText)
private bool InternalSetText(string newText)
{
if (IsValid != null && !IsValid(newText))
{

View File

@@ -13,10 +13,10 @@ namespace Robust.Client.UserInterface.Controls
public const string StylePropertyFill = "fill";
public const string StylePropertyGrabber = "grabber";
private readonly PanelContainer _foregroundPanel;
private readonly PanelContainer _backgroundPanel;
private readonly PanelContainer _fillPanel;
private readonly PanelContainer _grabber;
protected readonly PanelContainer _foregroundPanel;
protected readonly PanelContainer _backgroundPanel;
protected readonly PanelContainer _fillPanel;
protected readonly PanelContainer _grabber;
private bool _grabbed;
@@ -169,7 +169,7 @@ namespace Robust.Client.UserInterface.Controls
UpdateStyleBoxes();
}
private void UpdateStyleBoxes()
protected virtual void UpdateStyleBoxes()
{
StyleBox? GetStyleBox(string name)
{
@@ -182,7 +182,7 @@ namespace Robust.Client.UserInterface.Controls
}
_backgroundPanel.PanelOverride = BackgroundStyleBoxOverride ?? GetStyleBox(StylePropertyBackground);
_foregroundPanel.PanelOverride = BackgroundStyleBoxOverride ?? GetStyleBox(StylePropertyForeground);
_foregroundPanel.PanelOverride = ForegroundStyleBoxOverride ?? GetStyleBox(StylePropertyForeground);
_fillPanel.PanelOverride = FillStyleBoxOverride ?? GetStyleBox(StylePropertyFill);
_grabber.PanelOverride = GrabberStyleBoxOverride ?? GetStyleBox(StylePropertyGrabber);
}

View File

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

View File

@@ -228,7 +228,7 @@ namespace Robust.Client.UserInterface.CustomControls
foreach (var prototype in prototypeManager.EnumeratePrototypes<EntityPrototype>())
{
if (prototype.Abstract)
if (prototype.NoSpawn || prototype.Abstract)
{
continue;
}

View File

@@ -35,7 +35,7 @@ namespace Robust.Client.ViewVariables.Editors
Text = value switch
{
IPrototype prototype => prototype.ID,
ViewVariablesBlobMembers.PrototypeReferenceToken token => token.ID,
ViewVariablesBlobMembers.PrototypeReferenceToken token => token.ID ?? string.Empty,
_ => string.Empty
},
Editable = !ReadOnly

View File

@@ -9,7 +9,7 @@ namespace Robust.Client.ViewVariables.Editors
{
var lineEdit = new LineEdit
{
Text = (string) value!,
Text = (string) (value ?? ""),
Editable = !ReadOnly,
HorizontalExpand = true,
};

View File

@@ -142,7 +142,7 @@ namespace Robust.Client.ViewVariables.Instances
{
ViewVariablesTraitMembers.CreateMemberGroupHeader(
ref first,
TypeAbbreviation.Abbreviate(group.Key),
PrettyPrint.PrintUserFacingTypeShort(group.Key, 2),
clientVBox);
foreach (var control in group)
@@ -206,7 +206,7 @@ namespace Robust.Client.ViewVariables.Instances
foreach (var component in componentList)
{
var button = new Button {Text = TypeAbbreviation.Abbreviate(component.GetType()), TextAlign = Label.AlignMode.Left};
var button = new Button {Text = PrettyPrint.PrintUserFacingTypeShort(component.GetType(), 2), TextAlign = Label.AlignMode.Left};
var removeButton = new TextureButton()
{
StyleClasses = { DefaultWindow.StyleClassWindowCloseButton },

View File

@@ -45,7 +45,7 @@ namespace Robust.Client.ViewVariables.Traits
{
CreateMemberGroupHeader(
ref first,
TypeAbbreviation.Abbreviate(group.Key),
PrettyPrint.PrintUserFacingTypeShort(group.Key, 2),
_memberList);
foreach (var control in group)

View File

@@ -98,7 +98,7 @@ namespace Robust.Client.ViewVariables
Editable = access == VVAccess.ReadWrite,
Name = memberInfo.Name,
Type = memberType.AssemblyQualifiedName,
TypePretty = TypeAbbreviation.Abbreviate(memberType),
TypePretty = PrettyPrint.PrintUserFacingTypeShort(memberType, 2),
Value = value
};

View File

@@ -277,7 +277,7 @@ namespace Robust.Client.ViewVariables
public Task<ViewVariablesRemoteSession> RequestSession(ViewVariablesObjectSelector selector)
{
var msg = _netManager.CreateNetMessage<MsgViewVariablesReqSession>();
var msg = new MsgViewVariablesReqSession();
msg.Selector = selector;
msg.RequestId = _nextReqId++;
_netManager.ClientSendMessage(msg);
@@ -293,7 +293,7 @@ namespace Robust.Client.ViewVariables
throw new ArgumentException("Session is closed", nameof(session));
}
var msg = _netManager.CreateNetMessage<MsgViewVariablesReqData>();
var msg = new MsgViewVariablesReqData();
var reqId = msg.RequestId = _nextReqId++;
msg.RequestMeta = meta;
msg.SessionId = session.SessionId;
@@ -315,7 +315,7 @@ namespace Robust.Client.ViewVariables
throw new ArgumentException();
}
var closeMsg = _netManager.CreateNetMessage<MsgViewVariablesCloseSession>();
var closeMsg = new MsgViewVariablesCloseSession();
closeMsg.SessionId = session.SessionId;
_netManager.ClientSendMessage(closeMsg);
}
@@ -332,7 +332,7 @@ namespace Robust.Client.ViewVariables
throw new ArgumentException();
}
var msg = _netManager.CreateNetMessage<MsgViewVariablesModifyRemote>();
var msg = new MsgViewVariablesModifyRemote();
msg.SessionId = session.SessionId;
msg.ReinterpretValue = reinterpretValue;
msg.PropertyIndex = propertyIndex;

View File

@@ -31,6 +31,7 @@ using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Threading;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Serilog.Debugging;
@@ -87,6 +88,7 @@ namespace Robust.Server
[Dependency] private readonly ILocalizationManagerInternal _loc = default!;
[Dependency] private readonly INetConfigurationManager _netCfgMan = default!;
[Dependency] private readonly IServerConsoleHost _consoleHost = default!;
[Dependency] private readonly IParallelManagerInternal _parallelMgr = default!;
private readonly Stopwatch _uptimeStopwatch = new();
@@ -184,6 +186,8 @@ namespace Robust.Server
ProfileOptSetup.Setup(_config);
_parallelMgr.Initialize();
//Sets up Logging
_logHandlerFactory = logHandlerFactory;
@@ -338,7 +342,7 @@ namespace Robust.Server
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
prototypeManager.Initialize();
prototypeManager.LoadDirectory(Options.PrototypeDirectory);
prototypeManager.Resync();
prototypeManager.ResolveResults();
_consoleHost.Initialize();
_entityManager.Startup();

View File

@@ -312,15 +312,20 @@ namespace Robust.Server.Bql
{
var radius = (float)(double)arguments[0];
var entityLookup = EntitySystem.Get<EntityLookupSystem>();
var xformQuery = IoCManager.Resolve<IEntityManager>().GetEntityQuery<TransformComponent>();
var distinct = new HashSet<EntityUid>();
foreach (var uid in input)
{
foreach (var near in entityLookup.GetEntitiesInRange(xformQuery.GetComponent(uid).Coordinates,
radius))
{
if (!distinct.Add(near)) continue;
yield return near;
}
}
// TODO: Make this a foreach and reduce LINQ chain because it'll allocate a LOT
//BUG: GetEntitiesInRange effectively uses manhattan distance. This is not intended, near is supposed to be circular.
return input.Where(entityManager.HasComponent<TransformComponent>)
.SelectMany(e =>
entityLookup.GetEntitiesInRange(entityManager.GetComponent<TransformComponent>(e).Coordinates,
radius))
.Select(x => x) // Sloth's fault.
.Distinct();
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Globalization;
using System.Linq;
using System.Text;
@@ -114,17 +115,19 @@ namespace Robust.Server.Console.Commands
{
public string Command => "loadbp";
public string Description => "Loads a blueprint from disk into the game.";
public string Help => "loadbp <MapID> <Path> [storeUids]";
public string Help => "loadbp <MapID> <Path> [storeUids] [x y] [rotation]";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 2)
if (args.Length != 2 && args.Length != 3 && args.Length != 5 && args.Length != 6)
{
shell.WriteError("Must have either 2, 3, 5, or 6 arguments.");
return;
}
if (!int.TryParse(args[0], out var intMapId))
{
shell.WriteError($"{args[0]} is not a valid integer.");
return;
}
@@ -147,7 +150,41 @@ namespace Robust.Server.Console.Commands
var loadOptions = new MapLoadOptions();
if (args.Length > 2)
{
loadOptions.StoreMapUids = bool.Parse(args[2]);
if (!Boolean.TryParse(args[2], out var storeUids))
{
shell.WriteError($"{args[2]} is not a valid boolean..");
return;
}
loadOptions.StoreMapUids = storeUids;
}
if (args.Length >= 5)
{
if (!int.TryParse(args[3], out var x))
{
shell.WriteError($"{args[3]} is not a valid integer.");
return;
}
if (!int.TryParse(args[4], out var y))
{
shell.WriteError($"{args[4]} is not a valid integer.");
return;
}
loadOptions.Offset = new Vector2(x, y);
}
if (args.Length == 6)
{
if (!float.TryParse(args[5], out var rotation))
{
shell.WriteError($"{args[5]} is not a valid integer.");
return;
}
loadOptions.Rotation = new Angle(rotation);
}
var mapLoader = IoCManager.Resolve<IMapLoader>();
@@ -191,15 +228,20 @@ namespace Robust.Server.Console.Commands
{
public string Command => "loadmap";
public string Description => "Loads a map from disk into the game.";
public string Help => "loadmap <MapID> <Path>";
public string Help => "loadmap <MapID> <Path> [x y] [rotation]";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 1)
return;
if (args.Length != 2 && args.Length != 4 && args.Length != 5)
{
shell.WriteError($"Must have either 2, 4, or 5 arguments.");
}
if (!int.TryParse(args[0], out var intMapId))
{
shell.WriteError($"{args[0]} is not a valid integer.");
return;
}
var mapId = new MapId(intMapId);
@@ -217,7 +259,36 @@ namespace Robust.Server.Console.Commands
return;
}
IoCManager.Resolve<IMapLoader>().LoadMap(mapId, args[1]);
var loadOptions = new MapLoadOptions();
if (args.Length >= 3)
{
if (!int.TryParse(args[2], out var x))
{
shell.WriteError($"{args[2]} is not a valid integer.");
return;
}
if (!int.TryParse(args[3], out var y))
{
shell.WriteError($"{args[3]} is not a valid integer.");
return;
}
loadOptions.Offset = new Vector2(x, y);
}
if (args.Length == 4)
{
if (!float.TryParse(args[4], out var rotation))
{
shell.WriteError($"{args[4]} is not a valid integer.");
return;
}
loadOptions.Rotation = new Angle(rotation);
}
IoCManager.Resolve<IMapLoader>().LoadMap(mapId, args[1], loadOptions);
if (mapManager.MapExists(mapId))
shell.WriteLine($"Map {mapId} has been loaded from {args[1]}.");

View File

@@ -1,8 +1,8 @@
using System;
using Robust.Server.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
@@ -45,9 +45,12 @@ public sealed class ScaleCommand : IConsoleCommand
var entManager = IoCManager.Resolve<IEntityManager>();
entManager.EventBus.RaiseLocalEvent(uid, ref @event);
if (entManager.TryGetComponent(uid, out SpriteComponent? spriteComponent))
if (entManager.TryGetComponent(uid, out AppearanceComponent? appearanceComponent))
{
spriteComponent.Scale *= scale;
if (!appearanceComponent.TryGetData<Vector2>(ScaleVisuals.Scale, out var oldScale))
oldScale = Vector2.One;
appearanceComponent.SetData(ScaleVisuals.Scale, oldScale * scale);
}
if (entManager.TryGetComponent(uid, out FixturesComponent? manager))

View File

@@ -53,7 +53,7 @@ namespace Robust.Server.Console.Commands
{
if (args.Length != 2)
{
shell.WriteLine("Require 2 args for testbed!");
shell.WriteError("Require 2 args for testbed!");
return;
}
@@ -61,58 +61,67 @@ namespace Robust.Server.Console.Commands
if (!int.TryParse(args[0], out var mapInt))
{
shell.WriteLine($"Unable to parse map {args[0]}");
shell.WriteError($"Unable to parse map {args[0]}");
return;
}
var mapId = new MapId(mapInt);
if (!mapManager.MapExists(mapId))
{
shell.WriteLine("Unable to find map {mapId}");
return;
}
if (shell.Player == null)
{
shell.WriteLine("No player found");
shell.WriteError("No player found");
return;
}
var player = (IPlayerSession) shell.Player;
Action testbed;
SetupPlayer(mapId, shell, player, mapManager);
switch (args[1])
{
case "boxstack":
SetupPlayer(mapId, shell, player, mapManager);
CreateBoxStack(mapId);
testbed = () => CreateBoxStack(mapId);
break;
case "circlestack":
SetupPlayer(mapId, shell, player, mapManager);
CreateCircleStack(mapId);
testbed = () => CreateCircleStack(mapId);
break;
case "pyramid":
SetupPlayer(mapId, shell, player, mapManager);
CreatePyramid(mapId);
testbed = () => CreatePyramid(mapId);
break;
case "tumbler":
SetupPlayer(mapId, shell, player, mapManager);
CreateTumbler(mapId);
testbed = () => CreateTumbler(mapId);
break;
default:
shell.WriteLine($"testbed {args[0]} not found!");
shell.WriteError($"testbed {args[0]} not found!");
return;
}
Timer.Spawn(1000, () =>
{
if (!mapManager.MapExists(mapId)) return;
testbed();
});
shell.WriteLine($"Testbed on map {mapId}");
}
private void SetupPlayer(MapId mapId, IConsoleShell shell, IPlayerSession? player, IMapManager mapManager)
{
if (mapId == MapId.Nullspace) return;
if (!mapManager.MapExists(mapId))
{
mapManager.CreateMap(mapId);
}
mapManager.SetMapPaused(mapId, false);
var mapUid = mapManager.GetMapEntityIdOrThrow(mapId);
IoCManager.Resolve<IEntityManager>().GetComponent<SharedPhysicsMapComponent>(mapUid).Gravity = new Vector2(0, -9.8f);
shell.ExecuteCommand("aghost");
shell.ExecuteCommand($"tp 0 0 {mapId}");
shell.RemoteExecuteCommand($"physics shapes");
return;
}

View File

@@ -33,7 +33,7 @@ namespace Robust.Server.Console
if (!NetManager.IsConnected || session is null)
return;
var msg = NetManager.CreateNetMessage<MsgConCmd>();
var msg = new MsgConCmd();
msg.Text = command;
NetManager.ServerSendMessage(msg, ((IPlayerSession)session).ConnectedClient);
}
@@ -121,7 +121,7 @@ namespace Robust.Server.Console
private void HandleRegistrationRequest(INetChannel senderConnection)
{
var netMgr = IoCManager.Resolve<IServerNetManager>();
var message = netMgr.CreateNetMessage<MsgConCmdReg>();
var message = new MsgConCmdReg();
var counter = 0;
message.Commands = new MsgConCmdReg.Command[RegisteredCommands.Count];
@@ -154,7 +154,7 @@ namespace Robust.Server.Console
{
if (session != null)
{
var replyMsg = NetManager.CreateNetMessage<MsgConCmdAck>();
var replyMsg = new MsgConCmdAck();
replyMsg.Error = error;
replyMsg.Text = text;
NetManager.ServerSendMessage(replyMsg, session.ConnectedClient);

View File

@@ -3,7 +3,7 @@ using System.IO;
using System.IO.Compression;
using System.Threading;
using System.Threading.Tasks;
using ManagedHttpListener;
using SpaceWizards.HttpListener;
using Prometheus;
using Robust.Shared.Log;

View File

@@ -184,6 +184,7 @@ namespace Robust.Server.GameObjects
}
_subscribedSessions.Add(session);
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(Owner.Owner, new BoundUIOpenedEvent(UiKey, Owner.Owner, session));
SendMessage(new OpenBoundInterfaceMessage(), session);
if (_lastState != null)
{
@@ -236,11 +237,11 @@ namespace Robust.Server.GameObjects
public void CloseShared(IPlayerSession session)
{
var owner = Owner.Owner;
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(owner, new BoundUIClosedEvent(UiKey, owner, session));
OnClosed?.Invoke(session);
_subscribedSessions.Remove(session);
_playerStateOverrides.Remove(session);
session.PlayerStatusChanged -= OnSessionOnPlayerStatusChanged;
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(owner, new BoundUIClosedEvent(UiKey, owner, session));
if (_subscribedSessions.Count == 0)
{

View File

@@ -11,7 +11,7 @@ namespace Robust.Server.GameObjects
void FinishEntityLoad(EntityUid entity, IEntityLoadContext? context = null);
void FinishEntityInitialization(EntityUid entity);
void FinishEntityInitialization(EntityUid entity, MetaDataComponent? meta = null);
void FinishEntityStartup(EntityUid entity);
}

View File

@@ -52,9 +52,9 @@ namespace Robust.Server.GameObjects
LoadEntity(entity, context);
}
void IServerEntityManagerInternal.FinishEntityInitialization(EntityUid entity)
void IServerEntityManagerInternal.FinishEntityInitialization(EntityUid entity, MetaDataComponent? meta = null)
{
InitializeEntity(entity);
InitializeEntity(entity, meta);
}
void IServerEntityManagerInternal.FinishEntityStartup(EntityUid entity)
@@ -206,7 +206,7 @@ namespace Robust.Server.GameObjects
/// <inheritdoc />
public void SendSystemNetworkMessage(EntityEventArgs message)
{
var newMsg = _networkManager.CreateNetMessage<MsgEntity>();
var newMsg = new MsgEntity();
newMsg.Type = EntityMessageType.SystemMessage;
newMsg.SystemMessage = message;
newMsg.SourceTick = _gameTiming.CurTick;
@@ -217,7 +217,7 @@ namespace Robust.Server.GameObjects
/// <inheritdoc />
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel targetConnection)
{
var newMsg = _networkManager.CreateNetMessage<MsgEntity>();
var newMsg = new MsgEntity();
newMsg.Type = EntityMessageType.SystemMessage;
newMsg.SystemMessage = message;
newMsg.SourceTick = _gameTiming.CurTick;

View File

@@ -0,0 +1,41 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Players;
namespace Robust.Server.GameStates;
/// <summary>
/// Placeholder system to expose some parts of the internal <see cref="PVSSystem"/> that allows entities to ignore
/// normal PVS rules, such that they are always sent to clients.
/// </summary>
public sealed partial class PVSOverrideSystem : EntitySystem
{
[Shared.IoC.Dependency] private readonly PVSSystem _pvs = default!;
/// <summary>
/// Used to ensure that an entity is always sent to every client. Overrides any client-specific overrides.
/// </summary>
public void AddGlobalOverride(EntityUid uid)
{
_pvs.EntityPVSCollection.UpdateIndex(uid, true);
}
/// <summary>
/// Used to ensure that an entity is always sent to a specific client. Overrides any global or pre-existing
/// client-specific overrides.
/// </summary>
public void AddSessionOverride(EntityUid uid, ICommonSession session)
{
_pvs.EntityPVSCollection.UpdateIndex(uid, session, true);
}
/// <summary>
/// Removes any global or client-specific overrides.
/// </summary>
public void ClearOverride(EntityUid uid, TransformComponent? xform = null)
{
if (!Resolve(uid, ref xform))
return;
_pvs.EntityPVSCollection.UpdateIndex(uid, xform.Coordinates, true);
}
}

View File

@@ -7,9 +7,11 @@ using NetSerializer;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Collections;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Players;
@@ -29,6 +31,10 @@ internal sealed partial class PVSSystem : EntitySystem
[Shared.IoC.Dependency] private readonly INetConfigurationManager _netConfigManager = default!;
public const float ChunkSize = 8;
public const int TickBuffer = 10;
private static TransformComponentState _transformCullState =
new(Vector2.Zero, Angle.Zero, EntityUid.Invalid, false, false);
/// <summary>
/// Maximum number of pooled objects
@@ -54,11 +60,7 @@ internal sealed partial class PVSSystem : EntitySystem
/// <summary>
/// All <see cref="Robust.Shared.GameObjects.EntityUid"/>s a <see cref="ICommonSession"/> saw last iteration.
/// </summary>
private readonly Dictionary<ICommonSession, Dictionary<EntityUid, PVSEntityVisiblity>> _playerVisibleSets = new();
/// <summary>
/// All <see cref="Robust.Shared.GameObjects.EntityUid"/>s a <see cref="ICommonSession"/> saw along its entire connection.
/// </summary>
private readonly Dictionary<ICommonSession, HashSet<EntityUid>> _playerSeenSets = new();
private readonly Dictionary<ICommonSession, OverflowDictionary<GameTick, Dictionary<EntityUid, PVSEntityVisiblity>>> _playerVisibleSets = new();
private PVSCollection<EntityUid> _entityPvsCollection = default!;
public PVSCollection<EntityUid> EntityPVSCollection => _entityPvsCollection;
@@ -66,7 +68,7 @@ internal sealed partial class PVSSystem : EntitySystem
private readonly ObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>> _visSetPool
= new DefaultObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>>(
new DictPolicy<EntityUid, PVSEntityVisiblity>(), MaxVisPoolSize);
new DictPolicy<EntityUid, PVSEntityVisiblity>(), MaxVisPoolSize*TickBuffer);
private readonly ObjectPool<HashSet<EntityUid>> _uidSetPool
= new DefaultObjectPool<HashSet<EntityUid>>(new SetPolicy<EntityUid>(), MaxVisPoolSize);
@@ -83,11 +85,11 @@ internal sealed partial class PVSSystem : EntitySystem
private readonly ObjectPool<Dictionary<MapChunkLocation, int>> _mapChunkPool =
new DefaultObjectPool<Dictionary<MapChunkLocation, int>>(
new ChunkPoolPolicy<MapChunkLocation>(), 256);
new ChunkPoolPolicy<MapChunkLocation>(), MaxVisPoolSize);
private readonly ObjectPool<Dictionary<GridChunkLocation, int>> _gridChunkPool =
new DefaultObjectPool<Dictionary<GridChunkLocation, int>>(
new ChunkPoolPolicy<GridChunkLocation>(), 256);
new ChunkPoolPolicy<GridChunkLocation>(), MaxVisPoolSize);
private readonly Dictionary<uint, Dictionary<MapChunkLocation, int>> _mapIndices = new(4);
private readonly Dictionary<uint, Dictionary<GridChunkLocation, int>> _gridIndices = new(4);
@@ -114,7 +116,7 @@ internal sealed partial class PVSSystem : EntitySystem
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
SubscribeLocalEvent<MoveEvent>(OnEntityMove);
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChange);
SubscribeLocalEvent<TransformComponent, ComponentStartup>(OnTransformStartup);
SubscribeLocalEvent<TransformComponent, TransformStartupEvent>(OnTransformStartup);
EntityManager.EntityDeleted += OnEntityDeleted;
_configManager.OnValueChanged(CVars.NetPVS, SetPvs, true);
@@ -220,15 +222,15 @@ internal sealed partial class PVSSystem : EntitySystem
private void OnEntityMove(ref MoveEvent ev)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var coordinates = _transform.GetMoverCoordinates(ev.Component);
UpdateEntityRecursive(ev.Sender, ev.Component, coordinates, xformQuery, false);
}
private void OnTransformStartup(EntityUid uid, TransformComponent component, ComponentStartup args)
private void OnTransformStartup(EntityUid uid, TransformComponent component, ref TransformStartupEvent args)
{
// use Startup because GridId is not set during the eventbus init yet!
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var coordinates = _transform.GetMoverCoordinates(component);
UpdateEntityRecursive(uid, component, coordinates, xformQuery, false);
}
@@ -257,8 +259,7 @@ internal sealed partial class PVSSystem : EntitySystem
{
if (e.NewStatus == SessionStatus.InGame)
{
_playerVisibleSets.Add(e.Session, _visSetPool.Get());
_playerSeenSets.Add(e.Session, new HashSet<EntityUid>());
_playerVisibleSets.Add(e.Session, new OverflowDictionary<GameTick, Dictionary<EntityUid, PVSEntityVisiblity>>(TickBuffer, _visSetPool.Return));
foreach (var pvsCollection in _pvsCollections)
{
pvsCollection.AddPlayer(e.Session);
@@ -266,10 +267,12 @@ internal sealed partial class PVSSystem : EntitySystem
}
else if (e.NewStatus == SessionStatus.Disconnected)
{
var playerVisSet = _playerVisibleSets[e.Session];
var overflowDict = _playerVisibleSets[e.Session];
_playerVisibleSets.Remove(e.Session);
_visSetPool.Return(playerVisSet);
_playerSeenSets.Remove(e.Session);
foreach (var (_, playerVisSet) in overflowDict)
{
_visSetPool.Return(playerVisSet);
}
foreach (var pvsCollection in _pvsCollections)
{
pvsCollection.RemovePlayer(e.Session);
@@ -352,6 +355,8 @@ internal sealed partial class PVSSystem : EntitySystem
{
var (viewPos, range, mapId) = CalcViewBounds(in eyeEuid, transformQuery);
if(mapId == MapId.Nullspace) continue;
uint visMask = EyeComponent.DefaultVisibilityMask;
if (eyeQuery.TryGetComponent(eyeEuid, out var eyeComp))
visMask = eyeComp.VisibilityMask;
@@ -544,7 +549,7 @@ internal sealed partial class PVSSystem : EntitySystem
!AddToChunkSetRecursively(in parent, visMask, tree, set, transform, metadata)) //did we just fail to add the parent?
return false; //we failed? suppose we dont get added either
//todo paul i want it to crash here if it gets added double bc that shouldnt happen and will add alot of unneeded cycles, make this a simpl assignment at some point maybe idk
//i want it to crash here if it gets added double bc that shouldnt happen and will add alot of unneeded cycles
tree.Set(uid, parent);
set.Add(uid, mComp);
return true;
@@ -557,13 +562,10 @@ internal sealed partial class PVSSystem : EntitySystem
EntityUid[] viewerEntities)
{
DebugTools.Assert(session.Status == SessionStatus.InGame);
var newEntityBudget = _netConfigManager.GetClientCVar(session.ConnectedClient, CVars.NetPVSNewEntityBudget);
var enteredEntityBudget = _netConfigManager.GetClientCVar(session.ConnectedClient, CVars.NetPVSEntityBudget);
var newEntitiesSent = 0;
var entitiesSent = 0;
var playerVisibleSet = _playerVisibleSets[session];
_playerVisibleSets[session].TryGetValue(fromTick, out var playerVisibleSet);
var visibleEnts = _visSetPool.Get();
var seenSet = _playerSeenSets[session];
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
foreach (var i in chunkIndices)
@@ -572,8 +574,8 @@ internal sealed partial class PVSSystem : EntitySystem
if(!cache.HasValue) continue;
foreach (var rootNode in cache.Value.tree.RootNodes)
{
RecursivelyAddTreeNode(in rootNode, cache.Value.tree, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
ref entitiesSent, cache.Value.metadata, in enteredEntityBudget, in newEntityBudget);
RecursivelyAddTreeNode(in rootNode, cache.Value.tree, playerVisibleSet, visibleEnts, fromTick,
ref entitiesSent, cache.Value.metadata, in enteredEntityBudget);
}
}
@@ -581,8 +583,8 @@ internal sealed partial class PVSSystem : EntitySystem
while (globalEnumerator.MoveNext())
{
var uid = globalEnumerator.Current;
RecursivelyAddOverride(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget, in newEntityBudget);
RecursivelyAddOverride(in uid, playerVisibleSet, visibleEnts, fromTick,
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget);
}
globalEnumerator.Dispose();
@@ -590,15 +592,23 @@ internal sealed partial class PVSSystem : EntitySystem
while (localEnumerator.MoveNext())
{
var uid = localEnumerator.Current;
RecursivelyAddOverride(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget, in newEntityBudget);
RecursivelyAddOverride(in uid, playerVisibleSet, visibleEnts, fromTick,
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget);
}
localEnumerator.Dispose();
foreach (var viewerEntity in viewerEntities)
{
RecursivelyAddOverride(in viewerEntity, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget, in newEntityBudget);
RecursivelyAddOverride(in viewerEntity, playerVisibleSet, visibleEnts, fromTick,
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget);
}
var expandEvent = new ExpandPvsEvent(session, new List<EntityUid>());
RaiseLocalEvent(ref expandEvent);
foreach (var entityUid in expandEvent.Entities)
{
RecursivelyAddOverride(in entityUid, playerVisibleSet, visibleEnts, fromTick,
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget);
}
var entityStates = new List<EntityState>();
@@ -617,24 +627,26 @@ internal sealed partial class PVSSystem : EntitySystem
entityStates.Add(state);
}
foreach (var (entityUid, _) in playerVisibleSet)
if(playerVisibleSet != null)
{
// it was deleted, so we dont need to exit pvs
if(deletions.Contains(entityUid)) continue;
//TODO: HACK: somehow an entity left the view, transform does not exist (deleted?), but was not in the
// deleted list. This seems to happen with the map entity on round restart.
if (!EntityManager.EntityExists(entityUid))
continue;
entityStates.Add(new EntityState(entityUid, new NetListAsArray<ComponentChange>(new []
foreach (var (entityUid, _) in playerVisibleSet)
{
ComponentChange.Changed(_stateManager.TransformNetId, new TransformComponent.TransformComponentState(Vector2.Zero, Angle.Zero, EntityUid.Invalid, false, false)),
}), true));
// it was deleted, so we dont need to exit pvs
if (deletions.Contains(entityUid)) continue;
//TODO: HACK: somehow an entity left the view, transform does not exist (deleted?), but was not in the
// deleted list. This seems to happen with the map entity on round restart.
if (!EntityManager.EntityExists(entityUid))
continue;
entityStates.Add(new EntityState(entityUid, new NetListAsArray<ComponentChange>(new[]
{
ComponentChange.Changed(_stateManager.TransformNetId, _transformCullState),
}), true));
}
}
_playerVisibleSets[session] = visibleEnts;
_visSetPool.Return(playerVisibleSet);
_playerVisibleSets[session].Add(toTick, visibleEnts);
if (deletions.Count == 0) deletions = default;
if (entityStates.Count == 0) entityStates = default;
@@ -642,18 +654,14 @@ internal sealed partial class PVSSystem : EntitySystem
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private void RecursivelyAddTreeNode(
in EntityUid nodeIndex,
private void RecursivelyAddTreeNode(in EntityUid nodeIndex,
RobustTree<EntityUid> tree,
HashSet<EntityUid> seenSet,
Dictionary<EntityUid, PVSEntityVisiblity> previousVisibleEnts,
Dictionary<EntityUid, PVSEntityVisiblity>? previousVisibleEnts,
Dictionary<EntityUid, PVSEntityVisiblity> toSend,
GameTick fromTick,
ref int newEntitiesSent,
ref int totalEnteredEntities,
Dictionary<EntityUid, MetaDataComponent> metaDataCache,
in int enteredEntityBudget,
in int newEntityBudget)
in int enteredEntityBudget)
{
//are we valid?
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
@@ -664,8 +672,8 @@ internal sealed partial class PVSSystem : EntitySystem
if (nodeIndex.IsValid() && !toSend.ContainsKey(nodeIndex))
{
//are we new?
var (entered, budgetFail) = ProcessEntry(in nodeIndex, seenSet, previousVisibleEnts, ref newEntitiesSent,
ref totalEnteredEntities, in enteredEntityBudget, in newEntityBudget);
var (entered, budgetFail) = ProcessEntry(in nodeIndex, previousVisibleEnts,
ref totalEnteredEntities, in enteredEntityBudget);
if (budgetFail) return;
@@ -678,24 +686,21 @@ internal sealed partial class PVSSystem : EntitySystem
{
foreach (var child in node.Children)
{
RecursivelyAddTreeNode(in child, tree, seenSet, previousVisibleEnts, toSend, fromTick, ref newEntitiesSent,
ref totalEnteredEntities, metaDataCache, in enteredEntityBudget, in newEntityBudget);
RecursivelyAddTreeNode(in child, tree, previousVisibleEnts, toSend, fromTick,
ref totalEnteredEntities, metaDataCache, in enteredEntityBudget);
}
}
}
public bool RecursivelyAddOverride(
in EntityUid uid,
HashSet<EntityUid> seenSet,
Dictionary<EntityUid, PVSEntityVisiblity> previousVisibleEnts,
Dictionary<EntityUid, PVSEntityVisiblity>? previousVisibleEnts,
Dictionary<EntityUid, PVSEntityVisiblity> toSend,
GameTick fromTick,
ref int newEntitiesSent,
ref int totalEnteredEntities,
EntityQuery<MetaDataComponent> metaQuery,
EntityQuery<TransformComponent> transQuery,
in int enteredEntityBudget,
in int newEntityBudget)
in int enteredEntityBudget)
{
//are we valid?
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
@@ -705,24 +710,22 @@ internal sealed partial class PVSSystem : EntitySystem
if (toSend.ContainsKey(uid)) return true;
var parent = transQuery.GetComponent(uid).ParentUid;
if (parent.IsValid() && !RecursivelyAddOverride(in parent, seenSet, previousVisibleEnts, toSend, fromTick,
ref newEntitiesSent, ref totalEnteredEntities, metaQuery, transQuery, in enteredEntityBudget, in newEntityBudget))
if (parent.IsValid() && !RecursivelyAddOverride(in parent, previousVisibleEnts, toSend, fromTick,
ref totalEnteredEntities, metaQuery, transQuery, in enteredEntityBudget))
return false;
var (entered, _) = ProcessEntry(in uid, seenSet, previousVisibleEnts, ref newEntitiesSent,
ref totalEnteredEntities, in enteredEntityBudget, in newEntityBudget);
var (entered, _) = ProcessEntry(in uid, previousVisibleEnts,
ref totalEnteredEntities, in enteredEntityBudget);
AddToSendSet(in uid, metaQuery.GetComponent(uid), toSend, fromTick, entered);
return true;
}
private (bool entered, bool budgetFail) ProcessEntry(in EntityUid uid, HashSet<EntityUid> seenSet,
Dictionary<EntityUid, PVSEntityVisiblity> previousVisibleEnts,
ref int newEntitiesSent,
ref int totalEnteredEntities, in int enteredEntityBudget, in int newEntityBudget)
private (bool entered, bool budgetFail) ProcessEntry(in EntityUid uid,
Dictionary<EntityUid, PVSEntityVisiblity>? previousVisibleEnts,
ref int totalEnteredEntities, in int enteredEntityBudget)
{
var @new = !seenSet.Contains(uid);
var entered = @new | !previousVisibleEnts.Remove(uid);
var entered = previousVisibleEnts?.Remove(uid) == false;
if (entered)
{
@@ -732,16 +735,6 @@ internal sealed partial class PVSSystem : EntitySystem
totalEnteredEntities++;
}
if (@new)
{
//we just entered pvs, do we still have enough budget to send us?
if(newEntitiesSent >= newEntityBudget)
return (entered, true);
newEntitiesSent++;
seenSet.Add(uid);
}
return (entered, false);
}

View File

@@ -1,15 +1,14 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.ObjectPool;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
@@ -18,8 +17,11 @@ using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Threading;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using SharpZstd.Interop;
using Microsoft.Extensions.ObjectPool;
namespace Robust.Server.GameStates
{
@@ -40,9 +42,13 @@ namespace Robust.Server.GameStates
[Dependency] private readonly INetworkedMapManager _mapManager = default!;
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly IServerEntityNetworkManager _entityNetworkManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IParallelManager _parallelMgr = default!;
private ISawmill _logger = default!;
private DefaultObjectPool<PvsThreadResources> _threadResourcesPool = default!;
public ushort TransformNetId { get; set; }
public void PostInject()
@@ -60,6 +66,54 @@ namespace Robust.Server.GameStates
_networkManager.Disconnect += HandleClientDisconnect;
_pvs = EntitySystem.Get<PVSSystem>();
_parallelMgr.AddAndInvokeParallelCountChanged(ResetParallelism);
_cfg.OnValueChanged(CVars.NetPVSCompressLevel, _ => ResetParallelism(), true);
}
private void ResetParallelism()
{
var compressLevel = _cfg.GetCVar(CVars.NetPVSCompressLevel);
// The * 2 is because trusting .NET won't take more is what got this code into this mess in the first place.
_threadResourcesPool = new DefaultObjectPool<PvsThreadResources>(new PvsThreadResourcesObjectPolicy(compressLevel), _parallelMgr.ParallelProcessCount * 2);
}
private sealed class PvsThreadResourcesObjectPolicy : IPooledObjectPolicy<PvsThreadResources>
{
public int CompressionLevel;
public PvsThreadResourcesObjectPolicy(int ce)
{
CompressionLevel = ce;
}
PvsThreadResources IPooledObjectPolicy<PvsThreadResources>.Create()
{
var res = new PvsThreadResources();
res.CompressionContext.SetParameter(ZSTD_cParameter.ZSTD_c_compressionLevel, CompressionLevel);
return res;
}
bool IPooledObjectPolicy<PvsThreadResources>.Return(PvsThreadResources _)
{
return true;
}
}
private sealed class PvsThreadResources
{
public ZStdCompressionContext CompressionContext;
public PvsThreadResources()
{
CompressionContext = new ZStdCompressionContext();
}
~PvsThreadResources()
{
CompressionContext.Dispose();
}
}
private void HandleClientConnected(object? sender, NetChannelArgs e)
@@ -142,7 +196,7 @@ namespace Robust.Server.GameStates
(chunks, playerChunks, viewerEntities) = _pvs.GetChunks(players);
const int ChunkBatchSize = 2;
var chunksCount = chunks.Count;
var chunkBatches = (int) MathF.Ceiling((float) chunksCount / ChunkBatchSize);
var chunkBatches = (int)MathF.Ceiling((float)chunksCount / ChunkBatchSize);
chunkCache =
new (Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[chunksCount];
@@ -159,7 +213,8 @@ namespace Robust.Server.GameStates
for (var j = start; j < end; ++j)
{
var (visMask, chunkIndexLocation) = chunks[j];
reuse[j] = _pvs.TryCalculateChunk(chunkIndexLocation, visMask, transformQuery, metadataQuery, out var chunk);
reuse[j] = _pvs.TryCalculateChunk(chunkIndexLocation, visMask, transformQuery, metadataQuery,
out var chunk);
chunkCache[j] = chunk;
}
});
@@ -168,33 +223,31 @@ namespace Robust.Server.GameStates
ArrayPool<bool>.Shared.Return(reuse);
}
const int BatchSize = 2;
var batches = (int) MathF.Ceiling((float) players.Length / BatchSize);
Parallel.For(0, batches, i =>
{
var start = i * BatchSize;
var end = Math.Min(start + BatchSize, players.Length);
for (var j = start; j < end; ++j)
Parallel.For(
0, players.Length,
new ParallelOptions { MaxDegreeOfParallelism = _parallelMgr.ParallelProcessCount },
() => _threadResourcesPool.Get(),
(i, loop, resource) =>
{
try
{
SendStateUpdate(j);
SendStateUpdate(i, resource);
}
catch (Exception e) // Catch EVERY exception
{
_logger.Log(LogLevel.Error, e, "Caught exception while generating mail.");
}
}
});
return resource;
},
resource => _threadResourcesPool.Return(resource)
);
void SendStateUpdate(int sessionIndex)
void SendStateUpdate(int sessionIndex, PvsThreadResources resources)
{
var session = players[sessionIndex];
// KILL IT WITH FIRE
if(mainThread != Thread.CurrentThread)
if (mainThread != Thread.CurrentThread)
IoCManager.InitThread(new DependencyCollection(parentDeps), true);
var channel = session.ConnectedClient;
@@ -214,13 +267,15 @@ namespace Robust.Server.GameStates
// lastAck varies with each client based on lag and such, we can't just make 1 global state and send it to everyone
var lastInputCommand = inputSystem.GetLastInputCommand(session);
var lastSystemMessage = _entityNetworkManager.GetLastMessageSequence(session);
var state = new GameState(lastAck, _gameTiming.CurTick, Math.Max(lastInputCommand, lastSystemMessage), entStates, playerStates, deletions, mapData);
var state = new GameState(lastAck, _gameTiming.CurTick, Math.Max(lastInputCommand, lastSystemMessage),
entStates, playerStates, deletions, mapData);
InterlockedHelper.Min(ref oldestAckValue, lastAck.Value);
// actually send the state
var stateUpdateMessage = _networkManager.CreateNetMessage<MsgState>();
var stateUpdateMessage = new MsgState();
stateUpdateMessage.State = state;
stateUpdateMessage.CompressionContext = resources.CompressionContext;
// If the state is too big we let Lidgren send it reliably.
// This is to avoid a situation where a state is so large that it consistently gets dropped
@@ -238,7 +293,7 @@ namespace Robust.Server.GameStates
_networkManager.ServerSendMessage(stateUpdateMessage, channel);
}
if(_pvs.CullingEnabled)
if (_pvs.CullingEnabled)
_pvs.ReturnToPool(playerChunks);
_pvs.Cleanup(_playerManager.ServerSessions);
var oldestAck = new GameTick(oldestAckValue);

View File

@@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Server.Maps
{
[TypeSerializer]
internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingDataNode>
{
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)
{
throw new NotImplementedException();
}
public MapChunk Read(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, MapChunk? chunk = null)
{
var tileNode = (ValueDataNode)node["tiles"];
var tileBytes = Convert.FromBase64String(tileNode.Value);
using var stream = new MemoryStream(tileBytes);
using var reader = new BinaryReader(stream);
var mapManager = dependencies.Resolve<IMapManager>();
mapManager.SuppressOnTileChanged = true;
if (chunk == null)
{
throw new InvalidOperationException(
$"Someone tried deserializing a gridchunk without passing a value.");
}
if (context is not MapLoader.MapContext mapContext)
{
throw new InvalidOperationException(
$"Someone tried serializing a gridchunk without passing {nameof(MapLoader.MapContext)} as context.");
}
var tileMap = mapContext.TileMap;
if (tileMap == null)
{
throw new InvalidOperationException(
$"Someone tried deserializing a gridchunk before deserializing the tileMap.");
}
chunk.SuppressCollisionRegeneration = true;
var tileDefinitionManager = dependencies.Resolve<ITileDefinitionManager>();
for (ushort y = 0; y < chunk.ChunkSize; y++)
{
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
var id = reader.ReadUInt16();
var flags = (TileRenderFlag)reader.ReadByte();
var variant = reader.ReadByte();
var defName = tileMap[id];
id = tileDefinitionManager[defName].TileId;
var tile = new Tile(id, flags, variant);
chunk.SetTile(x, y, tile);
}
}
chunk.SuppressCollisionRegeneration = false;
mapManager.SuppressOnTileChanged = false;
return chunk;
}
public DataNode Write(ISerializationManager serializationManager, MapChunk value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
var root = new MappingDataNode();
var ind = new ValueDataNode($"{value.X},{value.Y}");
root.Add("ind", ind);
var gridNode = new ValueDataNode();
root.Add("tiles", gridNode);
gridNode.Value = SerializeTiles(value);
return root;
}
private static string SerializeTiles(MapChunk chunk)
{
// number of bytes written per tile, because sizeof(Tile) is useless.
const int structSize = 4;
var nTiles = chunk.ChunkSize * chunk.ChunkSize * structSize;
var barr = new byte[nTiles];
using (var stream = new MemoryStream(barr))
using (var writer = new BinaryWriter(stream))
{
for (ushort y = 0; y < chunk.ChunkSize; y++)
{
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
var tile = chunk.GetTile(x, y);
writer.Write(tile.TypeId);
writer.Write((byte)tile.Flags);
writer.Write(tile.Variant);
}
}
}
return Convert.ToBase64String(barr);
}
public MapChunk Copy(ISerializationManager serializationManager, MapChunk source, MapChunk target, bool skipHook,
ISerializationContext? context = null)
{
throw new NotImplementedException();
}
}
//todo paul make this be used
[TypeSerializer]
internal sealed class GridSerializer : ITypeSerializer<MapGrid, MappingDataNode>
{
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)
{
throw new NotImplementedException();
}
public MapGrid Read(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, MapGrid? grid = null)
{
var info = node.Get<MappingDataNode>("settings");
var chunks = node.Get<SequenceDataNode>("chunks");
ushort csz = 0;
ushort tsz = 0;
float sgsz = 0.0f;
foreach (var kvInfo in info.Cast<KeyValuePair<ValueDataNode, ValueDataNode>>())
{
var key = kvInfo.Key.Value;
var val = kvInfo.Value.Value;
if (key == "chunksize")
csz = ushort.Parse(val);
else if (key == "tilesize")
tsz = ushort.Parse(val);
else if (key == "snapsize")
sgsz = float.Parse(val, CultureInfo.InvariantCulture);
}
//TODO: Pass in options
if (context is not MapLoader.MapContext mapContext)
{
throw new InvalidOperationException(
$"Someone tried serializing a mapgrid without passing {nameof(MapLoader.MapContext)} as context.");
}
if (grid == null) throw new NotImplementedException();
//todo paul grid ??= dependencies.Resolve<MapManager>().CreateUnboundGrid(mapContext.TargetMap);
foreach (var chunkNode in chunks.Cast<MappingDataNode>())
{
var (chunkOffsetX, chunkOffsetY) =
serializationManager.Read<Vector2i>(chunkNode["ind"], context, skipHook);
var chunk = grid.GetChunk(chunkOffsetX, chunkOffsetY);
serializationManager.Read(typeof(MapChunkSerializer), chunkNode, context, skipHook, chunk);
}
return grid;
}
public DataNode Write(ISerializationManager serializationManager, MapGrid value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
var gridn = new MappingDataNode();
var info = new MappingDataNode();
var chunkSeq = new SequenceDataNode();
gridn.Add("settings", info);
gridn.Add("chunks", chunkSeq);
info.Add("chunksize", value.ChunkSize.ToString(CultureInfo.InvariantCulture));
info.Add("tilesize", value.TileSize.ToString(CultureInfo.InvariantCulture));
var chunks = value.GetMapChunks();
foreach (var chunk in chunks)
{
var chunkNode = serializationManager.WriteValue(chunk.Value);
chunkSeq.Add(chunkNode);
}
return gridn;
}
public MapGrid Copy(ISerializationManager serializationManager, MapGrid source, MapGrid target, bool skipHook,
ISerializationContext? context = null)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using YamlDotNet.RepresentationModel;
@@ -6,12 +8,12 @@ namespace Robust.Server.Maps
{
public interface IMapLoader
{
IMapGrid? LoadBlueprint(MapId mapId, string path);
IMapGrid? LoadBlueprint(MapId mapId, string path, MapLoadOptions options);
(IReadOnlyList<EntityUid> entities, GridId? gridId) LoadBlueprint(MapId mapId, string path);
(IReadOnlyList<EntityUid> entities, GridId? gridId) LoadBlueprint(MapId mapId, string path, MapLoadOptions options);
void SaveBlueprint(GridId gridId, string yamlPath);
void LoadMap(MapId mapId, string path);
void LoadMap(MapId mapId, string path, MapLoadOptions options);
(IReadOnlyList<EntityUid> entities, IReadOnlyList<GridId> gridIds) LoadMap(MapId mapId, string path);
(IReadOnlyList<EntityUid> entities, IReadOnlyList<GridId> gridIds) LoadMap(MapId mapId, string path, MapLoadOptions options);
void SaveMap(MapId mapId, string yamlPath);
event Action<YamlStream, string> LoadedMapData;

View File

@@ -1,5 +1,9 @@
namespace Robust.Server.Maps
using JetBrains.Annotations;
using Robust.Shared.Maths;
namespace Robust.Server.Maps
{
[PublicAPI]
public sealed class MapLoadOptions
{
/// <summary>
@@ -7,5 +11,38 @@
/// to maintain consistency upon subsequent savings.
/// </summary>
public bool StoreMapUids { get; set; }
/// <summary>
/// Offset to apply to the loaded objects.
/// </summary>
public Vector2 Offset
{
get => _offset;
set
{
TransformMatrix = Matrix3.CreateTransform(value, Rotation);
_offset = value;
}
}
private Vector2 _offset = Vector2.Zero;
/// <summary>
/// Rotation to apply to the loaded objects as a collective, around 0, 0.
/// </summary>
/// <remarks>Setting this overrides <</remarks>
public Angle Rotation
{
get => _rotation;
set
{
TransformMatrix = Matrix3.CreateTransform(Offset, value);
_rotation = value;
}
}
private Angle _rotation = Angle.Zero;
public Matrix3 TransformMatrix { get; set; } = Matrix3.Identity;
}
}

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