Compare commits

...

441 Commits

Author SHA1 Message Date
metalgearsloth
c4787b63a8 Version: 0.19.2.0 2022-06-02 21:02:43 +10:00
metalgearsloth
6b305349c9 Fix grid deletion crashing (#2899)
* Fix grid deletion crashing

* polite

* a

* excellent
2022-06-02 21:01:56 +10:00
Alex Evgrashin
3c16779d68 Add helper function in entity prototype (#2868) 2022-06-01 17:28:47 +02:00
Pieter-Jan Briers
38fcadf649 Remove allocations from physics ComputeDistance() (#2896) 2022-06-02 00:52:43 +10:00
metalgearsloth
6cc50013be Version: 0.19.1.3 2022-06-01 22:46:28 +10:00
metalgearsloth
bb7c9ad7fa Fix grid fixtures logger woopsy (#2895) 2022-06-01 22:36:50 +10:00
metalgearsloth
b53d838b14 Version: 0.19.1.2 2022-06-01 20:01:16 +10:00
metalgearsloth
e03831586e Physics tweaks (#2890)
* Tweak awake assetr slightly

It will still log but game shouldn't blow up anymore.

* b

* a

a
2022-06-01 13:13:15 +10:00
metalgearsloth
6acb8f8b1b Add logs to grid splitting (#2888) 2022-05-31 19:18:49 +10:00
metalgearsloth
29bf2cbbc4 Version: 0.19.1.1 2022-05-31 17:53:48 +10:00
metalgearsloth
4fc238864b Update circle solver to box2d's latest (#2883) 2022-05-30 15:38:20 +10:00
Leon Friedrich
cb8d2727f4 Undo all angle lerp changes (#2884) 2022-05-30 15:38:09 +10:00
metalgearsloth
9ef433d3ed Version: 0.19.1.0 2022-05-29 16:24:48 +10:00
Pieter-Jan Briers
02de3cd437 Fix tests with concrete EntityManager 2022-05-29 01:15:52 +02:00
Pieter-Jan Briers
e7211063e7 Remove unecessary Vertex2D.SizeOf 2022-05-29 01:13:12 +02:00
Leon Friedrich
e32ee1a58e Reuse post-shader render target (#2882) 2022-05-29 01:02:19 +02:00
Pieter-Jan Briers
1eb5859f01 Expose concrete EntityManager on IoC, use it in EntitySystem.
REMOVE VIRTUAL DISPATCH.
2022-05-29 01:01:50 +02:00
Pieter-Jan Briers
ff7c3dc45c Do FramePreEngine and FramePostEngine on server 2022-05-29 00:39:54 +02:00
Pieter-Jan Briers
655b5974a3 Move sRGB -> linear conversion for vertex color to vertex shader.
This took almost 2% of CPU time on release. Jeez.
2022-05-28 23:18:05 +02:00
Leon Friedrich
47d7c7cf6c Revert ShortestDistance changes (#2881) 2022-05-29 04:03:31 +10:00
wrexbe
1c39deae6c Fix github action test output spam (#2847) 2022-05-28 16:15:37 +02:00
Pieter-Jan Briers
6c4dc7fc72 Version: 0.19.0.0 2022-05-28 15:36:59 +02:00
Pieter-Jan Briers
3c5618364c Move ValueList<T> to Robust.Shared.Collections 2022-05-28 15:34:46 +02:00
Pieter-Jan Briers
bf1cc8fd7c Minor performance improvements (#2860) 2022-05-28 15:28:08 +02:00
20kdc
72d4d50788 Tile aliases (#2840) 2022-05-28 14:59:58 +02:00
Leon Friedrich
6a86fcdeeb Fix WorldSpace overlay drawing (#2878) 2022-05-28 14:52:01 +02:00
Pieter-Jan Briers
dad95ad73a Fix KeepAspectCovered 2022-05-28 14:46:15 +02:00
20kdc
77f915acf5 Strip Lidgren "Disconnected: " prefix when a connection fails because of interruption by a disconnect with a structured message. (#2880)
Note that this otherwise retains the prefix, which may be important for debugging.
2022-05-28 14:37:17 +02:00
Pieter-Jan Briers
6fc1daf978 Some compiler warning fixes. 2022-05-28 13:36:35 +02:00
Pieter-Jan Briers
934f589778 Put Control FrameUpdate() before style & layout. (#2877) 2022-05-28 13:09:19 +02:00
Clyybber
305ea97d5b Limit audio raycast length (#2851)
* Limit audio raycast length

* Add MidiManager.MaxDistanceForCulling and cull based on MaxDistance.
2022-05-28 13:07:36 +02:00
Pieter-Jan Briers
e3b26e80bf Sync CurTime base. (#2858) 2022-05-28 13:03:42 +02:00
Leon Friedrich
810ef119b0 Remove "A:" log (#2879) 2022-05-28 11:10:41 +02:00
20kdc
393b27679d ACZ general speedups (#2873)
* Cleave the ACZ code into two parts

* SSAZip - faster-performing than ZipArchive, very small codebase

* OnDemandFile to reduce RAM usage of SSAZip

* Modify OnDemandFile to be more amiable for replacing parts of ACManifest

* Make the ACZ Manifest code a lot less reliant on an "internal zip file" for memory usage reasons

* Finally allow disabling legacy client zip creation entirely

* ZIP is now only an ingestion format for ACZ, not actually served anymore

* We do a little renaming

* We do a little namespace adjustment

* We do a little ABOMINATION OF FILE MERGING
2022-05-27 20:52:38 +02:00
Pieter-Jan Briers
a592134caf run XamlIL with internal MSBuild by default.
This only needs to be external when developing XamlIL itself, and I assume doing it external worsens build perf somewhat.
2022-05-27 17:42:27 +02:00
wrexbe
2389df7c6f Ignore missing components on the client (#2846) 2022-05-27 15:30:22 +02:00
Leon Friedrich
422202348e Change SplitContainer MeasureOverride (#2874) 2022-05-27 13:31:54 +02:00
metalgearsloth
284e08ffd9 Version: 0.18.1.3 2022-05-27 10:18:30 +10:00
Leon Friedrich
2ce3d04bba Remove EntitySystem.Get from SpriteView.DrawInternal() (#2867) 2022-05-27 10:18:01 +10:00
Acruid
8e67bfe990 Backwards compatible API additions to support removing GridId from content. (#2855) 2022-05-27 10:17:05 +10:00
Leon Friedrich
27dc9510a3 Remove ambiguity in sprite specifier (#2844) 2022-05-27 10:16:08 +10:00
Leon Friedrich
d4708cb144 Make ignored component errors more explicit (#2875) 2022-05-27 10:15:44 +10:00
Pieter-Jan Briers
986de32043 Don't try to parse JSON structured disconnects if it doesn't look like JSON.
Avoid my debugger getting caught on the exception.
2022-05-27 00:03:39 +02:00
Kara
4e90c291b3 Make ShortestDistance not reduce angle (#2869) 2022-05-25 21:55:31 -07:00
metalgearsloth
7775767687 Version: 0.18.1.2 2022-05-26 14:29:12 +10:00
Leon Friedrich
5624476e2b Reset last modified tick (#2871) 2022-05-26 14:28:37 +10:00
Leon Friedrich
9ef80864b7 Fix completion divide by zero (#2866) 2022-05-26 04:38:08 +10:00
Clyybber
c32ef136a1 Fix completion crashing on a invalid path not starting with '/' (#2861) 2022-05-25 13:39:31 +10:00
metalgearsloth
b2ea0fe1e2 Version: 0.18.1.1 2022-05-25 00:56:51 +10:00
metalgearsloth
be86b12b14 Fix collisionwake on spawn (#2859)
* Fix collisionwake on spawn

Stuff that was immediately slept wasn't checked correctly.

* Testy
2022-05-25 00:55:11 +10:00
Pieter-Jan Briers
8b8e499615 Avoid TLS lookup in IoCManager.Resolve<T>(ref T) if instance provided. 2022-05-23 23:33:01 +02:00
metalgearsloth
36b0d38b70 Version: 0.18.1.0 2022-05-22 18:56:53 +10:00
Kara
78ef6921da Fix two angle lerping bugs (#2850) 2022-05-22 18:56:16 +10:00
metalgearsloth
f6ab67e92c Fix audio being occluded by non-hard fixtures (#2849) 2022-05-22 18:32:32 +10:00
Vera Aguilera Puerto
44ffdcf429 Also look for "default.dls" soundfonts on Linux.
Technically, it still opened them fine if they were called "default.sf2" but having the correct extension is better, honestly.
2022-05-20 21:55:26 +02:00
Vera Aguilera Puerto
53f4d3fab6 Allow soundfonts shipped by Content to override system soundfonts.
If Content wants to override the system soundfont, it should be able to.
2022-05-20 21:41:12 +02:00
metalgearsloth
7dcf543443 Version: 0.18.0.1 2022-05-20 18:30:09 +10:00
metalgearsloth
4e445ee313 Physics command niceties (#2843) 2022-05-20 14:17:20 +10:00
metalgearsloth
c6be3e145a Fix help command crash (#2841) 2022-05-19 18:18:33 +10:00
Pieter-Jan Briers
f90526be22 Bump ANGLE, add ES3OnFL10_0 support (#2834) 2022-05-19 07:55:36 +10:00
metalgearsloth
df09bc2cf0 Version: 0.18.0.0 2022-05-19 07:52:46 +10:00
metalgearsloth
a1affcfd3f BodyTypeChangedEvent changes (#2838) 2022-05-19 07:51:54 +10:00
metalgearsloth
a402f3a880 Version: 0.17.0.0 2022-05-17 13:32:33 +10:00
Leon Friedrich
b0fe9fb1a4 Log error for invalid coordinate conversions (#2835)
* Log error for invalid coordinate conversions

* remove $$$
2022-05-17 13:10:33 +10:00
Pieter-Jan Briers
5057c91dcd Console command completions v1. (#2817)
* Console command completions v1.

I think it works™️

* Unify cvar commands

* Handle no-completions-at-all better.

* Don't crash if you tab complete while no completions available.

* Always show hints if available

* Properly null completion hint over the wire

* Unify help command, localize it.

* Clean up + localize cvar command.

* Remove debug logging

* List command unified & localized.

* Remove server completions debug logging

* Remote execute command.

Had to make everything async for this.

* Don't lower case enums or bools
Why

* GC commands converted and localized.

* Fix remote command completions.

Whoops

* Kick command completions

* lsasm unified & localized.

* Revert "Don't lower case enums or bools"

This reverts commit 2f825347c3.

* ToString gc_mode command enums instead of trying to fix Fluent.

Ah well.

* Unify szr_stats

* Unify log commands, completions

* Fix compile

* Improve completion with complex cases (quotes, escapes)

* Code cleanup, comments.

* Fix tab completion with empty arg ruining everything.

* Fix RegisteredCommand completions

* Add more complex completion options system.

* Refactor content directory entries into a proper resource manager API.

* Implement GetEntries for DirLoader

* Make type hint darker.

* Exec command autocomplete, pulled play global sound code out to engine.
2022-05-17 13:07:25 +10:00
Pieter-Jan Briers
84a80db21f Quick and dirty environment variable to disable sandboxing.
Make game start faster.
2022-05-16 22:07:09 +02:00
20kdc
8657e5516b Structured disconnect messages (#2831)
* net structured disco

* Structured reason into NetChannelArgs/etc.

* Enable redial flag in common cases

* Get it all done

* Disco->DisconnectMessages
2022-05-16 21:48:12 +02:00
Pieter-Jan Briers
6b24da6990 Don't emit array precisions on GLES2 2022-05-16 17:32:45 +02:00
20kdc
0ee87bc771 Colour batch reduction (#2809)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2022-05-16 17:28:37 +02:00
metalgearsloth
6e35c89740 Significantly optimise prediction resetting (#2826)
* Significantly optimise prediction resetting

Turns out just tracking dirty entities and not iterating 30k is much faster.

* cleaner

* Slightly better again

* review

* Fix

* boop

* rebiew

* A
2022-05-16 22:08:17 +10:00
Leon Friedrich
9d69b330b5 Stop double AnchorStateChangedEvent (#2830) 2022-05-16 14:40:48 +10:00
metalgearsloth
22b3f3148c Add default ctor for mousejoint 2022-05-16 13:55:30 +10:00
metalgearsloth
b73b3007f2 Version: 0.16.0.0 2022-05-16 13:20:12 +10:00
Leon Friedrich
37dd4b0893 Pass sprite component in AppearanceChangeEvent (#2829) 2022-05-16 13:19:40 +10:00
metalgearsloth
07c5a38582 Issue collision change events on entity spawns again (#2828)
I thought I'd think of something better for pathfinding but I did not.
2022-05-16 13:17:48 +10:00
metalgearsloth
c866c6b59b Optimise grid traversal for pop-in (#2825)
This was like 1/3 of the frame spikes from PVS pop-in. The main benefit is just checking if the entity is anchored for grid traversal and the secondary was earlying-out handling component state if it's sent to nullspace.
2022-05-16 07:37:01 +10:00
ElectroJr
c3e97cb97e Version: 0.15.1.0 2022-05-15 15:25:38 +12:00
Leon Friedrich
52ffb97369 Change overlay screen texture wrapping (#2749) 2022-05-15 13:20:52 +10:00
Leon Friedrich
bad5657725 MapLoader and mapping command changes (#2744) 2022-05-15 12:28:38 +10:00
wrexbe
f8dc3c8a0e Add some CodeAnalysis attributes to whitelist (#2811) 2022-05-15 12:25:57 +10:00
Paul Ritter
77493b5d14 caches reflection in serv3 datadefinition delegates (#2790)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-05-14 16:37:18 +10:00
metalgearsloth
b1ceafbe4f Version: 0.15.0.0 2022-05-14 15:14:10 +10:00
Pieter-Jan Briers
41e9d9b08b Client profiling system (#2783)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-05-14 15:08:46 +10:00
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
Pieter-Jan Briers
de8c2c14bb Replace Lidgren encryption stuff with Libsodium (#2646) 2022-04-02 16:24:04 +02:00
mirrorcult
baebfff321 Merge pull request #2673 from ElectroJr/fix-description 2022-04-01 17:02:43 -07:00
Paul
24191404aa version 0.8.86 2022-04-02 01:28:12 +02:00
Paul Ritter
990842f5c2 fixes pvs dirty crash (#2674) 2022-04-02 06:09:14 +11:00
ElectroJr
2c2bd3f330 Version: 0.8.85 2022-04-02 00:38:07 +13:00
Leon Friedrich
1ae14c4bfa Add error tolerance to physics map (#2672) 2022-04-01 22:07:27 +11:00
metalgearsloth
61a1701dc9 Fix PVS take 3 (#2671) 2022-04-01 22:04:30 +11:00
ElectroJr
c6ea11346e fix entity description proxy method. 2022-04-02 00:02:58 +13:00
mirrorcult
ad7c871e28 Make effects properly clean up after itself (#2670) 2022-04-01 13:00:54 +11:00
metalgearsloth
c77b3f8022 Version: 0.8.84 2022-04-01 10:10:20 +11:00
metalgearsloth
b263f4a1df Fix PVS crash 2 (#2668)
Co-authored-by: Paul <ritter.paul1@googlemail.com>
2022-04-01 10:09:31 +11:00
Paul
1a11d41bba disable benchmarks workflow 2022-03-31 17:06:48 +02:00
Leon Friedrich
14d0a77644 Make directional sprite matrices static (#2661) 2022-04-01 00:11:17 +11:00
ElectroJr
b75045ce1a Version: 0.8.83 2022-03-31 15:25:31 +13:00
Leon Friedrich
1d8a8e15ef Add a is-In-container metadata flag. (#2585) 2022-03-31 13:23:33 +11:00
Leon Friedrich
28a087c267 Add GetEntitiesIntersectingBody (#2641) 2022-03-31 11:52:35 +11:00
Jane Lewis
8dc849b6bb Cache Sprite AABBs (#2622) 2022-03-31 11:52:00 +11:00
mirrorcult
7e56f46f35 Remove network component messages (#2659) 2022-03-31 10:24:27 +11:00
metalgearsloth
4453a23069 Version: 0.8.82 2022-03-30 23:34:50 +11:00
metalgearsloth
57be0bc845 Fix nullability (#2658) 2022-03-30 23:34:31 +11:00
Paul Ritter
430910ff36 sudo the benchmarks to elevate them to high prio 2022-03-30 14:10:13 +02:00
Paul Ritter
4c95807e2d how 2022-03-30 14:08:53 +02:00
metalgearsloth
92fd04552d Version: 0.8.81 2022-03-30 22:58:42 +11:00
Leon Friedrich
9c7e244821 ECS collision wake (#2656) 2022-03-30 22:57:57 +11:00
Vera Aguilera Puerto
41f64b5c3c Add overload to IntersectRayWithPredicate. (#2650) 2022-03-30 22:57:40 +11:00
Paul Ritter
4003003580 caching trees in pvs (#2652)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-03-30 22:57:31 +11:00
Paul Ritter
f49341b53c im a dummy 2022-03-30 11:16:09 +02:00
Leon Friedrich
61d9d9a832 Make the UI System TryGet functions not log failed resolves (#2657) 2022-03-30 16:37:34 +11:00
Paul
c0b7dd053f working on benchmarks workflow 2022-03-30 00:31:51 +02:00
Paul
f642e10acc versionbump to test benchmarks 2022-03-30 00:25:50 +02:00
Paul Ritter
2fb64480db benchmark workflow & storing it in db (#2653) 2022-03-29 21:43:32 +02:00
metalgearsloth
075b5ec203 Kill local comp messages (#2648) 2022-03-29 23:37:58 +11:00
metalgearsloth
21f2fe6b1b Minor spawn opt (#2651) 2022-03-29 12:34:42 +02:00
metalgearsloth
89080ef7c7 Version: 0.8.79 2022-03-29 18:40:32 +11:00
metalgearsloth
f5d36dc9ca Fix physics parenting bug (#2654) 2022-03-29 18:39:49 +11:00
wrexbe
ef87610b36 Map loader saving change (#2638)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-03-28 16:56:16 +11:00
mirrorcult
c89f1e4d31 Paramless ctor for joints (#2647)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-03-28 16:31:04 +11:00
metalgearsloth
c4805d89e0 Version: 0.8.78 2022-03-27 17:34:43 +11:00
Vera Aguilera Puerto
659ffb028a Dispose of SequencerEvents in MidiRenderer.
Oops!
2022-03-27 00:03:07 +01:00
metalgearsloth
a14c956ccc Nuke EntMapIdChangedMessage (#2621)
Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
2022-03-26 18:14:56 +01:00
metalgearsloth
facb3914a4 Fix joints crash (#2609) 2022-03-26 18:13:56 +01:00
Vera Aguilera Puerto
aab8f7876b Benchmarks for EntityManager (#2578) 2022-03-26 17:55:00 +01:00
Pieter-Jan Briers
1f199ea71c Add ITaskManager.BlockWaitOnTask()
Smugleaf needed this.

Also changed RobustSynchronizationContext to use channels instead of ConcurrentQueue internally.
2022-03-26 17:02:05 +01:00
metalgearsloth
00b557b77a Struct enumerator for anchored entities (#2626) 2022-03-26 12:53:25 +01:00
metalgearsloth
eac536cffe overlay allocs (#2642) 2022-03-26 12:48:11 +01:00
DrSmugleaf
bf75c6f247 Add IEntitySystemManager.GetEntitySystemOrNull (#2643) 2022-03-26 16:35:09 +11:00
metalgearsloth
1518c79291 Struct enumerator for GetAllGrids (#2624) 2022-03-26 03:17:42 +11:00
metalgearsloth
aae2f72d1a PVS pooling 4 (#2640) 2022-03-25 11:51:59 +01:00
Jane Lewis
273aa3d6ea Show player rotation in debug overlay (#2631) 2022-03-25 18:12:53 +11:00
metalgearsloth
756c4510eb Reduce prediction alloc (#2639) 2022-03-25 18:10:31 +11:00
Paul
cd50e89aec v0.8.77 2022-03-24 23:51:55 +01:00
metalgearsloth
25b648b929 Reduce PVS allocs a bunch (again (again)) (#2630) 2022-03-24 23:48:37 +01:00
metalgearsloth
1dbbb08250 Reduce server allocs a bunch (#2625) 2022-03-24 23:39:52 +01:00
DrSmugleaf
5432f7311e Fix keyboard focused being released and grabbed again immediately afterwards (#2634) 2022-03-24 15:25:25 +01:00
Zoldorf
e662802b4c Update build-docfx.yml 2022-03-23 17:32:12 -06:00
Vera Aguilera Puerto
bae2c390bb Improve Color.FromHex exception message.
It now includes the invalid hexcode argument.
Something in content kept getting these exceptions on prod and knowing the invalid argument would be very helpful when debugging these in the future.
2022-03-23 16:00:28 +01:00
Leon Friedrich
7976926eaf fix sequence node Except() (#2610)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2022-03-23 23:34:21 +11:00
metalgearsloth
4aee730753 Version: 0.8.76 2022-03-23 20:48:42 +11:00
metalgearsloth
e87c342aa2 inline robusttree pool (#2628) 2022-03-23 20:47:53 +11:00
Paul
d335d74d8f adds maxpoolsize to treepool 2022-03-22 18:02:00 +01:00
metalgearsloth
bc68b0e3ec Version: 0.8.75 2022-03-22 13:57:31 +11:00
metalgearsloth
b1eadbc58f Fix PVS allocation issues (#2623) 2022-03-22 13:55:57 +11:00
Silver
2d3165a16f remove inheritedmembers div 2022-03-21 18:02:35 -06:00
Zoldorf
d99d25de37 Create build-docfx.yml 2022-03-21 17:22:46 -06:00
Zoldorf
be035ab002 :mistake: 2022-03-21 17:22:08 -06:00
Zoldorf
12c8883eea Update build-test.yml 2022-03-21 15:36:41 -06:00
Zoldorf
758c7c1869 Update build-test.yml 2022-03-21 15:28:12 -06:00
Zoldorf
72ab1f5a1a Update build-test.yml 2022-03-21 15:25:40 -06:00
Zoldorf
6b9c1369c0 Update build-test.yml 2022-03-21 15:22:32 -06:00
Zoldorf
fd1f1dc64a Delete build-docfx.yml 2022-03-21 15:12:24 -06:00
Zoldorf
e211368bc4 Update build-docfx.yml 2022-03-21 15:07:41 -06:00
Zoldorf
39307f916d build-docfx.yml 2022-03-21 15:04:24 -06:00
metalgearsloth
7378cc50a1 Version: 0.8.74 2022-03-21 20:09:16 +11:00
Silver
867b9ad932 Adds Docfx to RobustToolbox 2022-03-20 23:08:59 -06:00
Paul Ritter
03064255f6 Makes Robusttree Children nullable to save allocations (#2570) 2022-03-21 03:02:52 +11:00
Pieter-Jan Briers
c9ccd0b873 Fix file dialogs crashing on macOS 2022-03-20 16:47:20 +01:00
metalgearsloth
650eceea7e Remove IoC + GetComp for every tile intersection (#2617) 2022-03-20 16:54:05 +11:00
DrSmugleaf
452ad5a6e5 Version: 0.8.73 2022-03-19 19:41:22 +01:00
Leon Friedrich
7c292c75d4 Add more information to reflection errors. (#2613) 2022-03-20 00:11:42 +11:00
DrSmugleaf
cab4113697 Add myself to CODEOWNERS 2022-03-19 11:27:54 +01:00
metalgearsloth
f1a5de79b5 Change entitylookup bounds to method (#2611) 2022-03-19 16:14:24 +11:00
metalgearsloth
191e80c175 Add EntityQuery support to filters (#2608) 2022-03-19 15:54:13 +11:00
Leon Friedrich
241bce56c8 Add Resolve() to component queries (#2607) 2022-03-19 15:53:39 +11:00
metalgearsloth
b105639b31 More transform ECS methods (#2606) 2022-03-18 10:32:49 +11:00
ElectroJr
419e63ecd5 Version: 0.8.72 2022-03-16 15:32:19 +13:00
metalgearsloth
46e632bf9d Fix effect overlay crash (#2604) 2022-03-15 18:05:13 +11:00
Leon Friedrich
a8871f4ff4 Add an assert to positional audio (#2602) 2022-03-15 14:54:50 +11:00
Paul
65f28c023d also write + bugfix + ratio 2022-03-15 00:43:40 +01:00
Paul
e506f6aba2 error for invalid customtypeserializer 2022-03-15 00:36:05 +01:00
metalgearsloth
93ae74b5a7 Update nuget dependencies (#2581) 2022-03-14 08:57:20 +01:00
Kara D
7b25fa98ee Version: 0.8.71 2022-03-13 19:30:44 -07:00
mirrorcult
e320b7abb4 Merge pull request #2599 from Sanctuary-Station/staging 2022-03-13 19:26:16 -07:00
Acruid
6b9b56ca83 Grid Components Allocate Grids (#2597)
* Deleting a map is now routed through MapComponent deletion.

* Remove map and grid deletions from map networking, just delete the entity if you want to remove the map.

* Moved the chunkSize property of created grids from the networked MapData to the MapGridComponent state.

* Remove unused IMapManager.DefaultMap property.

* Removed MapCreationTick field from MapManager.MapCollection.

* Removed _maps hashset field from MapManager.

* Removed CreatedMaps array from network MapData.

* MapGrid.ParentMapId is now derived from the bound TransformComponent, and isn't required in the MapGrid ctor.
Removed MapGrid.CreatedTick, it can be found on MapGridComponent.CreationTick.

* Remove a bunch of ApplyGameStatePre code duplication.

* Completely refactored CreateGrid.

* Adds AddComponentUninitialized to the ECS system. This allows you to access a component before it is initialized in a using block.

* Use AddComponentUninitialized to allocate a grid after the component is allocated.

* MapLoader now creates the grids after creating the map entities.

* Chunksize and TileSize properties are now actually read out of the map yaml.
TileSize has a public Setter.

* Minor cleanup.

* Moved grid allocation onto the MapGridComponent.

* Final Cleanup.

* Merge Fail.

* Fixed test, grid was getting deleted because it was empty.

* Remove DeletedChunkDatum from grid networking.

* ApplyMapGridState moved from map manager to the MapGridComponent.
2022-03-13 18:28:59 -07:00
Jezithyr
4d19790b3d This should fix the font caching issue? 2022-03-13 16:51:09 -07:00
Jezithyr
2027863d4c Addressing PR Feedback 2022-03-13 15:59:32 -07:00
Vera Aguilera Puerto
4a4444a1ee Adds MemoryContentRoot. (#2590) 2022-03-13 23:52:34 +01:00
Jezithyr
f10316d384 Fixing missing semicolons 2022-03-13 15:23:45 -07:00
Jezithyr
778be40c86 Condensing code 2022-03-13 14:43:24 -07:00
Jesse Rougeau
2046323c71 Update Robust.Client/UserInterface/UserInterfaceManager.cs
More condensing

Co-authored-by: Paul Ritter <ritter.paul1@googlemail.com>
2022-03-13 13:06:02 -07:00
Jesse Rougeau
71bb5cd58e Update Robust.Client/UserInterface/UserInterfaceManager.cs
Condensing sizing logic

Co-authored-by: Paul Ritter <ritter.paul1@googlemail.com>
2022-03-13 13:05:30 -07:00
Jezithyr
71c6e437fa Removing debug and fixing indentation 2022-03-12 20:36:23 -07:00
Jezithyr
26e1474179 Implemented auto UI Scaling :D
- Implemented autoUI scaling, scaling supports down to 520 by 520 pixel screens(Idk why you'd even have this)
- UI scaling can be adjusted by overriding the window root class
2022-03-12 20:31:13 -07:00
Kara D
89a7459fda Update .NET version for build-test workflow
This should hopefully make it catch CS8983
2022-03-12 19:06:23 -07:00
Paul Ritter
a61adf5631 makes budgets clientside & replicated (#2593) 2022-03-13 00:06:40 +11:00
Leon Friedrich
97db1712f3 Fix grid traversal velocities (#2594)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2022-03-12 23:57:30 +11:00
Paul
ea7012d114 version 0.8.70 2022-03-09 20:58:17 +01:00
Moony
d3eaea5697 Add support for tile variants. (#2577) 2022-03-09 20:56:58 +01:00
metalgearsloth
4ca3fbe4bf Version: 0.8.69 2022-03-10 00:29:08 +11:00
Vera Aguilera Puerto
81fea7595a AttachToGridOrMap checks if the grid/map is being terminated/is deleted. (#2592) 2022-03-10 00:27:18 +11:00
metalgearsloth
2e0806cc1f Version: 0.8.68 2022-03-08 15:48:39 +11:00
metalgearsloth
5325650e92 Fix disappearing grids (#2587)
Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
2022-03-08 12:25:03 +11:00
Acruid
31764d3c2d Mark C# events on MapManager as Obsolete (#2586)
* Obsoleted C# grid events from MapManager.GridCollection. Use the ECS EventBus events instead.

* Obsoleted C# events from MapManager.MapCollection. Use the ECS EventBus events instead.
2022-03-07 15:51:00 -08:00
Leon Friedrich
9df5152610 Decrease error tolerance for angular velocity updates (#2584) 2022-03-06 23:55:56 +11:00
metalgearsloth
c6cd780ab5 Remove funky merge artifact 2022-03-06 18:26:02 +11:00
Vera Aguilera Puerto
023f845c48 SpriteComponent exception specifies failing RSI path, if any. 2022-03-03 21:29:01 +01:00
Vera Aguilera Puerto
5e977da9bc Fix BaseServer.Shutdown bug when not specifying a reason.
If a shutdown was requested before the main loop had been started, it would only work if the reason was non-null. This is because `string? _shutdownReason` essentially acts as a shutdown request flag with its nullability.
2022-03-03 15:41:44 +01:00
metalgearsloth
fa1de04df7 Version: 0.8.67 2022-03-03 21:18:00 +11:00
metalgearsloth
33251222cd EntityLookup as a system (#2573) 2022-03-03 21:17:01 +11:00
Kara D
70c1e8680f Version: 0.8.66 2022-03-01 15:06:19 -07:00
Kara D
0d214769e6 vs2022 fix 2022-03-01 15:05:49 -07:00
Paul
495bbe891a obsoletes serhooks 2022-03-01 21:49:24 +01:00
Rember
5f68f569c3 BaseContainer.Insert() will now reset the entity's rotation after res… (#2576) 2022-03-01 23:23:56 +11:00
Leon Friedrich
52c883cb1f Fix lsmap and lsgrid (#2574) 2022-03-01 20:20:03 +11:00
metalgearsloth
4b20bae597 RobustUnitTest tweaks (#2568) 2022-03-01 13:35:32 +11:00
Leon Friedrich
64ff046ceb loadmap command tweak (#2572)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2022-02-28 21:17:58 +11:00
Kara D
5f2566a97d Version: 0.8.65 2022-02-27 12:59:54 -07:00
Kara D
c3735d1d5e vs2022 moment 2022-02-27 12:59:25 -07:00
metalgearsloth
749e547773 Version: 0.8.64 2022-02-28 00:45:53 +11:00
metalgearsloth
af8a010f43 Optimise PVS cangetcompstate (#2560) 2022-02-28 00:45:16 +11:00
metalgearsloth
a8a29e814f Allow content to override occlusion directions (#2528) 2022-02-27 23:15:45 +11:00
metalgearsloth
c3cb5406f6 Fix culling disabled (#2485)
Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
2022-02-27 22:59:36 +11:00
mirrorcult
73f26d93ca Merge pull request #2565 from ElectroJr/mapping-actions 2022-02-26 20:06:48 -07:00
metalgearsloth
5e86a99060 Version: 0.8.63 2022-02-27 13:00:41 +11:00
Paul Ritter
ce9070b966 changes pvs chunkorder to use a tree-structure, should fix children being sent without parents (#2562)
Co-authored-by: Paul Ritter <ritter.paul1@gmail.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-02-27 13:00:10 +11:00
Vera Aguilera Puerto
aa5bcefaf2 Add System.Threading.Monitor (lock keyword) to sandbox whitelist. 2022-02-26 16:50:41 +01:00
ElectroJr
2df267fc28 Merge remote-tracking branch 'upstream/master' into mapping-actions 2022-02-24 04:13:45 +13:00
ElectroJr
306fcce337 remove todo 2022-02-24 04:05:04 +13:00
ElectroJr
3683b66ef8 Allow SpriteSpecifier to load from entity prototype 2022-02-23 23:37:05 +13:00
Acruid
08a52fb892 MapChunk Cleanup (#2555)
* All IPhysShapes now expose a property to get the local AABB.

* Removed IMapChunk. It's internal, we only have 1 implementation in the engine, no need for abstraction, and removing it helps perf.

* Cleaned up issues in MapChunk file.

* Encapsulate _tiles access inside MapChunk.

* Remove IEnumerable<TileRef> from MapChunk.

* Remove CollidesWithChunk

* Move CalcWorldAABB and RegenerateCollision from MapChunk to MapGrid.
Remove MapChunk.GridId.

* Removed MapChunk.GetAllTiles

* Removed the terrible mocked unit tests.

* Moved the GetTileRef functions from MapChunk to MapGrid.

* Add an event raised on MapChunk when a tile is modified.
Completely remove the IMapGrid dependency from MapChunk.

* Fix bug where you cannot change the tile damage of a tile.
2022-02-21 20:49:30 -08:00
Pieter-Jan Briers
fd3c54b373 Seal some classes. 2022-02-21 14:02:34 +01:00
Pieter-Jan Briers
adee05b009 Make client start with non-seekable streams.
Not marking #2439 as fixed yet due to lack of thorough testing.
2022-02-21 11:23:37 +01:00
Pieter-Jan Briers
a66d40eb19 CVar to force ResourceManager to provide seekability for streams.
To help with #2439
2022-02-21 11:23:37 +01:00
metalgearsloth
d065a96e01 Don't serialize grid fixtures (#2477) 2022-02-21 20:15:30 +11:00
Kara D
ef7709aa78 Version: 0.8.62 2022-02-20 19:34:25 -07:00
Paul Ritter
305b330075 bandaid 2022-02-21 02:30:03 +01:00
Acruid
bec4297ce1 Move Map Pause data from MapManager to MapComponent. (#2543)
* Encapsulated existing _pausedMaps access in MapManager.Pause.
Added more unit tests.

* Moved the MapPaused flag from MapManager.Pause to MapComponent.

* Moved the MapPreInit flag from MapManager.Pause to MapComponent.

* Changed visibility so content can't access the flags directly.

* It's not the code that is wrong, it's the tests that are wrong!

* Removed completely obsolete bookkeeping event.
2022-02-20 13:36:45 -08:00
metalgearsloth
18b21b3d60 Version: 0.8.61 2022-02-20 17:44:49 +11:00
mirrorcult
9b263417b9 Merge pull request #2541 from metalgearsloth/2022-02-14_1 2022-02-19 12:33:46 -07:00
Acruid
13da0a7925 Adds command buffer to client, and input command injection (#2538)
Adds the command buffer to the console.
adds wait <ticks> command to console.
Adds the `incmd` command to inject input commands directly into the simulation, bypassing the frontend.
Removes client side permission check for debug builds.
2022-02-18 19:27:38 -08:00
Kara D
bb28db2412 Version: 0.8.60 2022-02-18 15:50:15 -07:00
mirrorcult
489d150ae0 Merge pull request #2558 from PaulRitter/2022_02_18_real_pvs_hours 2022-02-18 15:49:20 -07:00
Paul Ritter
077dbaf933 yo this slaps 2022-02-18 20:49:03 +01:00
Paul Ritter
26f83ac7a2 bored trainride 2022-02-17 15:08:55 +01:00
metalgearsloth
822009b429 Version: 0.8.59 2022-02-17 18:05:50 +11:00
Leon Friedrich
e07a4e516c Add random vector functions to IRobustRandom (#2551) 2022-02-17 18:05:12 +11:00
metalgearsloth
9ec38b5538 Optimise PVS recursion (#2524) 2022-02-17 18:03:14 +11:00
metalgearsloth
97da770978 More physics ECS (#2529) 2022-02-17 17:52:27 +11:00
metalgearsloth
3acbc8235d Funny struct enumerator for xform kids Part 2 (#2492) 2022-02-17 17:19:08 +11:00
Sam Weaver
a32359d5d4 Only include shadow casting lights in the "max lights per scene" count (#2446)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2022-02-17 14:24:36 +11:00
Paul
78fd39aee2 could fix a racecondition? maybe? 2022-02-16 23:55:04 +01:00
Kara D
fca5c14c67 Version: 0.8.58 2022-02-16 15:53:10 -07:00
Kara D
f60e3a14ef Fix VS2022 17.1 issue 2022-02-16 15:49:19 -07:00
Leon Friedrich
503a7032f9 Hopefully fix appearance component mispredict reconciliation (#2460) 2022-02-16 23:26:13 +01:00
metalgearsloth
918bbd3f01 Version: 0.8.57 2022-02-17 01:06:34 +11:00
Leon Friedrich
a1441d5051 Properly support sprite layer transforms. (#2533) 2022-02-17 00:06:14 +11:00
Leon Friedrich
58d6189c40 Allow sprite layer data to be set using PrototypeLayerData. (#2442) 2022-02-16 19:37:40 +11:00
Leon Friedrich
58defed1d2 Fix InteractionOutline 2: Electric boogaloo (#2511)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-02-16 18:32:40 +11:00
Paul
27a94384d0 v0.8.56 2022-02-15 18:16:07 +01:00
Paul Ritter
4b8f5815db pvs caching (#2547)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-02-15 18:14:44 +01:00
metalgearsloth
d830eef435 Version: 0.8.55 2022-02-15 21:16:35 +11:00
metalgearsloth
e7c4bf7341 Don't use worldbounds for PVS (#2473) 2022-02-15 21:07:41 +11:00
ike709
162e646404 Fix a weird issue with config (#2546)
Co-authored-by: ike709 <ike709@github.com>
2022-02-15 20:57:32 +11:00
metalgearsloth
93049fcacf More transform optimisations (#2519) 2022-02-15 20:43:49 +11:00
metalgearsloth
5b90db4beb inline 2022-02-14 18:30:33 +11:00
metalgearsloth
7692ff736b Some more ECS stuff
- Proxy methods on EntityManager
- Transform system stuff
2022-02-14 18:24:52 +11:00
Kara D
f25ad8ece9 Version: 0.8.54 2022-02-13 20:25:00 -07:00
mirrorcult
1aaf3b9250 Merge pull request #2521 from ElectroJr/public-AppearanceSystem 2022-02-13 20:24:23 -07:00
mirrorcult
11f6cff6df Merge pull request #2539 from Acruid/vv_fix_entName 2022-02-13 20:01:53 -07:00
mirrorcult
c2ea57c95a Merge pull request #2503 from Ygg01/linguini-multiline-error-fix 2022-02-13 20:00:47 -07:00
Ygg01
f61ae5da6b Update Linguini and line info in errors 2022-02-14 01:50:11 +01:00
Acruid
8ae9a5f2da Fix a typo where setting the description of an entity in VV sets the name. 2022-02-13 16:35:51 -08:00
Ygg01
3f7e89e006 Merge remote-tracking branch 'origin/master' into linguini-multiline-error-fix 2022-02-13 23:35:28 +01:00
Ygg01
cee4e4d62e Enable Fluent logs for only .ftl files (#2532) 2022-02-12 21:59:08 +11:00
metalgearsloth
14a3783760 Version: 0.8.53 2022-02-12 15:56:21 +11:00
mirrorcult
b4607f7b1f Draw effects below FOV (#2534) 2022-02-12 15:54:50 +11:00
Acruid
5a28c16cae Map Pausing Fixes (#2520) 2022-02-12 15:54:03 +11:00
ElectroJr
9e8bf861ea Merge remote-tracking branch 'upstream/master' into public-AppearanceSystem 2022-02-11 20:43:19 +13:00
ElectroJr
4cfb9210d0 Make AppearanceSystem public 2022-02-09 04:55:31 +13:00
Ygg01
681f77a796 Change newline append 2022-02-04 02:01:52 +01:00
Ygg01
2a7f1cbf48 Ensure that tests work regardless of platform 2022-02-03 04:06:53 +01:00
Ygg01
c4e63cfdc7 Fix multiline errors, add some tests 2022-02-03 01:43:45 +01:00
653 changed files with 29852 additions and 13444 deletions

2
.github/CODEOWNERS vendored
View File

@@ -11,7 +11,7 @@
/Robust.Analyzers @PaulRitter
/Robust.*/GameStates @PaulRitter
/Robust.Shared/Analyzers @PaulRitter
/Robust.*/Serialization @PaulRitter
/Robust.*/Serialization @PaulRitter @DrSmugleaf
/Robust.*/Prototypes @PaulRitter
/Robust.Shared/GameObjects/ComponentDependencies @PaulRitter
/Robust.*/Containers @PaulRitter

35
.github/workflows/benchmarks.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Benchmarks
on:
workflow_dispatch:
schedule:
- cron: '0 5 * * *'
push:
tags:
- 'v*'
concurrency: benchmarks
env:
ROBUST_BENCHMARKS_ENABLE_SQL: 1
ROBUST_BENCHMARKS_SQL_ADDRESS: ${{ secrets.BENCHMARKS_WRITE_ADDRESS }}
ROBUST_BENCHMARKS_SQL_PORT: ${{ secrets.BENCHMARKS_WRITE_PORT }}
ROBUST_BENCHMARKS_SQL_USER: ${{ secrets.BENCHMARKS_WRITE_USER }}
ROBUST_BENCHMARKS_SQL_PASSWORD: ${{ secrets.BENCHMARKS_WRITE_PASSWORD }}
ROBUST_BENCHMARKS_SQL_DATABASE: benchmarks
jobs:
benchmark:
name: Run Benchmarks
runs-on: ubuntu-latest
steps:
- 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

34
.github/workflows/build-docfx.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Build & Publish DocFX
on:
schedule:
- cron: "0 0 * * 0"
jobs:
docfx:
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: Build Project
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
- name: Build DocFX
uses: nikeee/docfx-action@v1.0.0
with:
args: Robust.Docfx/docfx.json
- name: Publish Docfx Documentation on GitHub Pages
uses: maxheld83/ghpages@master
env:
BUILD_DIR: Robust.Docfx/_robust-site
GH_PAT: ${{ secrets.GH_PAT }}

View File

@@ -22,10 +22,12 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.100
dotnet-version: 6.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
- name: Test Engine
run: dotnet test --no-build Robust.UnitTesting/Robust.UnitTesting.csproj -v n
run: dotnet test --no-build Robust.UnitTesting/Robust.UnitTesting.csproj -- NUnit.ConsoleOut=0

2
.gitignore vendored
View File

@@ -76,3 +76,5 @@ MSBuild/Robust.Custom.targets
release/
Robust.Docfx/*-site
Robust.Docfx/api

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.52</Version></PropertyGroup>
<PropertyGroup><Version>0.19.2.0</Version></PropertyGroup>
</Project>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<!-- Avoid MSBuild adding a None entry for XAML files because they'd show up TWICE in the project view. -->
<DefaultItemExcludes>**/*.xaml</DefaultItemExcludes>
<RobustUseExternalMSBuild>true</RobustUseExternalMSBuild>
<RobustUseExternalMSBuild>false</RobustUseExternalMSBuild>
<_RobustUseExternalMSBuild>$(RobustUseExternalMSBuild)</_RobustUseExternalMSBuild>
<_RobustUseExternalMSBuild Condition="'$(_RobustForceInternalMSBuild)' == 'true'">false</_RobustUseExternalMSBuild>
</PropertyGroup>

View File

@@ -0,0 +1,89 @@
### Localization for engine console commands
## 'help' command
cmd-help-desc = Display general help or help text for a specific command
cmd-help-help = Usage: help [command name]
When no command name is provided, displays general-purpose help text. If a command name is provided, displays help text for that command.
cmd-help-no-args = To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'. To search for commands, use 'list <filter>'.
cmd-help-unknown = Unknown command: { $command }
cmd-help-top = { $command } - { $description }
cmd-help-invalid-args = Invalid amount of arguments.
cmd-help-arg-cmdname = [command name]
## 'cvar' command
cmd-cvar-desc = Gets or sets a CVar.
cmd-cvar-help = Usage: cvar <name | ?> [value]
If a value is passed, the value is parsed and stored as the new value of the CVar.
If not, the current value of the CVar is displayed.
Use 'cvar ?' to get a list of all registered CVars.
cmd-cvar-invalid-args = Must provide exactly one or two arguments.
cmd-cvar-not-registered = CVar '{ $cvar }' is not registered. Use 'cvar ?' to get a list of all registered CVars.
cmd-cvar-parse-error = Input value is in incorrect format for type { $type }
cmd-cvar-compl-list = List available CVars
cmd-cvar-arg-name = <name | ?>
## 'list' command
cmd-list-desc = Lists available commands, with optional search filter
cmd-list-help = Usage: list [filter]
Lists all available commands. If an argument is provided, it will be used to filter commands by name.
cmd-list-heading = SIDE NAME DESC{"\u000A"}-------------------------{"\u000A"}
cmd-list-arg-filter = [filter]
## '>' command, aka remote exec
cmd-remoteexec-desc = Executes server-side commands
cmd-remoteexec-help = Usage: > <command> [arg] [arg] [arg...]
Executes a command on the server. This is necessary if a command with the same name exists on the client, as simply running the command would run the client command first.
## 'gc' command
cmd-gc-desc = Run the GC (Garbage Collector)
cmd-gc-help = Usage: gc [generation]
Uses GC.Collect() to execute the Garbage Collector.
If an argument is provided, it is parsed as a GC generation number and GC.Collect(int) is used.
Use the 'gfc' command to do an LOH-compacting full GC.
cmd-gc-failed-parse = Failed to parse argument.
cmd-gc-arg-generation = [generation]
## 'gcf' command
cmd-gcf-desc = Run the GC, fully, compacting LOH and everything.
cmd-gcf-help = Usage: gcf
Does a full GC.Collect(2, GCCollectionMode.Forced, true, true) while also compacting LOH.
This will probably lock up for hundreds of milliseconds, be warned.
## 'gc_mode' command
cmd-gc_mode-desc = Change/Read the GC Latency mode
cmd-gc_mode-help = Usage: gc_mode [type]
If no argument is provided, returns the current GC latency mode.
If an argument is passed, it is parsed as GCLatencyMode and set as the GC latency mode.
cmd-gc_mode-current = current gc latency mode: { $prevMode }
cmd-gc_mode-possible = possible modes:
cmd-gc_mode-option = - { $mode }
cmd-gc_mode-unknown = unknown gc latency mode: { $arg }
cmd-gc_mode-attempt = attempting gc latency mode change: { $prevMode } -> { $mode }
cmd-gc_mode-result = resulting gc latency mode: { $mode }
cmd-gc_mode-arg-type = [type]
## 'mem' command
cmd-mem-desc = Prints managed memory info
cmd-mem-help = Usage: mem
cmd-mem-report = Heap Size: { TOSTRING($heapSize, "N0") }
Total Allocated: { TOSTRING($totalAllocated, "N0") }
## 'physics' command
cmd-physics-overlay = {$overlay} is not a recognised overlay
## 'lsasm' command
cmd-lsasm-desc = Lists loaded assemblies by load context
cmd-lsasm-help = Usage: lsasm
## 'exec' command
cmd-exec-desc = Executes a script file from the game's writeable user data
cmd-exec-help = Usage: exec <fileName>
Each line in the file is executed as a single command, unless it starts with a #
cmd-exec-arg-filename = <fileName>

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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

View File

@@ -6,9 +6,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="3.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.0.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Globalization;
using BenchmarkDotNet.Analysers;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Filters;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Order;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Validators;
using Robust.Benchmarks.Exporters;
namespace Robust.Benchmarks.Configs;
public sealed class DefaultSQLConfig : IConfig
{
public static readonly IConfig Instance = new DefaultSQLConfig();
private DefaultSQLConfig(){}
public IEnumerable<IExporter> GetExporters()
{
yield return SQLExporter.Default;
}
public IEnumerable<IColumnProvider> GetColumnProviders() => DefaultConfig.Instance.GetColumnProviders();
public IEnumerable<ILogger> GetLoggers() => DefaultConfig.Instance.GetLoggers();
public IEnumerable<IDiagnoser> GetDiagnosers() => DefaultConfig.Instance.GetDiagnosers();
public IEnumerable<IAnalyser> GetAnalysers() => DefaultConfig.Instance.GetAnalysers();
public IEnumerable<Job> GetJobs() => DefaultConfig.Instance.GetJobs();
public IEnumerable<IValidator> GetValidators() => DefaultConfig.Instance.GetValidators();
public IEnumerable<HardwareCounter> GetHardwareCounters() => DefaultConfig.Instance.GetHardwareCounters();
public IEnumerable<IFilter> GetFilters() => DefaultConfig.Instance.GetFilters();
public IEnumerable<BenchmarkLogicalGroupRule> GetLogicalGroupRules() => DefaultConfig.Instance.GetLogicalGroupRules();
public IOrderer Orderer => DefaultConfig.Instance.Orderer!;
public SummaryStyle SummaryStyle => DefaultConfig.Instance.SummaryStyle;
public ConfigUnionRule UnionRule => DefaultConfig.Instance.UnionRule;
public string ArtifactsPath => DefaultConfig.Instance.ArtifactsPath;
public CultureInfo CultureInfo => DefaultConfig.Instance.CultureInfo!;
public ConfigOptions Options => DefaultConfig.Instance.Options;
}

View File

@@ -0,0 +1,54 @@
using BenchmarkDotNet.Attributes;
using JetBrains.Annotations;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.EntityManager;
[Virtual]
public class AddRemoveComponentBenchmark
{
private ISimulation _simulation = default!;
private IEntityManager _entityManager = default!;
[UsedImplicitly]
[Params(1, 10, 100, 1000)]
public int N;
[GlobalSetup]
public void GlobalSetup()
{
_simulation = RobustServerSimulation
.NewSimulation()
.RegisterComponents(f => f.RegisterClass<A>())
.InitializeInstance();
_entityManager = _simulation.Resolve<IEntityManager>();
var coords = new MapCoordinates(0, 0, new MapId(1));
_simulation.AddMap(coords.MapId);
for (var i = 0; i < N; i++)
{
_entityManager.SpawnEntity(null, coords);
}
}
[Benchmark]
public void AddRemoveComponent()
{
for (var i = 2; i <= N+1; i++)
{
var uid = new EntityUid(i);
_entityManager.AddComponent<A>(uid);
_entityManager.RemoveComponent<A>(uid);
}
}
[ComponentProtoName("A")]
public sealed class A : Component
{
}
}

View File

@@ -0,0 +1,225 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using BenchmarkDotNet.Attributes;
using Robust.Shared.GameObjects;
namespace Robust.Benchmarks.EntityManager;
public class ComponentIndexBenchmark
{
// Just a bunch of types to bloat the test lists.
private readonly CompIndexFetcher _compIndexFetcherDirect;
private readonly IFetcher _compIndexFetcher;
private readonly DictFetcher _dictFetcherDirect;
private readonly IFetcher _dictFetcher;
public ComponentIndexBenchmark()
{
_compIndexFetcherDirect = new CompIndexFetcher();
_compIndexFetcher = _compIndexFetcherDirect;
_dictFetcherDirect = new DictFetcher();
_dictFetcher = _dictFetcherDirect;
}
[GlobalSetup]
public void Setup()
{
var types = typeof(ComponentIndexBenchmark)
.GetNestedTypes(BindingFlags.NonPublic)
.Where(t => t.Name.StartsWith("TestType"))
.ToArray();
_compIndexFetcher.Init(types);
_dictFetcher.Init(types);
}
[Benchmark]
public int BenchCompIndex() => _compIndexFetcher.Get<TestType50>();
[Benchmark]
public int BenchDict() => _dictFetcher.Get<TestType50>();
[Benchmark]
public int BenchCompIndexDirect() => _compIndexFetcherDirect.Get<TestType50>();
[Benchmark]
public int BenchDictDirect() => _dictFetcherDirect.Get<TestType50>();
private static CompIdx ArrayIndexFor<T>() => CompArrayIndex<T>.Idx;
private static int _compIndexMaster = -1;
private static class CompArrayIndex<T>
{
// ReSharper disable once StaticMemberInGenericType
public static readonly CompIdx Idx = new(Interlocked.Increment(ref _compIndexMaster));
}
private static CompIdx GetCompIdIndex(Type type)
{
return (CompIdx)typeof(CompArrayIndex<>)
.MakeGenericType(type)
.GetField(nameof(CompArrayIndex<int>.Idx), BindingFlags.Static | BindingFlags.Public)!
.GetValue(null)!;
}
private interface IFetcher
{
void Init(Type[] types);
int Get<T>();
}
private sealed class CompIndexFetcher : IFetcher
{
private int[] _values = Array.Empty<int>();
public void Init(Type[] types)
{
var max = types.Max(t => GetCompIdIndex(t).Value);
_values = new int[max + 1];
var i = 0;
foreach (var type in types)
{
_values[GetCompIdIndex(type).Value] = i++;
}
}
public int Get<T>()
{
return _values[CompArrayIndex<T>.Idx.Value];
}
}
private sealed class DictFetcher : IFetcher
{
private readonly Dictionary<Type, int> _values = new();
public void Init(Type[] types)
{
var i = 0;
foreach (var type in types)
{
_values[type] = i++;
}
}
public int Get<T>()
{
return _values[typeof(T)];
}
}
// Just a bunch of types to pad the size of the arrays and such.
// @formatter:off
// ReSharper disable UnusedType.Local
private sealed class TestType1{}
private sealed class TestType2{}
private sealed class TestType3{}
private sealed class TestType4{}
private sealed class TestType5{}
private sealed class TestType6{}
private sealed class TestType7{}
private sealed class TestType8{}
private sealed class TestType9{}
private sealed class TestType10{}
private sealed class TestType11{}
private sealed class TestType12{}
private sealed class TestType13{}
private sealed class TestType14{}
private sealed class TestType15{}
private sealed class TestType16{}
private sealed class TestType17{}
private sealed class TestType18{}
private sealed class TestType19{}
private sealed class TestType20{}
private sealed class TestType21{}
private sealed class TestType22{}
private sealed class TestType23{}
private sealed class TestType24{}
private sealed class TestType25{}
private sealed class TestType26{}
private sealed class TestType27{}
private sealed class TestType28{}
private sealed class TestType29{}
private sealed class TestType30{}
private sealed class TestType31{}
private sealed class TestType32{}
private sealed class TestType33{}
private sealed class TestType34{}
private sealed class TestType35{}
private sealed class TestType36{}
private sealed class TestType37{}
private sealed class TestType38{}
private sealed class TestType39{}
private sealed class TestType40{}
private sealed class TestType41{}
private sealed class TestType42{}
private sealed class TestType43{}
private sealed class TestType44{}
private sealed class TestType45{}
private sealed class TestType46{}
private sealed class TestType47{}
private sealed class TestType48{}
private sealed class TestType49{}
private sealed class TestType50{}
private sealed class TestType51{}
private sealed class TestType52{}
private sealed class TestType53{}
private sealed class TestType54{}
private sealed class TestType55{}
private sealed class TestType56{}
private sealed class TestType57{}
private sealed class TestType58{}
private sealed class TestType59{}
private sealed class TestType60{}
private sealed class TestType61{}
private sealed class TestType62{}
private sealed class TestType63{}
private sealed class TestType64{}
private sealed class TestType65{}
private sealed class TestType66{}
private sealed class TestType67{}
private sealed class TestType68{}
private sealed class TestType69{}
private sealed class TestType70{}
private sealed class TestType71{}
private sealed class TestType72{}
private sealed class TestType73{}
private sealed class TestType74{}
private sealed class TestType75{}
private sealed class TestType76{}
private sealed class TestType77{}
private sealed class TestType78{}
private sealed class TestType79{}
private sealed class TestType80{}
private sealed class TestType81{}
private sealed class TestType82{}
private sealed class TestType83{}
private sealed class TestType84{}
private sealed class TestType85{}
private sealed class TestType86{}
private sealed class TestType87{}
private sealed class TestType88{}
private sealed class TestType89{}
private sealed class TestType90{}
private sealed class TestType91{}
private sealed class TestType92{}
private sealed class TestType93{}
private sealed class TestType94{}
private sealed class TestType95{}
private sealed class TestType96{}
private sealed class TestType97{}
private sealed class TestType98{}
private sealed class TestType99{}
// ReSharper restore UnusedType.Local
// @formatter:on
}

View File

@@ -0,0 +1,61 @@
using System;
using BenchmarkDotNet.Attributes;
using JetBrains.Annotations;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.EntityManager;
[Virtual]
public class GetComponentBenchmark
{
private ISimulation _simulation = default!;
private IEntityManager _entityManager = default!;
[UsedImplicitly]
[Params(1, 10, 100, 1000)]
public int N;
public A[] Comps = default!;
[GlobalSetup]
public void GlobalSetup()
{
_simulation = RobustServerSimulation
.NewSimulation()
.RegisterComponents(f => f.RegisterClass<A>())
.InitializeInstance();
_entityManager = _simulation.Resolve<IEntityManager>();
Comps = new A[N+2];
var coords = new MapCoordinates(0, 0, new MapId(1));
_simulation.AddMap(coords.MapId);
for (var i = 0; i < N; i++)
{
var uid = _entityManager.SpawnEntity(null, coords);
_entityManager.AddComponent<A>(uid);
}
}
[Benchmark]
public A[] GetComponent()
{
for (var i = 2; i <= N+1; i++)
{
Comps[i] = _entityManager.GetComponent<A>(new EntityUid(i));
}
// Return something so the JIT doesn't optimize out all the GetComponent calls.
return Comps;
}
[ComponentProtoName("A")]
public sealed class A : Component
{
}
}

View File

@@ -0,0 +1,62 @@
using BenchmarkDotNet.Attributes;
using JetBrains.Annotations;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.EntityManager;
[Virtual]
public class SpawnDeleteEntityBenchmark
{
private ISimulation _simulation = default!;
private IEntityManager _entityManager = default!;
private MapCoordinates _mapCoords = MapCoordinates.Nullspace;
private EntityCoordinates _entCoords = EntityCoordinates.Invalid;
[UsedImplicitly]
[Params(1, 10, 100, 1000)]
public int N;
[GlobalSetup]
public void GlobalSetup()
{
_simulation = RobustServerSimulation
.NewSimulation()
.RegisterComponents(f => f.RegisterClass<A>())
.InitializeInstance();
_entityManager = _simulation.Resolve<IEntityManager>();
_mapCoords = new MapCoordinates(0, 0, new MapId(1));
var uid = _simulation.AddMap(_mapCoords.MapId);
_entCoords = new EntityCoordinates(uid, 0, 0);
}
[Benchmark(Baseline = true)]
public void SpawnDeleteEntityMapCoords()
{
for (var i = 0; i < N; i++)
{
var uid = _entityManager.SpawnEntity(null, _mapCoords);
_entityManager.DeleteEntity(uid);
}
}
[Benchmark]
public void SpawnDeleteEntityEntCoords()
{
for (var i = 0; i < N; i++)
{
var uid = _entityManager.SpawnEntity(null, _entCoords);
_entityManager.DeleteEntity(uid);
}
}
[ComponentProtoName("A")]
public sealed class A : Component
{
}
}

View File

@@ -0,0 +1,195 @@
using System;
using System.Collections.Generic;
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;
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;
public sealed class SQLExporter : IExporter
{
public static readonly IExporter Default = new SQLExporter();
private SQLExporter(){}
public void ExportToLog(Summary summary, ILogger logger)
{
Export(summary, logger);
}
public IEnumerable<string> ExportToFiles(Summary summary, ILogger consoleLogger)
{
Export(summary, consoleLogger);
return Array.Empty<string>();
}
private bool TryGetEnvironmentVariable(string name, ILogger logger, [NotNullWhen(true)] out string? value)
{
value = Environment.GetEnvironmentVariable(name);
if (value == null)
logger.WriteError($"ROBUST_BENCHMARKS_ENABLE_SQL is set, but {name} is missing.");
return value != null;
}
private void Export(Summary summary, ILogger logger)
{
if (!TryGetEnvironmentVariable("ROBUST_BENCHMARKS_SQL_ADDRESS", logger, out var address) ||
!TryGetEnvironmentVariable("ROBUST_BENCHMARKS_SQL_PORT", logger, out var rawPort) ||
!TryGetEnvironmentVariable("ROBUST_BENCHMARKS_SQL_USER", logger, out var user) ||
!TryGetEnvironmentVariable("ROBUST_BENCHMARKS_SQL_PASSWORD", logger, out var password) ||
!TryGetEnvironmentVariable("ROBUST_BENCHMARKS_SQL_DATABASE", logger, out var db) ||
!TryGetEnvironmentVariable("GITHUB_SHA", logger, out var gitHash))
return;
if (!int.TryParse(rawPort, out var port))
{
logger.WriteError("Failed parsing ROBUST_BENCHMARKS_SQL_PORT to int.");
return;
}
var builder = new DbContextOptionsBuilder<BenchmarkContext>();
var connectionString = new NpgsqlConnectionStringBuilder
{
Host = address,
Port = port,
Database = db,
Username = user,
Password = password
}.ConnectionString;
builder.UseNpgsql(connectionString);
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();
}
finally
{
ctx.Dispose();
}
}
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)
{
var optionsBuilder = new DbContextOptionsBuilder<BenchmarkContext>();
optionsBuilder.UseNpgsql("Server=localhost");
return new BenchmarkContext(optionsBuilder.Options);
}
}
public class BenchmarkContext : DbContext
{
public DbSet<BenchmarkRun> BenchmarkRuns { get; set; } = default!;
public BenchmarkContext() { }
public BenchmarkContext(DbContextOptions<BenchmarkContext> options) : base(options) { }
}
public class BenchmarkRun
{
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>();
public static BenchmarkRun FromSummary(Summary summary, string gitHash)
{
return new BenchmarkRun
{
Reports = summary.Reports.Select(r => new BenchmarkRunReport
{
Parameters = r.BenchmarkCase.Parameters.Items.Select(p => new BenchmarkRunParameter
{
Name = p.Name,
Value = p.Value
}).ToArray(),
Statistics = r.ResultStatistics
}).ToArray(),
Name = summary.BenchmarksCases.First().FolderInfo,
RunDate = DateTime.UtcNow,
GitHash = gitHash
};
}
}
public class BenchmarkRunReport
{
public BenchmarkRunParameter[] Parameters { get; set; } = Array.Empty<BenchmarkRunParameter>();
public Statistics Statistics { get; set; } = default!;
}
public class BenchmarkRunParameter
{
public string Name { get; set; } = string.Empty;
public object Value { get; set; } = default!;
}

View File

@@ -0,0 +1,55 @@
// <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("20220328231938_InitialCreate")]
partial class InitialCreate
{
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<decimal>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("numeric(20,0)");
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("Date");
b.HasKey("Id");
b.ToTable("BenchmarkRuns");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Robust.Benchmarks.Exporters;
#nullable disable
namespace Robust.Benchmarks.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "BenchmarkRuns",
columns: table => new
{
Id = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
GitHash = table.Column<string>(type: "text", nullable: false),
RunDate = table.Column<DateTime>(type: "Date", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
Reports = table.Column<BenchmarkRunReport[]>(type: "jsonb", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BenchmarkRuns", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "BenchmarkRuns");
}
}
}

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

@@ -0,0 +1,55 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Robust.Benchmarks.Exporters;
#nullable disable
namespace Robust.Benchmarks.Migrations
{
[DbContext(typeof(BenchmarkContext))]
partial class BenchmarkContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(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

@@ -9,6 +9,9 @@ namespace Robust.Benchmarks.NumericsHelpers
[Params(32, 128)]
public int N { get; set; }
[Params(1,2)]
public int T { get; set; }
private float[] _inputA = default!;
private float[] _inputB = default!;
private float[] _output = default!;

View File

@@ -1,6 +1,8 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
using System;
using Robust.Benchmarks.Configs;
using Robust.Benchmarks.Exporters;
namespace Robust.Benchmarks
{
@@ -16,7 +18,8 @@ namespace Robust.Benchmarks
Console.WriteLine("THE DEBUG BUILD IS ONLY GOOD FOR FIXING A CRASHING BENCHMARK\n");
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, new DebugInProcessConfig());
#else
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
var config = Environment.GetEnvironmentVariable("ROBUST_BENCHMARKS_ENABLE_SQL") != null ? DefaultSQLConfig.Instance : null;
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
#endif
}
}

View File

@@ -6,13 +6,20 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>../bin/Benchmarks</OutputPath>
<OutputType>Exe</OutputType>
<NoWarn>RA0003</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Robust.Server\Robust.Server.csproj" />
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
<ProjectReference Include="..\Robust.UnitTesting\Robust.UnitTesting.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<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,11 @@
#!/usr/bin/env pwsh
param([String]$name)
if ($name -eq "")
{
Write-Error "must specify migration name"
exit
}
dotnet ef migrations add --context BenchmarkContext -o Migrations $name

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
if [ -z "$1" ] ; then
echo "Must specify migration name"
exit 1
fi
dotnet ef migrations add --context BenchmarkContext -o Migrations "$1"

View File

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

View File

@@ -5,8 +5,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Shared.Animations;
using Robust.Shared.Maths;
@@ -101,7 +101,7 @@ namespace Robust.Client.Animations
case double d:
return MathHelper.Lerp(d, (double) b, t);
case Angle angle:
return (Angle) MathHelper.Lerp(angle, (Angle) b, t);
return Angle.Lerp(angle, (Angle) b, t);
case Color color:
return Color.InterpolateBetween(color, (Color) b, t);
case int i:

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,440 @@ 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/default.dls",
"/usr/share/soundfonts/FluidR3_GM.sf2",
"/usr/share/soundfonts/freepats-general-midi.sf2",
"/usr/share/sounds/sf2/default.sf2",
"/usr/share/sounds/sf2/default.dls",
"/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 const float MaxDistanceForOcclusion = 1000;
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);
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
renderer.LoadSoundfont(FallbackSoundfont);
if (OperatingSystem.IsLinux())
{
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)
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 content-specific custom soundfonts, which could override the system/fallback soundfont.
foreach (var file in _resourceManager.ContentFindFiles(("/Audio/MidiCustom/")))
{
if (file.Extension != "sf2" && file.Extension != "dls") continue;
renderer.LoadSoundfont(file.ToString());
}
// Load every soundfont from the user data directory last, since those may override any other soundfont.
_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),
MathF.Min(sourceRelative.Length, MaxDistanceForOcclusion),
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 +458,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

@@ -13,6 +13,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -26,7 +27,6 @@ namespace Robust.Client
[Dependency] private readonly IPlayerManager _playMan = default!;
[Dependency] private readonly INetConfigurationManager _configManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _entityLookup = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IDiscordRichPresence _discord = default!;
[Dependency] private readonly IGameTiming _timing = default!;
@@ -46,6 +46,8 @@ namespace Robust.Client
public string? LastDisconnectReason { get; private set; }
private (TimeSpan, GameTick) _timeBase;
/// <inheritdoc />
public void Initialize()
{
@@ -53,20 +55,34 @@ namespace Robust.Client
_net.ConnectFailed += OnConnectFailed;
_net.Disconnect += OnNetDisconnect;
_net.RegisterNetMessage<MsgSyncTimeBase>(
SyncTimeBase,
NetMessageAccept.Handshake | NetMessageAccept.Client);
_configManager.OnValueChanged(CVars.NetTickrate, TickRateChanged, invokeImmediately: true);
_playMan.Initialize();
Reset();
}
private void TickRateChanged(int tickrate)
private void SyncTimeBase(MsgSyncTimeBase message)
{
Logger.DebugS("client", $"Synchronized time base: {message.Tick}: {message.Time}");
if (RunLevel >= ClientRunLevel.Connected)
_timing.TimeBase = (message.Time, message.Tick);
else
_timeBase = (message.Time, message.Tick);
}
private void TickRateChanged(int tickrate, in CVarChangeInfo info)
{
if (GameInfo != null)
{
GameInfo.TickRate = (byte) tickrate;
}
_timing.TickRate = (byte) tickrate;
_timing.SetTickRateAt((byte) tickrate, info.TickChanged);
Logger.InfoS("client", $"Tickrate changed to: {tickrate} on tick {_timing.CurTick}");
}
@@ -75,7 +91,7 @@ namespace Robust.Client
{
if (RunLevel == ClientRunLevel.Connecting)
{
_net.Shutdown("Client mashing that connect button.");
_net.Reset("Client mashing that connect button.");
Reset();
}
@@ -213,9 +229,8 @@ namespace Robust.Client
{
_entityManager.Startup();
_mapManager.Startup();
_entityLookup.Startup();
_timing.ResetSimTime();
_timing.ResetSimTime(_timeBase);
_timing.Paused = false;
}
@@ -224,7 +239,6 @@ namespace Robust.Client
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
_gameStates.Reset();
_playMan.Shutdown();
_entityLookup.Shutdown();
_entityManager.Shutdown();
_mapManager.Shutdown();
_discord.ClearPresence();

View File

@@ -11,6 +11,7 @@ using Robust.Client.Input;
using Robust.Client.Map;
using Robust.Client.Placement;
using Robust.Client.Player;
using Robust.Client.Profiling;
using Robust.Client.Prototypes;
using Robust.Client.Reflection;
using Robust.Client.ResourceManagement;
@@ -47,7 +48,6 @@ namespace Robust.Client
IoCManager.Register<IMapManagerInternal, NetworkedMapManager>();
IoCManager.Register<INetworkedMapManager, NetworkedMapManager>();
IoCManager.Register<IEntityManager, ClientEntityManager>();
IoCManager.Register<IEntityLookup, EntityLookup>();
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
@@ -62,6 +62,7 @@ namespace Robust.Client
IoCManager.Register<IResourceCache, ResourceCache>();
IoCManager.Register<IResourceCacheInternal, ResourceCache>();
IoCManager.Register<IClientNetManager, NetManager>();
IoCManager.Register<EntityManager, ClientEntityManager>();
IoCManager.Register<IClientEntityManager, ClientEntityManager>();
IoCManager.Register<IClientEntityManagerInternal, ClientEntityManager>();
IoCManager.Register<IEntityNetworkManager, ClientEntityManager>();
@@ -72,11 +73,11 @@ namespace Robust.Client
IoCManager.Register<IStateManager, StateManager>();
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
IoCManager.Register<IDebugDrawing, DebugDrawing>();
IoCManager.Register<ILightManager, LightManager>();
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IMidiManager, MidiManager>();
IoCManager.Register<IAuthManager, AuthManager>();
IoCManager.Register<ProfViewManager>();
IoCManager.Register<IPhysicsManager, PhysicsManager>();
switch (mode)
{

View File

@@ -0,0 +1,94 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Robust.Shared;
using Robust.Shared.Console;
using Robust.Shared.Network.Messages;
namespace Robust.Client.Console;
internal sealed partial class ClientConsoleHost
{
private readonly Dictionary<int, PendingCompletion> _completionsPending = new();
private int _completionSeq;
public async Task<CompletionResult> GetCompletions(List<string> args, CancellationToken cancel)
{
// Last element is the command currently being typed. May be empty.
// Logger.Debug($"Running completions: {string.Join(", ", args)}");
var delay = _cfg.GetCVar(CVars.ConCompletionDelay);
if (delay > 0)
await Task.Delay((int)(delay * 1000), cancel);
return await CalcCompletions(args, cancel);
}
private Task<CompletionResult> CalcCompletions(List<string> args, CancellationToken cancel)
{
if (args.Count == 1)
{
// Typing out command name, handle this ourselves.
var cmdOptions = CompletionResult.FromOptions(
RegisteredCommands.Values.Select(c => new CompletionOption(c.Command, c.Description)));
return Task.FromResult(cmdOptions);
}
if (!RegisteredCommands.TryGetValue(args[0], out var cmd))
return Task.FromResult(CompletionResult.Empty);
return cmd.GetCompletionAsync(LocalShell, args.ToArray()[1..], cancel).AsTask();
}
private Task<CompletionResult> DoServerCompletions(List<string> args, CancellationToken cancel)
{
var tcs = new TaskCompletionSource<CompletionResult>();
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancel);
var seq = _completionSeq++;
var pending = new PendingCompletion
{
Cts = cts,
Tcs = tcs
};
var msg = new MsgConCompletion
{
Args = args.ToArray(),
Seq = seq
};
cts.Token.Register(() =>
{
tcs.SetCanceled(cts.Token);
cts.Dispose();
_completionsPending.Remove(seq);
}, true);
NetManager.ClientSendMessage(msg);
_completionsPending.Add(seq, pending);
return tcs.Task;
}
private void ProcessCompletionResp(MsgConCompletionResp message)
{
if (!_completionsPending.TryGetValue(message.Seq, out var pending))
return;
pending.Cts.Dispose();
pending.Tcs.SetResult(message.Result);
_completionsPending.Remove(message.Seq);
}
private struct PendingCompletion
{
public TaskCompletionSource<CompletionResult> Tcs;
public CancellationTokenSource Cts;
}
}

View File

@@ -1,10 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Robust.Client.Log;
using Robust.Client.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
@@ -41,18 +46,23 @@ namespace Robust.Client.Console
}
/// <inheritdoc cref="IClientConsoleHost" />
internal sealed class ClientConsoleHost : ConsoleHost, IClientConsoleHost
internal sealed partial class ClientConsoleHost : ConsoleHost, IClientConsoleHost, IConsoleHostInternal
{
[Dependency] private readonly IClientConGroupController _conGroup = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private bool _requestedCommands;
public ClientConsoleHost() : base(isServer: false) {}
/// <inheritdoc />
public void Initialize()
{
NetManager.RegisterNetMessage<MsgConCmdReg>(HandleConCmdReg);
NetManager.RegisterNetMessage<MsgConCmdAck>(HandleConCmdAck);
NetManager.RegisterNetMessage<MsgConCmd>(ProcessCommand);
NetManager.RegisterNetMessage<MsgConCompletion>();
NetManager.RegisterNetMessage<MsgConCompletionResp>(ProcessCompletionResp);
_requestedCommands = false;
NetManager.Connected += OnNetworkConnected;
@@ -88,6 +98,11 @@ namespace Robust.Client.Console
OutputText(text, true, true);
}
public bool IsCmdServer(IConsoleCommand cmd)
{
return cmd is ServerDummyCommand;
}
public override event ConAnyCommandCallback? AnyCommandExecuted;
/// <inheritdoc />
@@ -108,14 +123,14 @@ namespace Robust.Client.Console
if (AvailableCommands.ContainsKey(commandName))
{
#if !DEBUG
var playerManager = IoCManager.Resolve<IPlayerManager>();
if (!_conGroup.CanCommand(commandName) && playerManager.LocalPlayer?.Session.Status > SessionStatus.Connecting)
{
WriteError(null, $"Insufficient perms for command: {commandName}");
return;
}
#endif
var command1 = AvailableCommands[commandName];
args.RemoveAt(0);
var shell = new ConsoleShell(this, null);
@@ -134,7 +149,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,36 +213,69 @@ namespace Robust.Client.Console
if (!NetManager.IsConnected)
return;
var msg = NetManager.CreateNetMessage<MsgConCmdReg>();
var msg = new MsgConCmdReg();
NetManager.ClientSendMessage(msg);
_requestedCommands = true;
}
}
/// <summary>
/// These dummies are made purely so list and help can list server-side commands.
/// </summary>
[Reflect(false)]
internal sealed class ServerDummyCommand : IConsoleCommand
{
internal ServerDummyCommand(string command, string help, string description)
/// <summary>
/// These dummies are made purely so list and help can list server-side commands.
/// </summary>
[Reflect(false)]
private sealed class ServerDummyCommand : IConsoleCommand
{
Command = command;
Help = help;
Description = description;
internal ServerDummyCommand(string command, string help, string description)
{
Command = command;
Help = help;
Description = description;
}
public string Command { get; }
public string Description { get; }
public string Help { get; }
// Always forward to server.
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
shell.RemoteExecuteCommand(argStr);
}
public async ValueTask<CompletionResult> GetCompletionAsync(
IConsoleShell shell,
string[] args,
CancellationToken cancel)
{
var host = (ClientConsoleHost)shell.ConsoleHost;
var argsList = args.ToList();
argsList.Insert(0, Command);
return await host.DoServerCompletions(argsList, cancel);
}
}
public string Command { get; }
public string Description { get; }
public string Help { get; }
// Always forward to server.
public void Execute(IConsoleShell shell, string argStr, string[] args)
private sealed class RemoteExecCommand : IConsoleCommand
{
shell.RemoteExecuteCommand(argStr);
public string Command => ">";
public string Description => Loc.GetString("cmd-remoteexec-desc");
public string Help => Loc.GetString("cmd-remoteexec-help");
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
shell.RemoteExecuteCommand(argStr["> ".Length..]);
}
public async ValueTask<CompletionResult> GetCompletionAsync(
IConsoleShell shell,
string[] args,
CancellationToken cancel)
{
var host = (ClientConsoleHost)shell.ConsoleHost;
return await host.DoServerCompletions(args.ToList(), cancel);
}
}
}
}

View File

@@ -1,5 +1,3 @@
using System;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
@@ -7,57 +5,6 @@ using Robust.Shared.IoC;
namespace Robust.Client.Console.Commands
{
[UsedImplicitly]
internal sealed class CVarCommand : SharedCVarCommand, IConsoleCommand
{
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 1 || args.Length > 2)
{
shell.WriteError("Must provide exactly one or two arguments.");
return;
}
var configManager = IoCManager.Resolve<IConfigurationManager>();
var name = args[0];
if (name == "?")
{
var cvars = configManager.GetRegisteredCVars().OrderBy(c => c);
shell.WriteLine(string.Join("\n", cvars));
return;
}
if (!configManager.IsCVarRegistered(name))
{
shell.WriteError($"CVar '{name}' is not registered. Use 'cvar ?' to get a list of all registered CVars.");
return;
}
if (args.Length == 1)
{
// Read CVar
var value = configManager.GetCVar<object>(name);
shell.WriteLine(value.ToString() ?? "");
}
else
{
// Write CVar
var value = args[1];
var type = configManager.GetCVarType(name);
try
{
var parsed = ParseObject(type, value);
configManager.SetCVar(name, parsed);
}
catch (FormatException)
{
shell.WriteLine($"Input value is in incorrect format for type {type}");
}
}
}
}
[UsedImplicitly]
public sealed class SaveConfig : IConsoleCommand
{

View File

@@ -168,7 +168,7 @@ namespace Robust.Client.Console.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = IoCManager.Resolve<IDebugDrawing>();
var mgr = EntitySystem.Get<DebugDrawingSystem>();
mgr.DebugPositions = !mgr.DebugPositions;
}
}
@@ -739,103 +739,6 @@ namespace Robust.Client.Console.Commands
}
}
internal sealed class GcCommand : IConsoleCommand
{
public string Command => "gc";
public string Description => "Run the GC.";
public string Help => "gc [generation]";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length == 0)
{
GC.Collect();
}
else
{
if (int.TryParse(args[0], out int result))
GC.Collect(result);
else
shell.WriteError("Failed to parse argument.");
}
}
}
internal sealed class GcFullCommand : IConsoleCommand
{
public string Command => "gcf";
public string Description => "Run the GC, fully, compacting LOH and everything.";
public string Help => "gcf";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect(2, GCCollectionMode.Forced, true, true);
}
}
internal sealed class GcModeCommand : IConsoleCommand
{
public string Command => "gc_mode";
public string Description => "Change/Read the GC Latency mode.";
public string Help => "gc_mode\nSee current GC Latencymode\ngc_mode [type]\n Change GC Latency mode to [type]";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var prevMode = GCSettings.LatencyMode;
if (args.Length == 0)
{
shell.WriteLine($"current gc latency mode: {(int) prevMode} ({prevMode})");
shell.WriteLine("possible modes:");
foreach (int mode in (int[]) Enum.GetValues(typeof(GCLatencyMode)))
{
shell.WriteLine($" {mode}: {Enum.GetName(typeof(GCLatencyMode), mode)}");
}
}
else
{
GCLatencyMode mode;
if (char.IsDigit(args[0][0]) && int.TryParse(args[0], out var modeNum))
{
mode = (GCLatencyMode) modeNum;
}
else if (!Enum.TryParse(args[0], true, out mode))
{
shell.WriteLine($"unknown gc latency mode: {args[0]}");
return;
}
shell.WriteLine($"attempting gc latency mode change: {(int) prevMode} ({prevMode}) -> {(int) mode} ({mode})");
GCSettings.LatencyMode = mode;
shell.WriteLine($"resulting gc latency mode: {(int) GCSettings.LatencyMode} ({GCSettings.LatencyMode})");
}
}
}
internal sealed class SerializeStatsCommand : IConsoleCommand
{
public string Command => "szr_stats";
public string Description => "Report serializer statistics.";
public string Help => "szr_stats";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
shell.WriteLine($"serialized: {RobustSerializer.BytesSerialized} bytes, {RobustSerializer.ObjectsSerialized} objects");
shell.WriteLine($"largest serialized: {RobustSerializer.LargestObjectSerializedBytes} bytes, {RobustSerializer.LargestObjectSerializedType} objects");
shell.WriteLine($"deserialized: {RobustSerializer.BytesDeserialized} bytes, {RobustSerializer.ObjectsDeserialized} objects");
shell.WriteLine($"largest serialized: {RobustSerializer.LargestObjectDeserializedBytes} bytes, {RobustSerializer.LargestObjectDeserializedType} objects");
}
}
internal sealed class ChunkInfoCommand : IConsoleCommand
{
public string Command => "chunkinfo";
@@ -861,7 +764,7 @@ namespace Robust.Client.Console.Commands
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
var chunk = internalGrid.GetChunk(chunkIndex);
shell.WriteLine($"worldBounds: {chunk.CalcWorldAABB()} localBounds: {chunk.CalcLocalBounds()}");
shell.WriteLine($"worldBounds: {internalGrid.CalcWorldAABB(chunk)} localBounds: {chunk.CachedBounds}");
}
}

View File

@@ -1,73 +0,0 @@
using System.Linq;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Network;
namespace Robust.Client.Console.Commands
{
sealed class HelpCommand : IConsoleCommand
{
public string Command => "help";
public string Help => "When no arguments are provided, displays a generic help text. When an argument is passed, display the help text for the command with that name.";
public string Description => "Display help text.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
switch (args.Length)
{
case 0:
shell.WriteLine("To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'.");
break;
case 1:
string commandname = args[0];
if (!shell.ConsoleHost.RegisteredCommands.ContainsKey(commandname))
{
if (!IoCManager.Resolve<IClientNetManager>().IsConnected)
{
// No server so nothing to respond with unknown command.
shell.WriteError("Unknown command: " + commandname);
return;
}
// TODO: Maybe have a server side help?
return;
}
IConsoleCommand command = shell.ConsoleHost.RegisteredCommands[commandname];
shell.WriteLine(string.Format("{0} - {1}", command.Command, command.Description));
shell.WriteLine(command.Help);
break;
default:
shell.WriteError("Invalid amount of arguments.");
break;
}
}
}
sealed class ListCommand : IConsoleCommand
{
public string Command => "list";
public string Help => "Usage: list [filter]\n" +
"Lists all available commands, and their short descriptions.\n" +
"If a filter is provided, " +
"only commands that contain the given string in their name will be listed.";
public string Description => "List all commands, optionally with a filter.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var filter = "";
if (args.Length == 1)
{
filter = args[0];
}
var conGroup = IoCManager.Resolve<IClientConGroupController>();
foreach (var command in shell.ConsoleHost.RegisteredCommands.Values
.Where(p => p.Command.Contains(filter) && (p is not ServerDummyCommand || conGroup.CanCommand(p.Command)))
.OrderBy(c => c.Command))
{
shell.WriteLine(command.Command + ": " + command.Description);
}
}
}
}

View File

@@ -1,27 +0,0 @@
using System.Linq;
using System.Runtime.Loader;
using JetBrains.Annotations;
using Robust.Shared.Console;
namespace Robust.Client.Console.Commands
{
[UsedImplicitly]
internal sealed class ListAssembliesCommand : IConsoleCommand
{
public string Command => "lsasm";
public string Description => "Lists loaded assemblies by load context.";
public string Help => Command;
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
foreach (var context in AssemblyLoadContext.All)
{
shell.WriteLine($"{context.Name}:");
foreach (var assembly in context.Assemblies.OrderBy(a => a.FullName))
{
shell.WriteLine($" {assembly.FullName}");
}
}
}
}
}

View File

@@ -1,73 +0,0 @@
using Robust.Shared.Log;
using System;
using Robust.Shared.Console;
namespace Robust.Client.Console.Commands
{
sealed class LogSetLevelCommand : IConsoleCommand
{
public string Command => "loglevel";
public string Description => "Changes the log level for a provided sawmill.";
public string Help => "Usage: loglevel <sawmill> <level>"
+ "\n sawmill: A label prefixing log messages. This is the one you're setting the level for."
+ "\n level: The log level. Must match one of the values of the LogLevel enum.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2)
{
shell.WriteError("Invalid argument amount. Expected 2 arguments.");
return;
}
var name = args[0];
var levelname = args[1];
LogLevel? level;
if (levelname == "null")
{
level = null;
}
else
{
if (!Enum.TryParse<LogLevel>(levelname, out var result))
{
shell.WriteLine("Failed to parse 2nd argument. Must be one of the values of the LogLevel enum.");
return;
}
level = result;
}
Logger.GetSawmill(name).Level = level;
}
}
sealed class TestLog : IConsoleCommand
{
public string Command => "testlog";
public string Description => "Writes a test log to a sawmill.";
public string Help => "Usage: testlog <sawmill> <level> <message>"
+ "\n sawmill: A label prefixing the logged message."
+ "\n level: The log level. Must match one of the values of the LogLevel enum."
+ "\n message: The message to be logged. Wrap this in double quotes if you want to use spaces.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 3)
{
shell.WriteError("Invalid argument amount. Expected 3 arguments.");
return;
}
var name = args[0];
var levelname = args[1];
var message = args[2]; // yes this doesn't support spaces idgaf.
if (!Enum.TryParse<LogLevel>(levelname, out var result))
{
shell.WriteLine("Failed to parse 2nd argument. Must be one of the values of the LogLevel enum.");
return;
}
var level = result;
Logger.LogS(level, name, message);
}
}
}

View File

@@ -1,6 +1,7 @@
using Robust.Client.Debugging;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
namespace Robust.Client.Console.Commands
{
@@ -8,12 +9,13 @@ 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(Loc.GetString("shell-wrong-arguments-number-need-specific", ("properAmount", 1), ("currentAmount", args.Length)));
return;
}
@@ -43,11 +45,27 @@ namespace Robust.Client.Console.Commands
system.Flags ^= PhysicsDebugFlags.Shapes;
break;
default:
shell.WriteLine($"{args[0]} is not a recognised overlay");
shell.WriteError(Loc.GetString("cmd-physics-overlay", ("overlay", args[0])));
return;
}
return;
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length != 1) return CompletionResult.Empty;
return CompletionResult.FromOptions(new[]
{
"aabbs",
"com",
"contactnormals",
"contactpoints",
"joints",
"shapeinfo",
"shapes",
});
}
}
}

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

@@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Robust.Shared.Console;
using Robust.Shared.Utility;
@@ -15,5 +18,7 @@ namespace Robust.Client.Console
event EventHandler<AddFormattedMessageArgs> AddFormatted;
void AddFormattedLine(FormattedMessage message);
Task<CompletionResult> GetCompletions(List<string> args, CancellationToken cancel);
}
}

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

@@ -6,17 +6,21 @@ using Robust.Shared.Maths;
namespace Robust.Client.Debugging
{
/// <inheritdoc />
public sealed class DebugDrawing : IDebugDrawing
/// <summary>
/// A collection of visual debug overlays for the client game.
/// </summary>
public sealed class DebugDrawingSystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
private bool _debugPositions;
/// <inheritdoc />
/// <summary>
/// Toggles the visual overlay of the local origin for each entity on screen.
/// </summary>
public bool DebugPositions
{
get => _debugPositions;
@@ -42,13 +46,13 @@ namespace Robust.Client.Debugging
private sealed class EntityPositionOverlay : Overlay
{
private readonly IEntityLookup _lookup;
private readonly EntityLookupSystem _lookup;
private readonly IEyeManager _eyeManager;
private readonly IEntityManager _entityManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(IEntityLookup lookup, IEyeManager eyeManager, IEntityManager entityManager)
public EntityPositionOverlay(EntityLookupSystem lookup, IEyeManager eyeManager, IEntityManager entityManager)
{
_lookup = lookup;
_eyeManager = eyeManager;
@@ -61,13 +65,11 @@ namespace Robust.Client.Debugging
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
var viewport = _eyeManager.GetWorldViewport();
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
foreach (var entity in _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, viewport))
{
var transform = _entityManager.GetComponent<TransformComponent>(entity);
var center = transform.WorldPosition;
var worldRotation = transform.WorldRotation;
var (center, worldRotation) = xformQuery.GetComponent(entity).GetWorldPositionRotation();
var xLine = worldRotation.RotateVec(Vector2.UnitX);
var yLine = worldRotation.RotateVec(Vector2.UnitY);

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

@@ -1,13 +0,0 @@
namespace Robust.Client.Debugging
{
/// <summary>
/// A collection of visual debug overlays for the client game.
/// </summary>
public interface IDebugDrawing
{
/// <summary>
/// Toggles the visual overlay of the local origin for each entity on screen.
/// </summary>
bool DebugPositions { get; set; }
}
}

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)
{
Interlocked.Decrement(ref _hasRedialled);
throw;
}
Shutdown("Redial");
}
}

View File

@@ -21,14 +21,16 @@ using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.Exceptions;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Profiling;
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;
@@ -50,7 +52,6 @@ namespace Robust.Client
[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly ITimerManager _timerManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IOverlayManagerInternal _overlayManager = default!;
@@ -67,6 +68,9 @@ 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!;
[Dependency] private readonly ProfManager _prof = default!;
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
private IWebViewManagerHook? _webViewHook;
@@ -92,7 +96,8 @@ namespace Robust.Client
_clyde.InitializePostWindowing();
_clydeAudio.InitializePostWindowing();
_clyde.SetWindowTitle(Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox");
_clyde.SetWindowTitle(
Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox");
_taskManager.Initialize();
_fontManager.SetFontDpi((uint)_configurationManager.GetCVar(CVars.DisplayFontDpi));
@@ -103,7 +108,8 @@ namespace Robust.Client
// Disable load context usage on content start.
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
_modLoader.SetUseLoadContext(!ContentStart);
_modLoader.SetEnableSandboxing(Options.Sandboxing);
var disableSandbox = Environment.GetEnvironmentVariable("ROBUST_DISABLE_SANDBOX") == "1";
_modLoader.SetEnableSandboxing(!disableSandbox && Options.Sandboxing);
var assemblyPrefix = Options.ContentModulePrefix ?? _resourceManifest!.AssemblyPrefix ?? "Content.";
if (!_modLoader.TryLoadModulesFrom(Options.AssemblyDirectory, assemblyPrefix))
@@ -132,8 +138,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();
@@ -158,7 +165,7 @@ namespace Robust.Client
// Setup main loop
if (_mainLoop == null)
{
_mainLoop = new GameLoop(_gameTiming)
_mainLoop = new GameLoop(_gameTiming, _runtimeLog, _prof)
{
SleepMode = displayMode == DisplayMode.Headless ? SleepMode.Delay : SleepMode.None
};
@@ -198,7 +205,7 @@ namespace Robust.Client
_clyde.Ready();
if (!Options.DisableCommandLineConnect &&
if (_resourceManifest!.AutoConnect &&
(_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
&& LaunchState.ConnectEndpoint != null)
{
@@ -214,7 +221,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)
@@ -224,7 +231,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)
{
@@ -259,7 +266,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)
@@ -327,6 +338,12 @@ namespace Robust.Client
ProfileOptSetup.Setup(_configurationManager);
_parallelMgr.Initialize();
_prof.Initialize();
#if !FULL_RELEASE
_configurationManager.OverrideDefault(CVars.ProfEnabled, true);
#endif
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
var mountOptions = _commandLineArgs != null
@@ -447,52 +464,127 @@ namespace Robust.Client
private void Input(FrameEventArgs frameEventArgs)
{
_clyde.ProcessInput(frameEventArgs);
_networkManager.ProcessPackets();
_taskManager.ProcessPendingTasks(); // tasks like connect
using (_prof.Group("Input Events"))
{
_clyde.ProcessInput(frameEventArgs);
}
using (_prof.Group("Network"))
{
_networkManager.ProcessPackets();
}
using (_prof.Group("Async"))
{
_taskManager.ProcessPendingTasks(); // tasks like connect
}
}
private void Tick(FrameEventArgs frameEventArgs)
{
_modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs);
_timerManager.UpdateTimers(frameEventArgs);
_taskManager.ProcessPendingTasks();
using (_prof.Group("Content pre engine"))
{
_modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs);
}
using (_prof.Group("Console"))
{
_console.CommandBufferExecute();
}
using (_prof.Group("Timers"))
{
_timerManager.UpdateTimers(frameEventArgs);
}
using (_prof.Group("Async"))
{
_taskManager.ProcessPendingTasks();
}
// GameStateManager is in full control of the simulation update in multiplayer.
if (_client.RunLevel == ClientRunLevel.InGame || _client.RunLevel == ClientRunLevel.Connected)
{
_gameStateManager.ApplyGameState();
using (_prof.Group("Game state"))
{
_gameStateManager.ApplyGameState();
}
}
// In singleplayer, however, we're in full control instead.
else if (_client.RunLevel == ClientRunLevel.SinglePlayerGame)
{
// The last real tick is the current tick! This way we won't be in "prediction" mode.
_gameTiming.LastRealTick = _gameTiming.CurTick;
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false);
_lookup.Update();
using (_prof.Group("Entity"))
{
// The last real tick is the current tick! This way we won't be in "prediction" mode.
_gameTiming.LastRealTick = _gameTiming.CurTick;
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false);
}
}
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
using (_prof.Group("Content post engine"))
{
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
}
}
private void Update(FrameEventArgs frameEventArgs)
{
_webViewHook?.Update();
_clydeAudio.FrameProcess(frameEventArgs);
_clyde.FrameProcess(frameEventArgs);
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePreEngine, frameEventArgs);
_stateManager.FrameUpdate(frameEventArgs);
if (_webViewHook != null)
{
using (_prof.Group("WebView"))
{
_webViewHook?.Update();
}
}
using (_prof.Group("ClydeAudio"))
{
_clydeAudio.FrameProcess(frameEventArgs);
}
using (_prof.Group("Clyde"))
{
_clyde.FrameProcess(frameEventArgs);
}
using (_prof.Group("Content Pre Engine"))
{
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePreEngine, frameEventArgs);
}
using (_prof.Group("State"))
{
_stateManager.FrameUpdate(frameEventArgs);
}
if (_client.RunLevel >= ClientRunLevel.Connected)
{
_placementManager.FrameUpdate(frameEventArgs);
_entityManager.FrameUpdate(frameEventArgs.DeltaSeconds);
using (_prof.Group("Placement"))
{
_placementManager.FrameUpdate(frameEventArgs);
}
using (_prof.Group("Entity"))
{
_entityManager.FrameUpdate(frameEventArgs.DeltaSeconds);
}
}
_overlayManager.FrameUpdate(frameEventArgs);
_userInterfaceManager.FrameUpdate(frameEventArgs);
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs);
using (_prof.Group("Overlay"))
{
_overlayManager.FrameUpdate(frameEventArgs);
}
using (_prof.Group("UI"))
{
_userInterfaceManager.FrameUpdate(frameEventArgs);
}
using (_prof.Group("Content Post Engine"))
{
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs);
}
}
internal static void SetupLogging(ILogManager logManager, Func<ILogHandler> logHandlerFactory)
@@ -510,7 +602,7 @@ namespace Robust.Client
logManager.GetSawmill("discord").Level = LogLevel.Warning;
logManager.GetSawmill("net.predict").Level = LogLevel.Info;
logManager.GetSawmill("szr").Level = LogLevel.Info;
logManager.GetSawmill("loc").Level = LogLevel.Error;
logManager.GetSawmill("loc").Level = LogLevel.Warning;
#if DEBUG_ONLY_FCE_INFO
#if DEBUG_ONLY_FCE_LOG
@@ -575,7 +667,6 @@ namespace Robust.Client
_networkManager.Shutdown("Client shutting down");
_midiManager.Shutdown();
IoCManager.Resolve<IEntityLookup>().Shutdown();
_entityManager.Shutdown();
_clyde.Shutdown();
_clydeAudio.Shutdown();
@@ -586,7 +677,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

@@ -27,7 +27,6 @@ namespace Robust.Client.GameObjects
public override void Initialize()
{
SetupNetworking();
ReceivedComponentMessage += (_, compMsg) => DispatchComponentMessage(compMsg);
ReceivedSystemMessage += (_, systemMsg) => EventBus.RaiseEvent(EventSource.Network, systemMsg);
base.Initialize();
@@ -38,9 +37,9 @@ namespace Robust.Client.GameObjects
return base.CreateEntity(prototypeName, uid);
}
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity)
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity, MetaDataComponent? meta)
{
base.InitializeEntity(entity);
base.InitializeEntity(entity, meta);
}
void IClientEntityManagerInternal.StartEntity(EntityUid entity)
@@ -52,9 +51,6 @@ namespace Robust.Client.GameObjects
public override IEntityNetworkManager EntityNetManager => this;
/// <inheritdoc />
public event EventHandler<NetworkComponentMessage>? ReceivedComponentMessage;
/// <inheritdoc />
public event EventHandler<object>? ReceivedSystemMessage;
@@ -90,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;
@@ -105,26 +101,6 @@ namespace Robust.Client.GameObjects
throw new NotSupportedException();
}
/// <inheritdoc />
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
public void SendComponentNetworkMessage(INetChannel? channel, EntityUid entity, IComponent component, ComponentMessage message)
{
var componentType = component.GetType();
var netId = ComponentFactory.GetRegistration(componentType).NetID;
if (!netId.HasValue)
throw new ArgumentException($"Component {componentType} does not have a NetID.", nameof(component));
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.ComponentMessage;
msg.EntityUid = entity;
msg.NetId = netId.Value;
msg.ComponentMessage = message;
msg.SourceTick = _gameTiming.CurTick;
_networkManager.ClientSendMessage(msg);
}
private void HandleEntityNetworkMessage(MsgEntity message)
{
if (message.SourceTick <= _gameStateManager.CurServerTick)
@@ -143,10 +119,6 @@ namespace Robust.Client.GameObjects
{
switch (message.Type)
{
case EntityMessageType.ComponentMessage:
ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message));
return;
case EntityMessageType.SystemMessage:
var msg = message.SystemMessage;
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());

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

@@ -8,7 +8,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[ComponentReference(typeof(OccluderComponent))]
internal sealed class ClientOccluderComponent : OccluderComponent
public sealed class ClientOccluderComponent : OccluderComponent
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
@@ -71,13 +71,29 @@ namespace Robust.Client.GameObjects
{
Occluding = OccluderDir.None;
if (Deleted || !_entityManager.GetComponent<TransformComponent>(Owner).Anchored)
if (Deleted)
return;
// Content may want to override the default behavior for occlusion.
var xform = _entityManager.GetComponent<TransformComponent>(Owner);
var ev = new OccluderDirectionsEvent
{
Component = xform,
};
_entityManager.EventBus.RaiseLocalEvent(Owner, ref ev);
if (ev.Handled)
{
Occluding = ev.Directions;
return;
}
var grid = _mapManager.GetGrid(_entityManager.GetComponent<TransformComponent>(Owner).GridID);
var position = _entityManager.GetComponent<TransformComponent>(Owner).Coordinates;
if (!xform.Anchored)
return;
var grid = _mapManager.GetGrid(xform.GridID);
var position = xform.Coordinates;
void CheckDir(Direction dir, OccluderDir oclDir)
{
foreach (var neighbor in grid.GetInDir(position, dir))
@@ -90,7 +106,7 @@ namespace Robust.Client.GameObjects
}
}
var angle = _entityManager.GetComponent<TransformComponent>(Owner).LocalRotation;
var angle = xform.LocalRotation;
var dirRolling = angle.GetCardinalDir();
// dirRolling starts at effective south
@@ -105,15 +121,28 @@ namespace Robust.Client.GameObjects
CheckDir(dirRolling, OccluderDir.East);
}
}
[Flags]
internal enum OccluderDir : byte
{
None = 0,
North = 1,
East = 1 << 1,
South = 1 << 2,
West = 1 << 3,
}
[Flags]
public enum OccluderDir : byte
{
None = 0,
North = 1,
East = 1 << 1,
South = 1 << 2,
West = 1 << 3,
}
/// <summary>
/// Raised by occluders when trying to get occlusion directions.
/// </summary>
[ByRefEvent]
public struct OccluderDirectionsEvent
{
public bool Handled = false;
public OccluderDir Directions = OccluderDir.None;
public TransformComponent Component = default!;
public OccluderDirectionsEvent() {}
}
}

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

@@ -25,6 +25,8 @@ namespace Robust.Client.GameObjects
[Animatable]
Vector2 Scale { get; set; }
Box2 Bounds { get; }
/// <summary>
/// A rotation applied to all layers.
/// </summary>
@@ -119,7 +121,8 @@ namespace Robust.Client.GameObjects
/// This is useful to allow layer map configs to be defined in prototypes,
/// while still allowing code to create configs if they're absent.
/// </remarks>
void LayerMapReserveBlank(object key);
/// <returns>Index of the new layer.</returns>
int LayerMapReserveBlank(object key);
/// <summary>
/// Adds a layer without texture (thus falling back to the error texture).
@@ -145,8 +148,8 @@ namespace Robust.Client.GameObjects
void RemoveLayer(int layer);
void RemoveLayer(object layerKey);
void LayerSetShader(int layer, ShaderInstance shader);
void LayerSetShader(object layerKey, ShaderInstance shader);
void LayerSetShader(int layer, ShaderInstance shader, string? prototype = null);
void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null);
void LayerSetShader(int layer, string shaderName);
void LayerSetShader(object layerKey, string shaderName);
@@ -217,8 +220,8 @@ namespace Robust.Client.GameObjects
int GetLayerDirectionCount(ISpriteLayer layer);
/// <summary>
/// Calculate sprite bounding box in world-space coordinates.
/// Calculate the rotated sprite bounding box in world-space coordinates.
/// </summary>
Box2 CalculateBoundingBox(Vector2 worldPos);
Box2Rotated CalculateRotatedBoundingBox(Vector2 worldPosition, Angle worldRotation, IEye? eye = null);
}
}

View File

@@ -25,8 +25,6 @@ namespace Robust.Client.GameObjects
RSI.State.Direction EffectiveDirection(Angle worldRotation);
Vector2 LocalToLayer(Vector2 localPos);
/// <summary>
/// Layer size in pixels.
/// Don't account layer scale or sprite world transform.

View File

@@ -84,10 +84,16 @@ namespace Robust.Client.GameObjects
foreach (var sprite in comp.SpriteTree.QueryAabb(localAABB))
{
var worldPos = _entityManager.GetComponent<TransformComponent>(sprite.Owner).WorldPosition;
var bounds = sprite.CalculateBoundingBox(worldPos);
var (worldPos, worldRot) = _entityManager.GetComponent<TransformComponent>(sprite.Owner).GetWorldPositionRotation();
var bounds = sprite.CalculateRotatedBoundingBox(worldPos, worldRot);
// Get scaled down bounds used to indicate the "south" of a sprite.
var localBound = bounds.Box;
var smallLocal = localBound.Scale(0.2f).Translated(-new Vector2(0f, localBound.Extents.Y));
var southIndicator = new Box2Rotated(smallLocal, bounds.Rotation, bounds.Origin);
handle.DrawRect(bounds, Color.Red.WithAlpha(0.2f));
handle.DrawRect(bounds.Scale(0.2f).Translated(-new Vector2(0f, bounds.Extents.Y)), Color.Blue.WithAlpha(0.5f));
handle.DrawRect(southIndicator, Color.Blue.WithAlpha(0.5f));
}
}
}

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,14 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
internal sealed class AppearanceSystem : SharedAppearanceSystem
public sealed class AppearanceSystem : SharedAppearanceSystem
{
private readonly Queue<ClientAppearanceComponent> _queuedUpdates = new();
@@ -50,10 +52,33 @@ namespace Robust.Client.GameObjects
if (!stateDiff) return;
component.AppearanceData = actualState.Data;
component.AppearanceData = CloneAppearanceData(actualState.Data);
MarkDirty(component);
}
/// <summary>
/// Take in an appearance data dictionary and attempt to clone it.
/// </summary>
/// <remarks>
/// As some appearance data values are not simple value-type objects, this is not just a shallow clone.
/// </remarks>
private Dictionary<object, object> CloneAppearanceData(Dictionary<object, object> data)
{
Dictionary<object, object> newDict = new(data.Count);
foreach (var (key, value) in data)
{
if (value.GetType().IsValueType)
newDict[key] = value;
else if (value is ICloneable cloneable)
newDict[key] = cloneable.Clone();
else
throw new NotSupportedException("Invalid object in appearance data dictionary. Appearance data must be cloneable");
}
return newDict;
}
public override void MarkDirty(AppearanceComponent component)
{
if (component.AppearanceDirty)
@@ -70,17 +95,21 @@ namespace Robust.Client.GameObjects
public override void FrameUpdate(float frameTime)
{
var spriteQuery = GetEntityQuery<SpriteComponent>();
while (_queuedUpdates.TryDequeue(out var appearance))
{
if (appearance.Deleted)
continue;
OnChangeData(appearance.Owner, appearance);
// Sprite comp is allowed to be null, so that things like spriteless point-lights can use this system
spriteQuery.TryGetComponent(appearance.Owner, out var sprite);
OnChangeData(appearance.Owner, sprite, appearance);
UnmarkDirty(appearance);
}
}
public void OnChangeData(EntityUid uid, ClientAppearanceComponent? appearanceComponent = null)
public void OnChangeData(EntityUid uid, SpriteComponent? sprite, ClientAppearanceComponent? appearanceComponent = null)
{
if (!Resolve(uid, ref appearanceComponent, false)) return;
@@ -88,6 +117,7 @@ namespace Robust.Client.GameObjects
{
Component = appearanceComponent,
AppearanceData = appearanceComponent.AppearanceData,
Sprite = sprite,
};
// Give it AppearanceData so we can still keep the friend attribute on the component.
@@ -107,7 +137,8 @@ namespace Robust.Client.GameObjects
[ByRefEvent]
public struct AppearanceChangeEvent
{
public AppearanceComponent Component = default!;
public IReadOnlyDictionary<object, object> AppearanceData = default!;
public AppearanceComponent Component;
public IReadOnlyDictionary<object, object> AppearanceData;
public SpriteComponent? Sprite;
}
}

View File

@@ -144,75 +144,94 @@ namespace Robust.Client.GameObjects
{
stream.Source.SetVolume(-10000000);
}
else
else if (stream.Attenuation == Attenuation.NoAttenuation)
{
var sourceRelative = ourPos - pos.Position;
var occlusion = 0f;
if (sourceRelative.Length > 0)
{
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
pos.Position,
sourceRelative.Normalized,
OcclusionCollisionMask),
sourceRelative.Length,
stream.TrackingEntity);
}
var distance = MathF.Max(stream.ReferenceDistance, MathF.Min(sourceRelative.Length, stream.MaxDistance));
float gain;
// Technically these are formulas for gain not decibels but EHHHHHHHH.
switch (stream.Attenuation)
{
case Attenuation.Default:
gain = 1f;
break;
// You thought I'd implement clamping per source? Hell no that's just for the overall OpenAL setting
// I didn't even wanna implement this much for linear but figured it'd be cleaner.
case Attenuation.InverseDistanceClamped:
case Attenuation.InverseDistance:
gain = stream.ReferenceDistance /
(stream.ReferenceDistance + stream.RolloffFactor * (distance - stream.ReferenceDistance));
break;
case Attenuation.LinearDistanceClamped:
case Attenuation.LinearDistance:
gain = 1f - stream.RolloffFactor * (distance - stream.ReferenceDistance) /
(stream.MaxDistance - stream.ReferenceDistance);
break;
case Attenuation.ExponentDistanceClamped:
case Attenuation.ExponentDistance:
gain = MathF.Pow((distance / stream.ReferenceDistance),
(-stream.RolloffFactor));
break;
default:
throw new ArgumentOutOfRangeException($"No implemented attenuation for {stream.Attenuation.ToString()}");
}
var volume = MathF.Pow(10, stream.Volume / 10);
var actualGain = MathF.Max(0f, volume * gain);
stream.Source.SetVolumeDirect(actualGain);
stream.Source.SetOcclusion(occlusion);
}
SetAudioPos(stream, stream.Attenuation != Attenuation.NoAttenuation ? pos.Position : ourPos);
void SetAudioPos(PlayingStream stream, Vector2 pos)
{
if (!stream.Source.SetPosition(pos))
//TODO: OpenAL supports positional audio together with no attenuation, we should do too.
if (!stream.Source.SetPosition(ourPos))
{
Logger.Warning("Interrupting positional audio, can't set position.");
stream.Source.StopPlaying();
}
}
if (stream.TrackingEntity != default)
else
{
stream.Source.SetVelocity(stream.TrackingEntity.GlobalLinearVelocity());
var sourceRelative = ourPos - pos.Position;
// OpenAL uses MaxDistance to limit how much attenuation can *reduce* the gain,
// and doesn't do any culling. We however cull based on MaxDistance, because
// this is what all current code that uses MaxDistance expects and because
// we don't need the OpenAL behaviour.
if (sourceRelative.Length > stream.MaxDistance)
{
stream.Source.SetVolume(-10000000);
}
else
{
var occlusion = 0f;
if (sourceRelative.Length > 0)
{
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
pos.Position,
sourceRelative.Normalized,
OcclusionCollisionMask),
sourceRelative.Length,
stream.TrackingEntity);
}
// OpenAL also limits the distance to <= AL_MAX_DISTANCE, but since we cull
// sources that are further away than stream.MaxDistance, we don't do that.
var distance = MathF.Max(stream.ReferenceDistance, sourceRelative.Length);
float gain;
// Technically these are formulas for gain not decibels but EHHHHHHHH.
switch (stream.Attenuation)
{
case Attenuation.Default:
gain = 1f;
break;
// You thought I'd implement clamping per source? Hell no that's just for the overall OpenAL setting
// I didn't even wanna implement this much for linear but figured it'd be cleaner.
case Attenuation.InverseDistanceClamped:
case Attenuation.InverseDistance:
gain = stream.ReferenceDistance /
(stream.ReferenceDistance + stream.RolloffFactor *
(distance - stream.ReferenceDistance));
break;
case Attenuation.LinearDistanceClamped:
case Attenuation.LinearDistance:
gain = 1f - stream.RolloffFactor * (distance - stream.ReferenceDistance) /
(stream.MaxDistance - stream.ReferenceDistance);
break;
case Attenuation.ExponentDistanceClamped:
case Attenuation.ExponentDistance:
gain = MathF.Pow((distance / stream.ReferenceDistance),
(-stream.RolloffFactor));
break;
default:
throw new ArgumentOutOfRangeException(
$"No implemented attenuation for {stream.Attenuation.ToString()}");
}
var volume = MathF.Pow(10, stream.Volume / 10);
var actualGain = MathF.Max(0f, volume * gain);
stream.Source.SetVolumeDirect(actualGain);
stream.Source.SetOcclusion(occlusion);
if (!stream.Source.SetPosition(pos.Position))
{
Logger.Warning("Interrupting positional audio, can't set position.");
stream.Source.StopPlaying();
}
if (stream.TrackingEntity != default)
{
stream.Source.SetVelocity(stream.TrackingEntity.GlobalLinearVelocity());
}
}
}
}
}
@@ -266,6 +285,7 @@ namespace Robust.Client.GameObjects
source.SetGlobal();
source.StartPlaying();
// These defaults differ from AudioParams.Default
var playing = new PlayingStream
{
Source = source,

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

@@ -168,7 +168,7 @@ namespace Robust.Client.GameObjects
// This container is expecting an entity... but it got parented to some other entity???
// Ah well, the sever should send a new container state that updates expected entities so just ignore it for now.
return;
}
}
RemoveExpectedEntity(message.Entity);
@@ -210,68 +210,101 @@ namespace Robust.Client.GameObjects
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
var pointQuery = EntityManager.GetEntityQuery<PointLightComponent>();
var spriteQuery = EntityManager.GetEntityQuery<SpriteComponent>();
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
foreach (var toUpdate in _updateQueue)
{
if (EntityManager.Deleted(toUpdate))
{
if (Deleted(toUpdate))
continue;
}
UpdateEntityRecursively(toUpdate);
UpdateEntityRecursively(toUpdate, xformQuery, pointQuery, spriteQuery);
}
_updateQueue.Clear();
}
private void UpdateEntityRecursively(EntityUid entity)
private void UpdateEntityRecursively(
EntityUid entity,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<PointLightComponent> pointQuery,
EntityQuery<SpriteComponent> spriteQuery)
{
// TODO: Since we are recursing down,
// we could cache ShowContents data here to speed it up for children.
// Am lazy though.
UpdateEntity(entity);
// Recursively go up parents and containers to see whether both sprites and lights need to be occluded
// Could maybe optimise this more by checking nearest parent that has sprite / light and whether it's container
// occluded but this probably isn't a big perf issue.
var xform = xformQuery.GetComponent(entity);
var parent = xform.ParentUid;
var child = entity;
var spriteOccluded = false;
var lightOccluded = false;
foreach (var child in EntityManager.GetComponent<TransformComponent>(entity).Children)
while (parent.IsValid() && !spriteOccluded && !lightOccluded)
{
UpdateEntityRecursively(child.Owner);
var parentXform = xformQuery.GetComponent(parent);
if (TryComp<ContainerManagerComponent>(parent, out var manager) && manager.TryGetContainer(child, out var container))
{
spriteOccluded = spriteOccluded || !container.ShowContents;
lightOccluded = lightOccluded || container.OccludesLight;
}
child = parent;
parent = parentXform.ParentUid;
}
// Alright so
// This is the CBT bit.
// The issue is we need to go through the children and re-check whether they are or are not contained.
// if they are contained then the occlusion values may need updating for all those children
UpdateEntity(entity, xform, xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded);
}
private void UpdateEntity(EntityUid entity)
private void UpdateEntity(
EntityUid entity,
TransformComponent xform,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<PointLightComponent> pointQuery,
EntityQuery<SpriteComponent> spriteQuery,
bool spriteOccluded,
bool lightOccluded)
{
if (EntityManager.TryGetComponent(entity, out SpriteComponent? sprite))
if (spriteQuery.TryGetComponent(entity, out var sprite))
{
sprite.ContainerOccluded = false;
// We have to recursively scan for containers upwards in case of nested containers.
var tempParent = entity;
while (tempParent.TryGetContainer(out var container))
{
if (!container.ShowContents)
{
sprite.ContainerOccluded = true;
break;
}
tempParent = container.Owner;
}
sprite.ContainerOccluded = spriteOccluded;
}
if (EntityManager.TryGetComponent(entity, out PointLightComponent? light))
if (pointQuery.TryGetComponent(entity, out var light))
{
light.ContainerOccluded = false;
light.ContainerOccluded = lightOccluded;
}
// We have to recursively scan for containers upwards in case of nested containers.
var tempParent = entity;
while (tempParent.TryGetContainer(out var container))
var childEnumerator = xform.ChildEnumerator;
// Try to avoid TryComp if we already know stuff is occluded.
if ((!spriteOccluded || !lightOccluded) && TryComp<ContainerManagerComponent>(entity, out var manager))
{
while (childEnumerator.MoveNext(out var child))
{
if (container.OccludesLight)
// Thank god it's by value and not by ref.
var childSpriteOccluded = spriteOccluded;
var childLightOccluded = lightOccluded;
// We already know either sprite or light is not occluding so need to check container.
if (manager.TryGetContainer(child.Value, out var container))
{
light.ContainerOccluded = true;
break;
childSpriteOccluded = childSpriteOccluded || !container.ShowContents;
childLightOccluded = childLightOccluded || container.OccludesLight;
}
tempParent = container.Owner;
UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, childSpriteOccluded, childLightOccluded);
}
}
else
{
while (childEnumerator.MoveNext(out var child))
{
UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded);
}
}
}

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

@@ -25,7 +25,7 @@ namespace Robust.Client.GameObjects
if (_enabled)
{
_lightOverlay = new DebugLightOverlay(
IoCManager.Resolve<IEntityLookup>(),
EntitySystem.Get<EntityLookupSystem>(),
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IMapManager>(),
Get<RenderingTreeSystem>());
@@ -44,7 +44,7 @@ namespace Robust.Client.GameObjects
private sealed class DebugLightOverlay : Overlay
{
private IEntityLookup _lookup;
private EntityLookupSystem _lookup;
private IEyeManager _eyeManager;
private IMapManager _mapManager;
@@ -52,7 +52,7 @@ namespace Robust.Client.GameObjects
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugLightOverlay(IEntityLookup lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
public DebugLightOverlay(EntityLookupSystem lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
{
_lookup = lookup;
_eyeManager = eyeManager;
@@ -72,7 +72,7 @@ namespace Robust.Client.GameObjects
{
foreach (var light in tree.LightTree)
{
var aabb = _lookup.GetWorldAabbFromEntity(light.Owner);
var aabb = _lookup.GetWorldAABB(light.Owner);
if (!aabb.Intersects(worldBounds)) continue;
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.1f));

View File

@@ -323,7 +323,7 @@ namespace Robust.Client.GameObjects
{
private readonly IPlayerManager _playerManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
private readonly ShaderInstance _unshadedShader;
private readonly EffectSystem _owner;
@@ -342,7 +342,6 @@ namespace Robust.Client.GameObjects
var map = _owner.eyeManager.CurrentMap;
var worldHandle = args.WorldHandle;
ShaderInstance? currentShader = null;
if (_playerManager.LocalPlayer?.ControlledEntity is not {} playerEnt)
return;
@@ -362,13 +361,8 @@ namespace Robust.Client.GameObjects
continue;
}
var newShader = effect.Shaded ? null : _unshadedShader;
if (newShader != currentShader)
{
worldHandle.UseShader(newShader);
currentShader = newShader;
}
if (!effect.Shaded)
worldHandle.UseShader(_unshadedShader);
// TODO: Should be doing matrix transformations
var effectSprite = effect.EffectSprite;
@@ -377,6 +371,9 @@ namespace Robust.Client.GameObjects
(attachedXform?.Coordinates ?? effect.Coordinates)
.Offset(effect.AttachedOffset);
// If we've never seen the entity before then can't resolve coordinates.
if (!coordinates.IsValid(_entityManager)) continue;
// ???
var rotation = attachedXform?.WorldRotation ?? _entityManager.GetComponent<TransformComponent>(coordinates.EntityId).WorldRotation;
@@ -386,6 +383,9 @@ namespace Robust.Client.GameObjects
var rotatedBox = new Box2Rotated(effectArea, effect.Rotation + rotation, effectOrigin);
worldHandle.DrawTextureRect(effectSprite, rotatedBox, ToColor(effect.Color));
if (!effect.Shaded)
worldHandle.UseShader(null);
}
}
}

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,19 @@
using Robust.Client.Graphics;
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects;
internal class GridRenderingSystem : EntitySystem
{
private readonly IClydeInternal _clyde;
public GridRenderingSystem(IClydeInternal clyde)
{
_clyde = clyde;
}
public override void Initialize()
{
_clyde.RegisterGridEcsEvents();
}
}

View File

@@ -4,11 +4,15 @@ using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
@@ -21,6 +25,8 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientGameStateManager _stateManager = default!;
[Dependency] private readonly IConsoleHost _conHost = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly IPlayerCommandStates _cmdStates = new PlayerCommandStates();
@@ -108,6 +114,43 @@ namespace Robust.Client.GameObjects
public override void Initialize()
{
SubscribeLocalEvent<PlayerAttachSysMessage>(OnAttachedEntityChanged);
_conHost.RegisterCommand("incmd",
"Inserts an input command into the simulation",
"incmd <KeyFunction> <d|u KeyState> <wxPos> <wyPos>",
GenerateInputCommand);
}
public override void Shutdown()
{
base.Shutdown();
_conHost.UnregisterCommand("incmd");
}
private void GenerateInputCommand(IConsoleShell shell, string argstr, string[] args)
{
var localPlayer = _playerManager.LocalPlayer;
if(localPlayer is null)
return;
var pent = localPlayer.ControlledEntity;
if(pent is null)
return;
BoundKeyFunction keyFunction = new BoundKeyFunction(args[0]);
BoundKeyState state = args[1] == "u" ? BoundKeyState.Up: BoundKeyState.Down;
var pxform = Transform(pent.Value);
var wPos = pxform.WorldPosition + new Vector2(float.Parse(args[2]), float.Parse(args[3]));
var coords = EntityCoordinates.FromMap(EntityManager, pent.Value, new MapCoordinates(wPos, pxform.MapID));
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state,
coords, new ScreenCoordinates(0, 0, default), EntityUid.Invalid);
HandleInputCommand(localPlayer.Session, keyFunction, message);
}
private void OnAttachedEntityChanged(PlayerAttachSysMessage message)

View File

@@ -70,18 +70,17 @@ namespace Robust.Client.GameObjects
UpdatesAfter.Add(typeof(TransformSystem));
UpdatesAfter.Add(typeof(PhysicsSystem));
_mapManager.MapCreated += MapManagerOnMapCreated;
_mapManager.OnGridCreated += MapManagerOnGridCreated;
SubscribeLocalEvent<MapChangedEvent>(MapManagerOnMapCreated);
SubscribeLocalEvent<GridInitializeEvent>(MapManagerOnGridCreated);
// Due to how recursion works, this must be done.
SubscribeLocalEvent<MoveEvent>(AnythingMoved);
SubscribeLocalEvent<SpriteComponent, EntMapIdChangedMessage>(SpriteMapChanged);
SubscribeLocalEvent<SpriteComponent, EntParentChangedMessage>(SpriteParentChanged);
SubscribeLocalEvent<SpriteComponent, ComponentRemove>(RemoveSprite);
SubscribeLocalEvent<SpriteComponent, SpriteUpdateEvent>(HandleSpriteUpdate);
SubscribeLocalEvent<PointLightComponent, EntMapIdChangedMessage>(LightMapChanged);
SubscribeLocalEvent<PointLightComponent, EntParentChangedMessage>(LightParentChanged);
SubscribeLocalEvent<PointLightComponent, PointLightRadiusChangedEvent>(PointLightRadiusChanged);
SubscribeLocalEvent<PointLightComponent, PointLightUpdateEvent>(HandleLightUpdate);
@@ -113,12 +112,18 @@ namespace Robust.Client.GameObjects
private void AnythingMoved(ref MoveEvent args)
{
var xforms = EntityManager.GetEntityQuery<TransformComponent>();
var pointQuery = EntityManager.GetEntityQuery<PointLightComponent>();
var spriteQuery = EntityManager.GetEntityQuery<SpriteComponent>();
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
AnythingMovedSubHandler(args.Sender, xforms);
AnythingMovedSubHandler(args.Sender, xformQuery, pointQuery, spriteQuery);
}
private void AnythingMovedSubHandler(EntityUid uid, EntityQuery<TransformComponent> xforms)
private void AnythingMovedSubHandler(
EntityUid uid,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<PointLightComponent> pointQuery,
EntityQuery<SpriteComponent> spriteQuery)
{
// To avoid doing redundant updates (and we don't need to update a grid's children ever)
if (!_checkedChildren.Add(uid) || EntityManager.HasComponent<RenderingTreeComponent>(uid)) return;
@@ -127,17 +132,19 @@ namespace Robust.Client.GameObjects
// WHATEVER YOU DO, DON'T REPLACE THIS WITH SPAMMING EVENTS UNLESS YOU HAVE A GUARANTEE IT WON'T LAG THE GC.
// (Struct-based events ok though)
// Ironically this was lagging the GC lolz
if (EntityManager.TryGetComponent(uid, out SpriteComponent? sprite))
if (spriteQuery.TryGetComponent(uid, out var sprite))
QueueSpriteUpdate(sprite);
if (EntityManager.TryGetComponent(uid, out PointLightComponent? light))
if (pointQuery.TryGetComponent(uid, out var light))
QueueLightUpdate(light);
if (!xforms.TryGetComponent(uid, out var xform)) return;
if (!xformQuery.TryGetComponent(uid, out var xform)) return;
foreach (var child in xform.ChildEntities)
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
AnythingMovedSubHandler(child, xforms);
AnythingMovedSubHandler(child.Value, xformQuery, pointQuery, spriteQuery);
}
}
@@ -147,10 +154,6 @@ namespace Robust.Client.GameObjects
// Otherwise these will still have their past MapId and that's all we need..
#region SpriteHandlers
private void SpriteMapChanged(EntityUid uid, SpriteComponent component, EntMapIdChangedMessage args)
{
QueueSpriteUpdate(component);
}
private void SpriteParentChanged(EntityUid uid, SpriteComponent component, ref EntParentChangedMessage args)
{
@@ -180,10 +183,6 @@ namespace Robust.Client.GameObjects
#endregion
#region LightHandlers
private void LightMapChanged(EntityUid uid, PointLightComponent component, EntMapIdChangedMessage args)
{
QueueLightUpdate(component);
}
private void LightParentChanged(EntityUid uid, PointLightComponent component, ref EntParentChangedMessage args)
{
@@ -212,13 +211,6 @@ namespace Robust.Client.GameObjects
}
#endregion
public override void Shutdown()
{
base.Shutdown();
_mapManager.MapCreated -= MapManagerOnMapCreated;
_mapManager.OnGridCreated -= MapManagerOnGridCreated;
}
private void OnTreeRemove(EntityUid uid, RenderingTreeComponent component, ComponentRemove args)
{
foreach (var sprite in component.SpriteTree)
@@ -235,9 +227,9 @@ namespace Robust.Client.GameObjects
component.LightTree.Clear();
}
private void MapManagerOnMapCreated(object? sender, MapEventArgs e)
private void MapManagerOnMapCreated(MapChangedEvent e)
{
if (e.Map == MapId.Nullspace)
if (e.Destroyed || e.Map == MapId.Nullspace)
{
return;
}
@@ -245,9 +237,9 @@ namespace Robust.Client.GameObjects
EntityManager.EnsureComponent<RenderingTreeComponent>(_mapManager.GetMapEntityId(e.Map));
}
private void MapManagerOnGridCreated(MapId mapId, GridId gridId)
private void MapManagerOnGridCreated(GridInitializeEvent ev)
{
EntityManager.EnsureComponent<RenderingTreeComponent>(_mapManager.GetGrid(gridId).GridEntityId);
EntityManager.EnsureComponent<RenderingTreeComponent>(_mapManager.GetGrid(ev.GridId).GridEntityId);
}
private RenderingTreeComponent? GetRenderTree(EntityUid entity, EntityQuery<TransformComponent> xforms)
@@ -369,13 +361,10 @@ namespace Robust.Client.GameObjects
private Box2 SpriteAabbFunc(in SpriteComponent value)
{
var xforms = EntityManager.GetEntityQuery<TransformComponent>();
var xform = xforms.GetComponent(value.Owner);
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos), worldRot, worldPos);
var tree = GetRenderTree(value.Owner, xforms);
return tree == null ? bounds.CalcBoundingBox() : xforms.GetComponent(tree.Owner).InvWorldMatrix.TransformBox(bounds);
return SpriteAabbFunc(value, worldPos, worldRot, xforms);
}
private Box2 LightAabbFunc(in PointLightComponent value)
@@ -392,7 +381,7 @@ namespace Robust.Client.GameObjects
private Box2 SpriteAabbFunc(SpriteComponent value, Vector2 worldPos, Angle worldRot, EntityQuery<TransformComponent> xforms)
{
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos), worldRot, worldPos);
var bounds = value.CalculateRotatedBoundingBox(worldPos, worldRot);
var tree = GetRenderTree(value.Owner, xforms);
return tree == null ? bounds.CalcBoundingBox() : xforms.GetComponent(tree.Owner).InvWorldMatrix.TransformBox(bounds);

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) ||
ev.Sprite == null) return;
var vecScale = (Vector2)scale;
// Set it directly because prediction may call this multiple times.
ev.Sprite.Scale = vecScale;
}
}

View File

@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects;
public sealed partial class SpriteSystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
private readonly Dictionary<string, IRsiStateLike> _cachedPrototypeIcons = new();
public Texture Frame0(SpriteSpecifier specifier)
{
return RsiStateLike(specifier).Default;
}
public IRsiStateLike RsiStateLike(SpriteSpecifier specifier)
{
switch (specifier)
{
case SpriteSpecifier.Texture tex:
return tex.GetTexture(_resourceCache);
case SpriteSpecifier.Rsi rsi:
return GetState(rsi);
case SpriteSpecifier.EntityPrototype prototypeIcon:
return GetPrototypeIcon(prototypeIcon.EntityPrototypeId);
default:
throw new NotSupportedException();
}
}
/// <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)
{
// Check if this prototype has been cached before, and if so return the result.
if (_cachedPrototypeIcons.TryGetValue(prototype, out var cachedResult))
return cachedResult;
if (!_proto.TryIndex<EntityPrototype>(prototype, out var entityPrototype))
{
// 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 ?? GetFallbackState();
Del(dummy);
return result;
}
[Pure]
public RSI.State GetFallbackState()
{
return _resourceCache.GetFallback<RSIResource>().RSI["error"];
}
[Pure]
public RSI.State GetState(SpriteSpecifier.Rsi rsiSpecifier)
{
if (_resourceCache.TryGetResource<RSIResource>(
SharedSpriteComponent.TextureRoot / rsiSpecifier.RsiPath,
out var theRsi) &&
theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state))
{
return state;
}
Logger.Error("Failed to load RSI {0}", rsiSpecifier.RsiPath);
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

@@ -11,7 +11,7 @@ namespace Robust.Client.GameObjects
/// Updates the layer animation for every visible sprite.
/// </summary>
[UsedImplicitly]
public sealed class SpriteSystem : EntitySystem
public sealed partial class SpriteSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly RenderingTreeSystem _treeSystem = default!;
@@ -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

@@ -52,7 +52,8 @@ namespace Robust.Client.GameObjects
// Only lerp if parent didn't change.
// E.g. entering lockers would do it.
if (transform.LerpParent == transform.ParentUid)
if (transform.LerpParent == transform.ParentUid &&
transform.ParentUid.IsValid())
{
if (transform.LerpDestination != null)
{
@@ -71,7 +72,7 @@ namespace Robust.Client.GameObjects
{
var lerpDest = transform.LerpAngle.Value;
var lerpSource = transform.LerpSourceAngle;
if (lerpDest.Theta - lerpSource.Theta < MaxInterpolationAngle)
if (Math.Abs(Angle.ShortestDistance(lerpDest, lerpSource)) < MaxInterpolationAngle)
{
transform.LocalRotation = Angle.Lerp(lerpSource, lerpDest, step);
// Setting LocalRotation clears LerpAngle so fix that.

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

@@ -0,0 +1,82 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Extensions.ObjectPool;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.GameStates;
/// <summary>
/// Tracks dirty entities on the client for the purposes of gamestatemanager.
/// </summary>
internal sealed class ClientDirtySystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
private readonly Dictionary<GameTick, HashSet<EntityUid>> _dirtyEntities = new();
private ObjectPool<HashSet<EntityUid>> _dirtyPool =
new DefaultObjectPool<HashSet<EntityUid>>(new DefaultPooledObjectPolicy<HashSet<EntityUid>>(), 64);
// Keep it out of the pool because it's probably going to be a lot bigger.
private HashSet<EntityUid> _dirty = new(256);
public override void Initialize()
{
base.Initialize();
EntityManager.EntityDirtied += OnEntityDirty;
}
public override void Shutdown()
{
base.Shutdown();
EntityManager.EntityDirtied -= OnEntityDirty;
_dirtyEntities.Clear();
}
internal void Reset()
{
foreach (var (_, sets) in _dirtyEntities)
{
sets.Clear();
_dirtyPool.Return(sets);
}
_dirtyEntities.Clear();
}
public IEnumerable<EntityUid> GetDirtyEntities(GameTick currentTick)
{
_dirty.Clear();
// This is just to avoid collection being modified during iteration unfortunately.
foreach (var (tick, dirty) in _dirtyEntities)
{
if (tick < currentTick) continue;
foreach (var ent in dirty)
{
_dirty.Add(ent);
}
}
return _dirty;
}
private void OnEntityDirty(EntityUid e)
{
if (e.IsClientSide()) return;
var tick = _timing.CurTick;
if (!_dirtyEntities.TryGetValue(tick, out var ents))
{
ents = _dirtyPool.Get();
_dirtyEntities[tick] = ents;
}
ents.Add(e);
}
}

View File

@@ -21,6 +21,7 @@ using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
using Robust.Shared.Profiling;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -45,7 +46,6 @@ namespace Robust.Client.GameStates
[Dependency] private readonly IComponentFactory _compFactory = default!;
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IPlayerManager _players = default!;
[Dependency] private readonly IClientNetManager _network = default!;
[Dependency] private readonly IBaseClient _client = default!;
@@ -55,6 +55,7 @@ namespace Robust.Client.GameStates
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly ProfManager _prof = default!;
#if EXCEPTION_TOLERANCE
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
#endif
@@ -225,18 +226,28 @@ namespace Robust.Client.GameStates
ResetPredictedEntities(_timing.CurTick);
}
if (!curState.Extrapolated)
using (_prof.Group("FullRep"))
{
_processor.UpdateFullRep(curState);
if (!curState.Extrapolated)
{
_processor.UpdateFullRep(curState);
}
}
// Store last tick we got from the GameStateProcessor.
_lastProcessedTick = _timing.CurTick;
// apply current state
var createdEntities = ApplyGameState(curState, nextState);
List<EntityUid> createdEntities;
using (_prof.Group("ApplyGameState"))
{
createdEntities = ApplyGameState(curState, nextState);
}
MergeImplicitData(createdEntities);
using (_prof.Group("MergeImplicitData"))
{
MergeImplicitData(createdEntities);
}
if (_lastProcessedSeq < curState.LastProcessedInput)
{
@@ -271,6 +282,7 @@ namespace Robust.Client.GameStates
if (IsPredictionEnabled)
{
using var _p = _prof.Group("Prediction");
using var _ = _timing.StartPastPredictionArea();
if (_pendingInputs.Count > 0)
@@ -291,6 +303,8 @@ namespace Robust.Client.GameStates
for (var t = _lastProcessedTick.Value + 1; t <= targetTick; t++)
{
var groupStart = _prof.WriteGroupStart();
var tick = new GameTick(t);
_timing.CurTick = tick;
@@ -322,37 +336,43 @@ namespace Robust.Client.GameStates
if (t != targetTick)
{
// Don't run EntitySystemManager.TickUpdate if this is the target tick,
// because the rest of the main loop will call into it with the target tick later,
// and it won't be a past prediction.
_entitySystemManager.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: false);
((IBroadcastEventBusInternal) _entities.EventBus).ProcessEventQueue();
using (_prof.Group("Systems"))
{
// Don't run EntitySystemManager.TickUpdate if this is the target tick,
// because the rest of the main loop will call into it with the target tick later,
// and it won't be a past prediction.
_entitySystemManager.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: false);
}
using (_prof.Group("Event queue"))
{
((IBroadcastEventBusInternal) _entities.EventBus).ProcessEventQueue();
}
}
_prof.WriteGroupEnd(groupStart, "Prediction tick", ProfData.Int64(t));
}
}
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: !IsPredictionEnabled);
_lookup.Update();
using (_prof.Group("Tick"))
{
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: !IsPredictionEnabled);
}
}
private void ResetPredictedEntities(GameTick curTick)
{
foreach (var meta in _entityManager.EntityQuery<MetaDataComponent>(true))
using var _ = _prof.Group("ResetPredictedEntities");
var countReset = 0;
var system = _entitySystemManager.GetEntitySystem<ClientDirtySystem>();
var query = _entityManager.GetEntityQuery<MetaDataComponent>();
foreach (var entity in system.GetDirtyEntities(curTick))
{
var entity = meta.Owner;
// TODO: 99% there's an off-by-one here.
if (entity.IsClientSide() || meta.EntityLastModifiedTick < curTick)
{
continue;
}
// Check log level first to avoid the string alloc.
if (_sawmill.Level <= LogLevel.Debug)
{
_sawmill.Debug($"Entity {entity} was made dirty.");
}
if (!_processor.TryGetLastServerStates(entity, out var last))
{
@@ -360,6 +380,8 @@ namespace Robust.Client.GameStates
continue;
}
countReset += 1;
// TODO: handle component deletions/creations.
foreach (var (netId, comp) in _entityManager.GetNetComponents(entity))
{
@@ -370,13 +392,21 @@ namespace Robust.Client.GameStates
continue;
}
_sawmill.Debug($" And also its component {comp.GetType()}");
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" And also its component {comp.GetType()}");
// TODO: Handle interpolation.
var handleState = new ComponentHandleState(compState, null);
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
comp.HandleComponentState(compState, null);
comp.LastModifiedTick = curTick;
}
query.GetComponent(entity).EntityLastModifiedTick = curTick;
}
system.Reset();
_prof.WriteValue("Reset count", ProfData.Int32(countReset));
}
private void MergeImplicitData(List<EntityUid> createdEntities)
@@ -412,28 +442,47 @@ namespace Robust.Client.GameStates
private void AckGameState(GameTick sequence)
{
var msg = _network.CreateNetMessage<MsgStateAck>();
var msg = new MsgStateAck();
msg.Sequence = sequence;
_network.ClientSendMessage(msg);
}
private List<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
{
_config.TickProcessMessages();
_mapManager.ApplyGameStatePre(curState.MapData, curState.EntityStates.Span);
var createdEntities = ApplyEntityStates(curState.EntityStates.Span, curState.EntityDeletions.Span,
nextState != null ? nextState.EntityStates.Span : default);
_players.ApplyPlayerStates(curState.PlayerStates.Value ?? Array.Empty<PlayerState>());
_mapManager.ApplyGameStatePost(curState.MapData);
using (_prof.Group("Config"))
{
_config.TickProcessMessages();
}
using (_prof.Group("Map Pre"))
{
_mapManager.ApplyGameStatePre(curState.MapData, curState.EntityStates.Span);
}
List<EntityUid> createdEntities;
using (_prof.Group("Entity"))
{
createdEntities = ApplyEntityStates(curState.EntityStates.Span, curState.EntityDeletions.Span,
nextState != null ? nextState.EntityStates.Span : default);
}
using (_prof.Group("Player"))
{
_players.ApplyPlayerStates(curState.PlayerStates.Value ?? Array.Empty<PlayerState>());
}
using (_prof.Group("Callback"))
{
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState));
}
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState));
return createdEntities;
}
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>();
@@ -541,6 +590,9 @@ namespace Robust.Client.GameStates
}
#endif
_prof.WriteValue("Created", ProfData.Int32(created.Count));
_prof.WriteValue("Applied", ProfData.Int32(toApply.Count));
return created;
}
@@ -552,6 +604,8 @@ namespace Robust.Client.GameStates
if (curState != null)
{
compStateWork.EnsureCapacity(curState.ComponentChanges.Span.Length);
foreach (var compChange in curState.ComponentChanges.Span)
{
if (compChange.Deleted)
@@ -584,6 +638,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

@@ -15,6 +15,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Audio;
using Robust.Shared.Log;
using Vector2 = Robust.Shared.Maths.Vector2;
using Robust.Shared.Utility;
namespace Robust.Client.Graphics.Audio
{
@@ -193,8 +194,10 @@ namespace Robust.Client.Graphics.Audio
{
_didPositionWarning = true;
Logger.WarningS("clyde.oal",
"Attempting to set position on audio source with multiple audio channels! Stream: '{0}'",
"Attempting to set position on audio source with multiple audio channels! Stream: '{0}'. Make sure the audio is MONO, not stereo.",
_sourceStream.Name);
// warning isn't enough, people just ignore it :(
DebugTools.Assert(false, $"Attempting to set position on audio source with multiple audio channels! Stream: '{_sourceStream.Name}'. Make sure the audio is MONO, not stereo.");
}
#endif

View File

@@ -2,6 +2,7 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -132,6 +133,12 @@ namespace Robust.Client.Graphics
public ScreenCoordinates MapToScreen(MapCoordinates point)
{
if (CurrentEye.Position.MapId != point.MapId)
{
Logger.Error($"Attempted to convert map coordinates ({point}) to screen coordinates with an eye on another map ({CurrentEye.Position.MapId})");
return new(default, WindowId.Invalid);
}
return new(WorldToScreen(point.Position), MainViewport.Window?.Id ?? default);
}

View File

@@ -5,20 +5,19 @@ namespace Robust.Client.Graphics.Clyde
private static readonly (string, uint)[] BaseShaderAttribLocations =
{
("aPos", 0),
("tCoord", 1)
("tCoord", 1),
("modulate", 2)
};
private const int UniIModUV = 0;
private const int UniIModelMatrix = 1;
private const int UniIModulate = 2;
private const int UniITexturePixelSize = 3;
private const int UniIMainTexture = 4;
private const int UniILightTexture = 5;
private const int UniCount = 6;
private const int UniITexturePixelSize = 2;
private const int UniIMainTexture = 3;
private const int UniILightTexture = 4;
private const int UniCount = 5;
private const string UniModUV = "modifyUV";
private const string UniModelMatrix = "modelMatrix";
private const string UniModulate = "modulate";
private const string UniTexturePixelSize = "TEXTURE_PIXEL_SIZE";
private const string UniMainTexture = "TEXTURE";
private const string UniLightTexture = "lightMap";

View File

@@ -1,7 +1,5 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -18,8 +16,8 @@ namespace Robust.Client.Graphics.Clyde
private readonly Dictionary<GridId, Dictionary<Vector2i, MapChunkData>> _mapChunkData =
new();
private int _verticesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
private int _indicesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
private int _verticesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
private int _indicesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
private void _drawGrids(Viewport viewport, Box2Rotated worldBounds, IEye eye)
{
@@ -39,7 +37,6 @@ namespace Robust.Client.Graphics.Clyde
gridProgram.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
gridProgram.SetUniformTextureMaybe(UniILightTexture, TextureUnit.Texture1);
gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1));
gridProgram.SetUniform(UniIModulate, Color.White);
foreach (var mapGrid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
{
@@ -52,7 +49,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))
{
@@ -78,7 +75,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
private void _updateChunkMesh(IMapGrid grid, IMapChunk chunk)
private void _updateChunkMesh(IMapGrid grid, MapChunk chunk)
{
var data = _mapChunkData[grid.Index];
@@ -91,25 +88,41 @@ namespace Robust.Client.Graphics.Clyde
Span<Vertex2D> vertexBuffer = stackalloc Vertex2D[_verticesPerChunk(chunk)];
var i = 0;
foreach (var tile in chunk)
var cSz = grid.ChunkSize;
var cScaled = chunk.Indices * cSz;
for (ushort x = 0; x < cSz; x++)
{
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile.Tile);
if (regionMaybe == null)
for (ushort y = 0; y < cSz; y++)
{
continue;
var tile = chunk.GetTile(x, y);
if (tile.IsEmpty)
continue;
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile);
Box2 region;
if (regionMaybe == null || regionMaybe.Length <= tile.Variant)
{
region = _tileDefinitionManager.ErrorTileRegion;
}
else
{
region = regionMaybe[tile.Variant];
}
var gx = x + cScaled.X;
var gy = y + cScaled.Y;
var vIdx = i * 4;
vertexBuffer[vIdx + 0] = new Vertex2D(gx, gy, region.Left, region.Bottom, Color.White);
vertexBuffer[vIdx + 1] = new Vertex2D(gx + 1, gy, region.Right, region.Bottom, Color.White);
vertexBuffer[vIdx + 2] = new Vertex2D(gx + 1, gy + 1, region.Right, region.Top, Color.White);
vertexBuffer[vIdx + 3] = new Vertex2D(gx, gy + 1, region.Left, region.Top, Color.White);
var nIdx = i * GetQuadBatchIndexCount();
var tIdx = (ushort)(i * 4);
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
i += 1;
}
var region = regionMaybe.Value;
var vIdx = i * 4;
vertexBuffer[vIdx + 0] = new Vertex2D(tile.X, tile.Y, region.Left, region.Bottom);
vertexBuffer[vIdx + 1] = new Vertex2D(tile.X + 1, tile.Y, region.Right, region.Bottom);
vertexBuffer[vIdx + 2] = new Vertex2D(tile.X + 1, tile.Y + 1, region.Right, region.Top);
vertexBuffer[vIdx + 3] = new Vertex2D(tile.X, tile.Y + 1, region.Left, region.Top);
var nIdx = i * GetQuadBatchIndexCount();
var tIdx = (ushort) (i * 4);
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
i += 1;
}
GL.BindVertexArray(datum.VAO);
@@ -122,13 +135,13 @@ namespace Robust.Client.Graphics.Clyde
datum.TileCount = i;
}
private MapChunkData _initChunkBuffers(IMapGrid grid, IMapChunk chunk)
private unsafe MapChunkData _initChunkBuffers(IMapGrid grid, MapChunk chunk)
{
var vao = GenVertexArray();
BindVertexArray(vao);
CheckGlError();
var vboSize = _verticesPerChunk(chunk) * Vertex2D.SizeOf;
var vboSize = _verticesPerChunk(chunk) * sizeof(Vertex2D);
var eboSize = _indicesPerChunk(chunk) * sizeof(ushort);
var vbo = new GLBuffer(this, BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw,
@@ -137,12 +150,7 @@ namespace Robust.Client.Graphics.Clyde
eboSize, $"Grid {grid.Index} chunk {chunk.Indices} EBO");
ObjectLabelMaybe(ObjectLabelIdentifier.VertexArray, vao, $"Grid {grid.Index} chunk {chunk.Indices} VAO");
// Vertex Coords
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 0);
GL.EnableVertexAttribArray(0);
// Texture Coords.
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 2 * sizeof(float));
GL.EnableVertexAttribArray(1);
SetupVAOLayout();
CheckGlError();
// Assign VBO and EBO to VAO.
@@ -159,7 +167,7 @@ namespace Robust.Client.Graphics.Clyde
return datum;
}
private bool _isChunkDirty(IMapGrid grid, IMapChunk chunk)
private bool _isChunkDirty(IMapGrid grid, MapChunk chunk)
{
var data = _mapChunkData[grid.Index];
return !data.TryGetValue(chunk.Indices, out var datum) || datum.Dirty;
@@ -175,7 +183,7 @@ namespace Robust.Client.Graphics.Clyde
// Don't need to set it if we don't have an entry since lack of an entry is treated as dirty.
}
private void _updateOnGridModified(object? sender, GridChangedEventArgs args)
private void _updateOnGridModified(GridModifiedEvent args)
{
foreach (var (pos, _) in args.Modified)
{
@@ -185,21 +193,23 @@ namespace Robust.Client.Graphics.Clyde
}
}
private void _updateTileMapOnUpdate(object? sender, TileChangedEventArgs args)
private void _updateTileMapOnUpdate(TileChangedEvent args)
{
var grid = _mapManager.GetGrid(args.NewTile.GridIndex);
var chunk = grid.GridTileToChunkIndices(new Vector2i(args.NewTile.X, args.NewTile.Y));
_setChunkDirty(grid, chunk);
}
private void _updateOnGridCreated(MapId mapId, GridId gridId)
private void _updateOnGridCreated(GridStartupEvent ev)
{
var gridId = ev.GridId;
Logger.DebugS("grid", $"Adding {gridId} to grid renderer");
_mapChunkData.Add(gridId, new Dictionary<Vector2i, MapChunkData>());
}
private void _updateOnGridRemoved(MapId mapId, GridId gridId)
private void _updateOnGridRemoved(GridRemovalEvent ev)
{
var gridId = ev.GridId;
Logger.DebugS("grid", $"Removing {gridId} from grid renderer");
var data = _mapChunkData[gridId];

View File

@@ -13,7 +13,8 @@ using Robust.Client.UserInterface.CustomControls;
using Robust.Shared;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Profiling;
using OGLTextureWrapMode = OpenToolkit.Graphics.OpenGL.TextureWrapMode;
namespace Robust.Client.Graphics.Clyde
{
@@ -24,11 +25,19 @@ namespace Robust.Client.Graphics.Clyde
{
public ClydeDebugLayers DebugLayers { get; set; }
private readonly RefList<(SpriteComponent sprite, Matrix3 worldMatrix, Angle worldRotation, float yWorldPos)>
private readonly RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRotation, Box2 spriteScreenBB)>
_drawingSpriteList
=
new();
// TODO allow this scale to be passed with PostShader as variable
/// <summary>
/// Some shaders that enlarge the final sprite, like emission or highlight effects, need to use a slightly larger render target.
/// </summary>
public static float PostShadeScale = 1.25f;
private List<Overlay> _overlays = new();
public void Render()
{
CheckTransferringScreenshots();
@@ -89,6 +98,7 @@ namespace Robust.Client.Graphics.Clyde
ClearFramebuffer(_userInterfaceManager.GetMainClearColor());
using (DebugGroup("UI"))
using (_prof.Group("UI"))
{
_userInterfaceManager.Render(_renderHandle);
FlushRenderQueue();
@@ -96,29 +106,46 @@ namespace Robust.Client.Graphics.Clyde
TakeScreenshot(ScreenshotType.Final);
// And finally, swap those buffers!
SwapAllBuffers();
using (_prof.Group("Swap buffers"))
{
// And finally, swap those buffers!
SwapAllBuffers();
}
using (_prof.Group("Stats"))
{
_prof.WriteValue("GL Draw Calls", ProfData.Int32(_debugStats.LastGLDrawCalls));
_prof.WriteValue("Clyde Draw Calls", ProfData.Int32(_debugStats.LastClydeDrawCalls));
_prof.WriteValue("Batches", ProfData.Int32(_debugStats.LastBatches));
_prof.WriteValue("Max Batch Verts", ProfData.Int32(_debugStats.LargestBatchVertices));
_prof.WriteValue("Max Batch Idxes", ProfData.Int32(_debugStats.LargestBatchIndices));
_prof.WriteValue("Lights", ProfData.Int32(_debugStats.TotalLights));
}
}
private void RenderSingleOverlay(Overlay overlay, Viewport vp, OverlaySpace space, in Box2 worldBox, in Box2Rotated worldBounds)
{
if (overlay.RequestScreenTexture)
{
FlushRenderQueue();
UpdateOverlayScreenTexture(space, vp.RenderTarget);
}
if (overlay.OverwriteTargetFrameBuffer())
{
ClearFramebuffer(default);
}
overlay.ClydeRender(_renderHandle, space, null, vp, new UIBox2i((0, 0), vp.Size), worldBox, worldBounds);
}
private void RenderOverlays(Viewport vp, OverlaySpace space, in Box2 worldBox, in Box2Rotated worldBounds)
{
using (DebugGroup($"Overlays: {space}"))
{
var list = GetOverlaysForSpace(space);
foreach (var overlay in list)
foreach (var overlay in GetOverlaysForSpace(space))
{
if (overlay.RequestScreenTexture)
{
FlushRenderQueue();
UpdateOverlayScreenTexture(space, vp.RenderTarget);
}
if (overlay.OverwriteTargetFrameBuffer())
{
ClearFramebuffer(default);
}
overlay.ClydeRender(_renderHandle, space, null, vp, new UIBox2i((0, 0), vp.Size), worldBox, worldBounds);
RenderSingleOverlay(overlay, vp, space, worldBox, worldBounds);
}
FlushRenderQueue();
@@ -132,11 +159,13 @@ namespace Robust.Client.Graphics.Clyde
OverlaySpace space,
in UIBox2i bounds)
{
using var _ = _prof.Group($"Overlays SS {space}");
var list = GetOverlaysForSpace(space);
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)
{
@@ -146,19 +175,19 @@ namespace Robust.Client.Graphics.Clyde
private List<Overlay> GetOverlaysForSpace(OverlaySpace space)
{
var list = new List<Overlay>();
_overlays.Clear();
foreach (var overlay in _overlayManager.AllOverlays)
{
if ((overlay.Space & space) != 0)
{
list.Add(overlay);
_overlays.Add(overlay);
}
}
list.Sort(OverlayComparer.Instance);
_overlays.Sort(OverlayComparer.Instance);
return list;
return _overlays;
}
private ClydeTexture? ScreenBufferTexture;
@@ -185,6 +214,12 @@ namespace Robust.Client.Graphics.Clyde
if (lastFrameSize != texture.Size)
{
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS,
(int)OGLTextureWrapMode.MirroredRepeat);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT,
(int)OGLTextureWrapMode.MirroredRepeat);
GL.TexImage2D(TextureTarget.Texture2D, 0,
_hasGLSrgb ? PixelInternalFormat.Srgb8Alpha8 : PixelInternalFormat.Rgba8, texture.Size.X,
texture.Size.Y, 0,
@@ -205,7 +240,6 @@ namespace Robust.Client.Graphics.Clyde
return false;
}
private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
{
var mapId = eye.Position.MapId;
@@ -217,21 +251,10 @@ namespace Robust.Client.Graphics.Clyde
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldAABB, worldBounds);
var screenSize = viewport.Size;
eye.GetViewMatrix(out var eyeMatrix, eye.Scale);
ProcessSpriteEntities(mapId, eyeMatrix, worldBounds, _drawingSpriteList);
ProcessSpriteEntities(mapId, viewport, eye, worldBounds, _drawingSpriteList);
var worldOverlays = new List<Overlay>();
foreach (var overlay in _overlayManager.AllOverlays)
{
if ((overlay.Space & OverlaySpace.WorldSpace) != 0)
{
worldOverlays.Add(overlay);
}
}
worldOverlays.Sort(OverlayComparer.Instance);
var worldOverlays = GetOverlaysForSpace(OverlaySpace.WorldSpaceEntities);
// We use a separate list for indexing so that the sort is faster.
var indexList = ArrayPool<int>.Shared.Rent(_drawingSpriteList.Count);
@@ -242,64 +265,39 @@ namespace Robust.Client.Graphics.Clyde
}
var overlayIndex = 0;
RenderTexture? entityPostRenderTarget = null;
Array.Sort(indexList, 0, _drawingSpriteList.Count, new SpriteDrawingOrderComparer(_drawingSpriteList));
bool flushed = false;
for (var i = 0; i < _drawingSpriteList.Count; i++)
{
ref var entry = ref _drawingSpriteList[indexList[i]];
var flushed = false;
for (var j = overlayIndex; j < worldOverlays.Count; j++)
{
overlayIndex = j;
var overlay = worldOverlays[j];
if (overlay.ZIndex <= entry.sprite.DrawDepth)
if (overlay.ZIndex > entry.sprite.DrawDepth)
{
if (!flushed)
{
FlushRenderQueue();
flushed = true;
}
overlay.ClydeRender(
_renderHandle,
OverlaySpace.WorldSpace,
null,
viewport,
new UIBox2i((0, 0), viewport.Size),
worldAABB,
worldBounds);
overlayIndex = j;
continue;
flushed = false;
break;
}
break;
if (!flushed)
{
FlushRenderQueue();
flushed = true;
}
RenderSingleOverlay(overlay, viewport, OverlaySpace.WorldSpaceEntities, worldAABB, worldBounds);
}
var matrix = entry.worldMatrix;
var worldPosition = new Vector2(matrix.R0C2, matrix.R1C2);
RenderTexture? entityPostRenderTarget = null;
Vector2i roundedPos = default;
if (entry.sprite.PostShader != null)
{
// calculate world bounding box
var spriteBB = entry.sprite.CalculateBoundingBox(worldPosition);
var spriteLB = spriteBB.BottomLeft;
var spriteRT = spriteBB.TopRight;
// finally we can calculate screen bounding in pixels
var screenLB = viewport.WorldToLocal(spriteLB);
var screenRT = viewport.WorldToLocal(spriteRT);
// we need to scale RT a for effects like emission or highlight
// scale can be passed with PostShader as variable in future
var postShadeScale = 1.25f;
var screenSpriteSize = (Vector2i) ((screenRT - screenLB) * postShadeScale).Rounded();
// Rotate the vector by the eye angle, otherwise the bounding box will be incorrect
screenSpriteSize = (Vector2i) eye.Rotation.RotateVec(screenSpriteSize).Rounded();
screenSpriteSize.Y = -screenSpriteSize.Y;
// get the size of the sprite on screen, scaled slightly to allow for shaders that increase the final sprite size.
var screenSpriteSize = (Vector2i) (entry.spriteScreenBB.Size * PostShadeScale).Rounded();
// I'm not 100% sure why it works, but without it post-shader
// can be lower or upper by 1px than original sprite depending on sprite rotation or scale
@@ -312,26 +310,41 @@ namespace Robust.Client.Graphics.Clyde
// check that sprite size is valid
if (screenSpriteSize.X > 0 && screenSpriteSize.Y > 0)
{
// create new render texture with correct sprite size
entityPostRenderTarget = CreateRenderTarget(screenSpriteSize,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb, true),
name: nameof(entityPostRenderTarget));
// This is really bare-bones render target re-use logic. One problem is that if it ever draws a
// single large entity in a frame, the render target may be way to big for every subsequent
// entity. But the vast majority of sprites are currently all 32x32, so it doesn't matter all that
// much.
//
// Also, if there are many differenty sizes, and they all happen to be drawn in order of
// increasing size, then this will still generate a whole bunch of render targets. So maybe
// iterate once _drawingSpriteList, check sprite sizes, and decide what render targets to create
// based off of that?
//
// TODO PERFORMANCE better renderTarget re-use / caching.
if (entityPostRenderTarget == null
|| entityPostRenderTarget.Size.X < screenSpriteSize.X
|| entityPostRenderTarget.Size.Y < screenSpriteSize.Y)
{
entityPostRenderTarget = CreateRenderTarget(screenSpriteSize,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb, true),
name: nameof(entityPostRenderTarget));
}
_renderHandle.UseRenderTarget(entityPostRenderTarget);
_renderHandle.Clear(new Color());
// Calculate viewport so that the entity thinks it's drawing to the same position,
// which is necessary for light application,
// but it's ACTUALLY drawing into the center of the render target.
var spritePos = spriteBB.Center;
var screenPos = viewport.WorldToLocal(spritePos);
var (roundedX, roundedY) = roundedPos = (Vector2i) screenPos;
var flippedPos = new Vector2i(roundedX, screenSize.Y - roundedY);
roundedPos = (Vector2i) entry.spriteScreenBB.Center;
var flippedPos = new Vector2i(roundedPos.X, screenSize.Y - roundedPos.Y);
flippedPos -= entityPostRenderTarget.Size / 2;
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));
}
}
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in worldPosition);
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in entry.worldPos);
if (entry.sprite.PostShader != null && entityPostRenderTarget != null)
{
@@ -356,30 +369,49 @@ namespace Robust.Client.Graphics.Clyde
_renderHandle.SetProjView(oldProj, oldView);
_renderHandle.UseShader(null);
// TODO: cache this properly across frames.
entityPostRenderTarget.DisposeDeferred();
}
}
// draw remainder of overlays
for (var j = overlayIndex; j < worldOverlays.Count; j++)
{
if (!flushed)
{
FlushRenderQueue();
flushed = true;
}
RenderSingleOverlay(worldOverlays[j], viewport, OverlaySpace.WorldSpaceEntities, worldAABB, worldBounds);
}
ArrayPool<int>.Shared.Return(indexList);
entityPostRenderTarget?.DisposeDeferred();
_drawingSpriteList.Clear();
FlushRenderQueue();
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void ProcessSpriteEntities(MapId map, Matrix3 eyeMatrix, Box2Rotated worldBounds,
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list)
private void ProcessSpriteEntities(MapId map, Viewport view, IEye eye, Box2Rotated worldBounds,
RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRot, Box2 spriteScreenBB)> list)
{
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
// Construct a matrix equivalent for Viewport.WorldToLocal()
eye.GetViewMatrix(out var viewMatrix, view.RenderScale);
var uiProjmatrix = Matrix3.Identity;
uiProjmatrix.R0C0 = EyeManager.PixelsPerMeter;
uiProjmatrix.R1C1 = -EyeManager.PixelsPerMeter;
uiProjmatrix.R0C2 = view.Size.X / 2f;
uiProjmatrix.R1C2 = view.Size.Y / 2f;
var worldToLocal = viewMatrix * uiProjmatrix;
foreach (var comp in _entitySystemManager.GetEntitySystem<RenderingTreeSystem>().GetRenderTrees(map, worldBounds))
{
var bounds = xforms.GetComponent(comp.Owner).InvWorldMatrix.TransformBox(worldBounds);
comp.SpriteTree.QueryAabb(ref list, (
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
ref RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRot, Box2 spriteScreenBB)> state,
in SpriteComponent value) =>
{
var entity = value.Owner;
@@ -387,12 +419,10 @@ namespace Robust.Client.Graphics.Clyde
ref var entry = ref state.AllocAdd();
entry.sprite = value;
Vector2 worldPos;
(worldPos, entry.worldRot, entry.matrix) = transform.GetWorldPositionRotationMatrix();
var eyePos = eyeMatrix.Transform(worldPos);
// Didn't use the bounds from the query as that has to be re-calculated (and is probably more expensive than this).
var bounds = value.CalculateBoundingBox(eyePos);
entry.yWorldPos = eyePos.Y - bounds.Extents.Y;
(entry.worldPos, entry.worldRot) = transform.GetWorldPositionRotation();
var spriteWorldBB = value.CalculateRotatedBoundingBox(entry.worldPos, entry.worldRot, eye);
entry.spriteScreenBB = worldToLocal.TransformBox(spriteWorldBB);
return true;
}, bounds, true);
@@ -456,6 +486,8 @@ namespace Robust.Client.Graphics.Clyde
return;
}
using var _ = _prof.Group("Viewport");
RenderInRenderTarget(viewport.RenderTarget, () =>
{
using var _ = DebugGroup($"Viewport: {viewport.Name}");
@@ -477,24 +509,33 @@ namespace Robust.Client.Graphics.Clyde
if (_eyeManager.CurrentMap != MapId.Nullspace)
{
using (DebugGroup("Lights"))
using (_prof.Group("Lights"))
{
DrawLightsAndFov(viewport, worldBounds, worldAABB, eye);
}
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowWorld, worldAABB, worldBounds);
using (_prof.Group("Overlays WSBW"))
{
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowWorld, worldAABB, worldBounds);
}
using (DebugGroup("Grids"))
using (_prof.Group("Grids"))
{
_drawGrids(viewport, worldBounds, eye);
}
// We will also render worldspace overlays here so we can do them under / above entities as necessary
using (DebugGroup("Entities"))
using (_prof.Group("Entities"))
{
DrawEntities(viewport, worldBounds, worldAABB, eye);
}
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowFOV, worldAABB, worldBounds);
using (_prof.Group("Overlays WSBFOV"))
{
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowFOV, worldAABB, worldBounds);
}
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawFov)
{
@@ -527,7 +568,10 @@ namespace Robust.Client.Graphics.Clyde
UIBox2.FromDimensions(Vector2.Zero, viewport.Size), new Color(1, 1, 1, 0.5f));
}
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB, worldBounds);
using (_prof.Group("Overlays WS"))
{
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB, worldBounds);
}
_currentViewport = oldVp;
}, viewport.ClearColor);

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