Compare commits

...

175 Commits

Author SHA1 Message Date
Pieter-Jan Briers
e430c5b1db Version: 204.0.2 2024-08-11 18:01:33 +02:00
Pieter-Jan Briers
68583ceacf Security updates (#5353)
* Fix security bug in WritableDirProvider.OpenOsWindow()

Reported by @NarryG and @nyeogmi

* Sandbox updates

* Update ImageSharp again

(cherry picked from commit 7d778248ee)
(cherry picked from commit f66cda74e95619ddba2221bda644bf4394619805)
(cherry picked from commit db8ba83866c523e08e4fba0b80cd954f4f190613)
(cherry picked from commit 5ea7aa07c26a499a2fb9930a09ef8d13f85494c0)
2024-08-11 18:01:32 +02:00
Pieter-Jan Briers
8a40cb6503 Version: 204.0.1 2024-03-10 20:51:23 +01:00
Pieter-Jan Briers
7baaa4a5d0 Backport 859f150404
(cherry picked from commit 24d5c26fa6)
(cherry picked from commit 2ddfced846dc7d5eea0dcc2ca0617153eb46adc6)
2024-03-10 20:51:23 +01:00
metalgearsloth
ac60567583 Version: 204.0.0 2024-01-09 21:52:36 +11:00
metalgearsloth
7592997f4e Add random pick / take methods for sets (#4821)
* Add random pick / take methods for sets

I want it don't at me.

* cleanup (mraow)

* Revert "cleanup (mraow)"

This reverts commit e279957f21.

* flatpak

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

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

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

* Fix integration tests

* Also freeze _entTraitDict

* A

* I love integration test setup logic

* Re-add public method

* Freeze event bus

* Remove per-component dictionary lookup on EntAddComponent

* release notes + fix test jank

* Fix merge

* fix tests

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

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

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

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

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

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

* Fix integration tests

* Also freeze _entTraitDict

* A

* I love integration test setup logic

* Re-add public method

* changes

* Better notes

* And the new method

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

* remove autonetfield

---------

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

* Undo breaking change

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

* Add test

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

* More cleanup

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

* oops

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

Need to track down the -10 gain setter.

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

* cleanup

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

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

* Fix tests

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

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

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

* Audio option

* Add Cvar

* Note and fix disabled sounds

* Fix double sound

* Comment removal

* ou

---------

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

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

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

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

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

* Better LoD

* Add PvsPriority flag

* Undo cvar name change

* reorganize grafana metrics

* Fix tests

* Fix replays

* Don't try process empty chunks

* Fix move benchmark

* Fix benchmark

* Remove obsolete audio methods

* Moar benchmarks

* Rename EntityData

* A

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

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

* fix tests?

* space

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

* Better readability????

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

* Make _fallbackCultures non-nullable

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

* loaded bool

* ok

---------

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

* mostly address review

---------

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

* Remove bad debug assert

* EntityQuery & misc optimizations

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

* I love engine tests

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

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

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

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

* Comment cleanup

* Obsolete MoveEvent .FromStateHandling

* Faster map position calculation

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

---------

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

* (^///^)

* suggestion

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

* add anther attribute ctor

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

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

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

* Move comment to new line

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

---------

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

* Stop overbudget dictionary removals

* Merge additional logs

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

* Broadcast MoveEvent -> C# event

* oops

* forgob

* release notes

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

* entity query

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

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

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

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

* Remove last-ack

* Remove lastSent

* Use structs

* Don't clear previous-sent on deletion

* Pass data into ProcessEntry

* Store visibility data in EntityData

* Turn ToSend into hashset

* Add EntityStringRepresentation constructors

* Add MetaDataComponent to EntityData

* Remove metadata cache

* Create GetStates partial class

* add ToSend partial class

* fix debug asserts

* Cleanup

* Rename partial class

* Try to optimize `ProcessLeavePvs`

* Add ProcessLeavePvsBenchmark

* Turn HashSet into List

* Collect expensive asserts into one block

* Revert "Add ProcessLeavePvsBenchmark"

This reverts commit 93d984e929.

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

* Add assert

* Change log to verbose

* RStopwatch.StartNew

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

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

* More logs

* AAAA

* Change log to verbose

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

* Update RobustMappedStringSerializer.MappedStringDict.cs

* Change log to verbose

* RStopwatch.StartNew()
2023-12-16 15:48:57 +11:00
ElectroJr
ac86accc20 Version: 192.0.0 2023-12-15 23:43:53 -05:00
Leon Friedrich
19ff7f25ca Slightly speed up IDependencyCollection (#4716) 2023-12-16 00:23:12 +01:00
Pieter-Jan Briers
3f83733a03 Decode ogg files as shorts instead of floats (#4715) 2023-12-15 20:48:07 +01:00
Leon Friedrich
438fed2f0e Make TryGetEntity protected (#4713)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2023-12-15 19:40:43 +01:00
Pieter-Jan Briers
816a535a92 Fix obsoletion warnings for BinaryFormatter stuff
This became an outright warning in .NET 8. Into the trash, never used it.
2023-12-15 19:37:13 +01:00
Pieter-Jan Briers
01df42aa8f Fix ICommonSession.ConnectedClient obsoletion warnings 2023-12-15 19:34:30 +01:00
Pieter-Jan Briers
a200d73ef9 Use FrozenDictionary in DependencyCollection.
Did not test the perf of this at all but it's the perfect use case so...
2023-12-15 19:28:56 +01:00
metalgearsloth
8d30735ffb Run ProcessQueuedAcks asynchronously (#4696) 2023-12-15 19:10:09 +01:00
Pieter-Jan Briers
2686150f9d Version: 191.0.1 2023-12-15 19:08:35 +01:00
Pieter-Jan Briers
d720e9393b Fix .NET 8 sandbox.
Oops.
2023-12-15 19:08:06 +01:00
Pieter-Jan Briers
f5b1c26bec Update Lidgren to fix nullable compilation issue 2023-12-15 18:28:10 +01:00
Pieter-Jan Briers
3204002c72 Version: 191.0.0 2023-12-15 18:09:15 +01:00
Pieter-Jan Briers
4c79d0c6d0 .NET 8 (#4712) 2023-12-15 18:07:41 +01:00
Pieter-Jan Briers
e0d38fb8bd Fix TryGetResource silently eating exceptions
why god
2023-12-15 16:13:17 +01:00
Leon Friedrich
250f6ca7db Obsolete TransformComponent.ChildEntities (#4706)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2023-12-14 17:50:29 +11:00
KP
773365c185 Add a workaround for MacOS' bad CoreAudio OpenAL gain behaviour (#4710)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2023-12-14 17:41:52 +11:00
metalgearsloth
9a1e6af586 Remove DeferMoveEvent (#4709) 2023-12-14 17:20:02 +11:00
Pieter-Jan Briers
dc96318379 Fix Lidgren compile on GitHub hopefully 2023-12-13 19:08:08 +01:00
Pieter-Jan Briers
31a3f145de Add [ViewVariables] to BaseButton.EnableAllKeybinds
Makes it show up in the devwindow UI inspector.
2023-12-13 00:28:33 +01:00
Pieter-Jan Briers
331e1fcc81 Update Lidgren to v0.2.7
This adds NRT annotations. Fix compatibility with those.
2023-12-13 00:06:00 +01:00
Leon Friedrich
dc7a51e582 Fix showrays networking (#4701) 2023-12-12 20:17:36 +11:00
metalgearsloth
bfe8e687da Fix audio params variation (#4699) 2023-12-12 20:16:43 +11:00
metalgearsloth
04b6d60d76 Fix TileEdgeOverlay drawing over grids (#4698) 2023-12-12 20:13:20 +11:00
Leon Friedrich
5bc5bfd58a Add more implicit Entity<T> casts (#4552) 2023-12-12 13:27:31 +11:00
TemporalOroboros
56899b4e64 Some ContainerManagerComponent ECSing (#4622) 2023-12-12 13:24:18 +11:00
DrSmugleaf
a23915e0dd Make MapLoaderSystem.GetSaveData public (#4702) 2023-12-12 13:09:39 +11:00
metalgearsloth
726d91c5e8 Version: 190.1.1 2023-12-11 21:20:58 +11:00
metalgearsloth
d8a8783680 Revert "Avoid recontruction broadphase job every tick (#4653)" (#4695) 2023-12-11 20:11:57 +11:00
metalgearsloth
8839dd9a3b Version: 190.1.0 2023-12-11 19:56:10 +11:00
metalgearsloth
0296d9635c Fix some grid setting asserts (#4580) 2023-12-11 19:49:55 +11:00
metalgearsloth
f24d9751d4 Add GetRotation for matrices and better precision (#4691) 2023-12-11 18:40:57 +11:00
wixoa
58ac82ae55 Add OnGrabbed and OnReleased events to the Slider control (#4694) 2023-12-11 18:39:09 +11:00
metalgearsloth
b9130bf236 Version: 190.0.0 2023-12-10 21:33:56 +11:00
metalgearsloth
a2cd33afe5 Add better OpenAL logging (#4687) 2023-12-10 21:25:43 +11:00
metalgearsloth
1772651049 Fix color_picker shader compilation (#4690) 2023-12-10 21:21:55 +11:00
Leon Friedrich
826fa4d131 Ensure shaders parameters are updated when swapping instances (#4689) 2023-12-10 20:36:23 +11:00
Leon Friedrich
0b712ae86c Add colour gradients to sliders (#4688) 2023-12-10 20:35:43 +11:00
Łukasz Mędrek
22528fc484 Fix colour space translation feedback loop(#3839) (#4681) 2023-12-10 14:10:43 +11:00
Leon Friedrich
20a411e6ce Remove non-networked comps from NetComponents dictionary (#4686) 2023-12-10 13:56:34 +11:00
Leon Friedrich
6f9ed8a242 Stop terminating entities from being prematurely detached to nullspace (#4675) 2023-12-10 13:18:42 +11:00
Łukasz Mędrek
790f4c1309 Fix HSV and HSL producing black color on hue 360 (#4682) 2023-12-10 13:14:15 +11:00
metalgearsloth
0b62cb6445 Change netsyncenabled to an assert (#4604) 2023-12-10 13:03:25 +11:00
metalgearsloth
9d0f4d8a08 Update release notes (#4684) 2023-12-10 12:49:47 +11:00
345 changed files with 10161 additions and 5989 deletions

View File

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

View File

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

View File

@@ -23,7 +23,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
dotnet-version: 8.0.x
- name: Package client
run: Tools/package_client_build.py -p windows mac linux

View File

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

View File

@@ -10,6 +10,9 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<SkipRobustAnalyzer>true</SkipRobustAnalyzer>
<Nullable>enable</Nullable>
<LangVersion>12.0</LangVersion>
</PropertyGroup>
<ItemGroup>

View File

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

View File

@@ -1,8 +1,8 @@
<Project>
<!-- Engine-specific properties. Content should not use this file. -->
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11</LangVersion>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
</PropertyGroup>

View File

@@ -3,7 +3,7 @@
<!-- Import this at the end of any project files in Robust and Content. -->
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<Import Project="Robust.Custom.targets" Condition="Exists('Robust.Custom.targets')"/>

View File

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

View File

@@ -54,6 +54,441 @@ END TEMPLATE-->
*None yet*
## 204.0.2
## 204.0.1
## 204.0.0
### Breaking changes
* Make EntityManager abstract and make IEntityManager.EntityNetManager not nullable.
* Make VVAccess.ReadWrite default for all Datafields instead of VVAccess.ReadOnly
### New features
* `TextEdit.OnTextChanged`
* Add Pick and PickAndTake versions for System.Random for ICollections.
### Bugfixes
* Fix `IClipboardManager.GetText()` returning null in some cases.
* Fix possible NRE in server-side console command completion code.
* Fix possible NRE on DebugConsole logs.
* Fix exception when VVing non-networked components.
### Other
* Remove "Do not use from content" from IComponent.
## 203.0.0
### Breaking changes
* `IComponentFactory.RegisterIgnore()` no longer supports overwriting existing registrations, components should get ignored before they are registered.
* Event bus subscriptions are now locked after `IEntityManager` has started, instead of after the first component gets added. Any event subscriptions now need to happen before startup (but after init).
* Event bus subscriptions must now be locked before raising any events.
* Delete FodyWeavers.xsd as it hasn't been used for a long time.
* Remove physics sleep cancelling as it was, in hindsight, a bad idea.
### New features
* `RobustUnitTest` now has a `ExtraComponents` field for automatically registering additional components.
* `IComponentFactory.RegisterIgnore()` now accepts more than one string.
* Added `IComponentFactory.RegisterTypes` for simultaneously registering multiple components.
### Bugfixes
* Clamp volume calculations for audio rather than throwing.
## 202.1.1
### Bugfixes
* Reverted some map/grid initialisation changes that might've been causing broadphase/physics errors.
* Fixed PVS sometimes sending entities without first sending their children.
* Fixed a container state handling bug caused by containers not removing expected entities when shutting down.
* Fixed a `EnsureEntity<T>` state handling bug caused by improper handling of entity deletions.
* Fixed a bad NetSyncEnabled debug assert.
## 202.1.0
### New features
* Add GetLocalEntitiesIntersecting overload that takes in a griduid and a Vector2i tile.
## 202.0.0
### Breaking changes
* Various entity manager methods now have a new `where T : IComponent` constraint.
* The `IComponentFactory.ComponentAdded` event has been renamed to `ComponentsAdded` and now provides an array of component registrations.
* `IComponentFactory.RegisterIgnore()` no longer supports overwriting existing registrations, components should get ignored before they are registered.
### New features
* Added `IComponentFactory.GetAllRegistrations()`
* Add IComponentState interface support for component states so structs can be used in lieu of classes.
## 201.0.0
### Breaking changes
* The `zCircleGradient` shader function arguments have changed. It now requires a pixel-size to ensure that the gradient is properly entered.
### Bugfixes
* Fixed some PVS null reference errors.
## 200.0.0
### Breaking changes
* MappingDataNode is now ordered.
* Make invalid AutoNetworkedFields compiler errors.
### New features
* `OSWindowStyles.NoTitleBar` (supported only on Linux X11 for now).
### Bugfixes
* Avoid calling DirtyEntity when a component's last modified tick is not current.
* Fix `tpgrid` allowing moving grids to nullspace.
### Other
* `OSWindowStyles.NoTitleOptions` is now supported on Linux X11.
## 199.0.0
### Breaking changes
* Various `IEntityManager` C# events now use `Entity<MetadataComponent>` instead of `EntityUid`
* Entity visibility masks now use a ushort instead of an integer.
* Run grid traversal on entity spawn.
### New features
* Added two new `IEntityManager` C# events that get raiseed before and after deleting ("flushing") all entities.
* Added a new `DeleteEntity()` override that takes in the entity's metadata and transform components.
* Add better audio logs.
* Expand z-library shader.
* Add a Box2i union for Vector2i and add a Contains variant that assumes the Vector2i is a tile and not a point.
### Bugfixes
* Try to prevent some NREs in PVS.
* More PVS fixes and cleanup.
## 198.1.0
### New features
* `IClydeViewport` now provides access to the light render target.
* Added a style-class to the `MenuBar` popup control.
* Added `NextGaussian()` extension method for `System.Random`.
* Added per-session variant of `PvsOverrideSystem.AddForceSend()`.
### Bugfixes
* Stopped the client from logging errors when attempting to delete invalid entities.
### Other
* The `DevWindow` UI inspector has been improved a bit and it now groups properties by their defining type.
## 198.0.1
### Bugfixes
* Fix preprocessor flag for FULL_RELEASE preventing building.
## 198.0.0
### Breaking changes
* Disable DefaultMagicAczProvider for FULL_RELEASE as it's only meant for debugging.
### New features
* Automatic UI scale is disabled by default for non-main windows. If desired, it can be re-enabled per window by changing `WindowRoot.DisableAutoScaling`.
* Add UI click and hover sound support via IUserInterfaceManager.SetClickSound / .SetHoverSound
### Bugfixes
* Fix GetEntitiesIntersecting for map entities without grids.
### Other
* Print more diagnostics on server startup.
## 197.1.0
### New features
* ACZ improvements: `IStatusHost.InvalidateAcz()` and `IStatusHost.SetFullHybridAczProvider()`.
### Bugfixes
* Fixes a PVS bug that happens when grids moved across maps.
* Fixes sprite animations not working properly
## 197.0.0
### Breaking changes
* PvsOverrideSystem has been reworked:
* Session and global overrides now default to always being recursive (i.e., sending all children).
* Session & global overrides will always respect a client's PVS budgets.
* Entities with an override will now still be sent in the same way as other entities if they are within a player's view. If you want to prevent them from being sent, you need to use visibility masks.
* Entities can have more than one kind of override (i.e., multiple sessions).
### New features
* Added a `PvsSize ` field to `EyeComponent`, which can be used to modify the PVS range of an eye.
* Added a new `NetLowLodRange` cvar for reducing the number of distant entities that get sent to a player. If a PVS chunk is beyond this range (but still within PVS range), then only high-priority entities on that chunk will get sent.
* Added a new metadata flag for tagging an entity as a "high prority" entity that should get sent even on distant chunks. This only works for entities that are directly attached to a grid or map. This is currently used by lights & occluders.
### Other
* PVS has been reworked again, and should hopefully be noticeable faster.
* PVS now prioritizes sending chunks that are closer to a player's eyes.
## 196.0.0
### Breaking changes
* Dirtying a non-networked component will now fail a debug assert.
* The `IInvocationContext` interface for toolshed commands now requires a UserId field. The session field should be cleared if a player disconnects.
### New features
* `LocalizationManager` now supports multiple fallback cultures
* SpriteView now supports using a `NetEntity` to select an entity to draw.
* Added methods for simultaneously dirtying several components on the same entity.
* Animated sprite layers now have a "Cycle" option that will reverse an animation when it finishes.
### Bugfixes
* Fixed a recursion/stack-overflow in `GridTraversalSystem`
* Ensure `Robust.Client.WebView` processes get shut down if game process exits uncleanly.
* Fixed Toolshed commands not properly functioning after disconnecting and reconnecting.
### Other
* Console command completions no longer suggest toolshed commands for non-toolshed commands.
## 195.0.1
### Bugfixes
* Fixes playing audio using audio streams
* Fixes placement manager exceptions when placing self deleting / spawner entities
* Fixed `IPrototypeManager.EnumeratePrototypes<T>` throwing an exception when there are no instances.
## 195.0.0
### New features
* Generic versions of `DebugTools.AssertEquals()` functions.
* `[Prototype]` now does not need to have a name specified, the name is inferred from the class name.
### Bugfixes
* Fixes a physics bug that could cause deleted entities to remain on the physics map.
* Fixes a bug in entity lookup code that could cause clients to get stuck in an infinite loop.
### Other
* `Robust.Client.WebView` has been brought alive again.
* The addition of physics joints is no longer deferred to the next tick.
* Grid traversal is no longer deferred to the next tick.
* Integration tests now fail when console commands log errors.
## 194.1.0
### New features
* `IAudioManager` has APIs to directly load `AudioStream`s from data streams.
* `AudioSystem` has new `Play*` methods.
* `EntityCoordinates.TryDelta()`
* `EntityLookupSystem.GetEntitiesInRange()` untyped hashset overload has `flags` parameter.
## 194.0.2
### Internal
* Added some null-checks to PVS to try reduce the error spam.
## 194.0.1
### Bugfixes
* Fixed `Control.SetPositionInParent` failing to move an entity to the last position.
* Fixed audio occlusion not working.
### Internal
* Added some logs for grid/map deletion and movement to debug some map loading issues.
* Refactored some parts of PVS. It should be slightly faster, though the game may be unstable for a bit.
## 194.0.0
### Breaking changes
* MoveEvent is no longer raised broadcast, subscribe to the SharedTransformSystem.OnGlobalMoveEvent C# event instead
### Bugfixes
* Fixed the game sometimes freezing while trying to load specific audio files.
## 193.2.0
### Other
* Added more PVS error logs
## 193.1.1
### Bugfixes
* Fixed an exception when building in FULL_RELEASE
## 193.1.0
### New features
* Added FrozenDictionary and FrozenHashSet to sandbox whitelist
* Added yaml type serializers for FrozenDictionary and FrozenHashSet
* Added `IPrototypeManager.GetInstances<T>()`
* `IPrototypeManager` now also raises `PrototypesReloadedEventArgs` as a system event.
### Bugfixes
* Might fix some PVS bugs added in the last version.
### Internal
* Various static dictionaries have been converted into FrozenDictionary.
## 193.0.0
### Breaking changes
* The `TransformChildrenEnumerator`'s out values are now non-nullable
### New features
* Added `IPrototypeManager.TryGetInstances()`, which returns a dictionary of prototype instances for a given prototype kind/type.
### Bugfixes
* Fixed `BaseAudioSource.SetAuxiliary()` throwing errors on non-EFX systems
### Internal
* The internals of PVS system have been reworked to reduce the number of dictionary lookups.
* `RobustMappedStringSerializer` now uses frozen dictionaries
* `IPrototypeManager` now uses frozen dictionaries
## 192.0.0
### Breaking changes
* `EntitySystem.TryGetEntity` is now `protected`.
### Internal
* PVS message ack processing now happens asynchronously
* Dependency collections now use a `FrozenDictionary`
## 191.0.1
### Bugfixes
.* Fix sandbox being broken thanks to .NET 8.
## 191.0.0
### Breaking changes
* Robust now uses **.NET 8**. Nyoom.
### Bugfixes
* `IResourceCache.TryGetResource<T>` won't silently eat all exceptions anymore.
## 190.1.1
### Bugfixes
* Revert broadphase job to prevent OOM from logs.
## 190.1.0
### New features
* Add OnGrabbed / OnReleased to slider controls.
* Add Rotation method for matrices and also make the precision slightly better when angles are passed in by taking double-precision not single-precision floats.
### Bugfixes
* Fix some grid setting asserts when adding gridcomponent to existing maps.
## 190.0.0
### New features
* Add color gradients to sliders.
### Bugfixes
* Fix HSV / HSL producing black colors on 360 hue.
* Stop terminating entities from prematurely detaching to nullspace.
* Ensure shader parameters update when swapping instances.
### Other
* Add more verbose logging to OpenAL errors.
### Internal
* Change NetSyncEnabled to an assert and fix instances where it slips through to PVS.
## 189.0.0
### Breaking changes
@@ -64,6 +499,7 @@ END TEMPLATE-->
### New features
* Improve YAML linter error messages for parent nodes.
* ExpandPvsEvent will also be raised directed to the session's attached entity.
### Bugfixes
@@ -360,7 +796,7 @@ END TEMPLATE-->
### Breaking changes
* Most methods in ActorSystem have been moved to ISharedPlayerManager.
* Most methods in ActorSystem have been moved to ISharedPlayerManager.
* Several actor/player related components and events have been moved to shared.
### New features

View File

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

View File

@@ -9,6 +9,7 @@ cmd-parse-failure-float = {$arg} is not a valid float.
cmd-parse-failure-bool = {$arg} is not a valid bool.
cmd-parse-failure-uid = {$arg} is not a valid entity UID.
cmd-parse-failure-mapid = {$arg} is not a valid MapId.
cmd-parse-failure-grid = {$arg} is not a valid grid.
cmd-parse-failure-entity-exist = UID {$arg} does not correspond to an existing entity.
cmd-error-file-not-found = Could not find file: {$file}.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -97,11 +97,11 @@ internal partial class AudioManager
// check the git history, I originally used libvorbisfile which worked and loaded 16 bit LPCM.
if (vorbis.Channels == 1)
{
format = ALFormat.MonoFloat32Ext;
format = ALFormat.Mono16;
}
else if (vorbis.Channels == 2)
{
format = ALFormat.StereoFloat32Ext;
format = ALFormat.Stereo16;
}
else
{
@@ -110,9 +110,9 @@ internal partial class AudioManager
unsafe
{
fixed (float* ptr = vorbis.Data.Span)
fixed (short* ptr = vorbis.Data.Span)
{
AL.BufferData(buffer, format, (IntPtr) ptr, vorbis.Data.Length * sizeof(float),
AL.BufferData(buffer, format, (IntPtr) ptr, vorbis.Data.Length * sizeof(short),
(int) vorbis.SampleRate);
}
}
@@ -216,6 +216,25 @@ internal partial class AudioManager
public void SetMasterGain(float newGain)
{
if (newGain < 0f)
{
OpenALSawmill.Error("Tried to set master gain below 0, clamping to 0");
AL.Listener(ALListenerf.Gain, 0f);
return;
}
#region Platform hack for MacOS
// HACK/BUG: Apple's OpenAL implementation has a bug where values of 0f for listener gain don't actually
// HACK/BUG: prevent sound playback. Workaround is to cap the minimum gain at a value just above 0.
if (OperatingSystem.IsMacOS() && newGain == 0f)
{
OpenALSawmill.Verbose("Not setting gain to 0 because Apple can't write an OpenAL implementation");
AL.Listener(ALListenerf.Gain, float.Epsilon);
return;
}
#endregion Platform hack for MacOS
AL.Listener(ALListenerf.Gain, newGain);
}
@@ -283,7 +302,7 @@ internal partial class AudioManager
}
/// <inheritdoc/>
public IBufferedAudioSource? CreateBufferedAudioSource(int buffers, bool floatAudio=false)
IBufferedAudioSource? IAudioInternal.CreateBufferedAudioSource(int buffers, bool floatAudio=false)
{
var source = AL.GenSource();

View File

@@ -140,6 +140,18 @@ internal sealed partial class AudioManager : IAudioInternal
}
}
/// <summary>
/// Like _checkAlError but allows custom data to be passed in as relevant.
/// </summary>
internal void LogALError(string message, [CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1)
{
var error = AL.GetError();
if (error != ALError.NoError)
{
OpenALSawmill.Error("[{0}:{1}] AL error: {2}, {3}. Stacktrace is {4}", callerMember, callerLineNumber, error, message, Environment.StackTrace);
}
}
public void _checkAlError([CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1)
{
var error = AL.GetError();

View File

@@ -158,12 +158,24 @@ public sealed partial class AudioSystem : SharedAudioSystem
return;
}
// Source has already been set
if (component.Loaded)
{
return;
}
if (!TryGetAudio(component.FileName, out var audioResource))
{
Log.Error($"Error creating audio source for {audioResource}, can't find file {component.FileName}");
return;
}
SetupSource(component, audioResource);
component.Loaded = true;
}
private void SetupSource(AudioComponent component, AudioResource audioResource, TimeSpan? length = null)
{
var source = _audio.CreateAudioSource(audioResource);
if (source == null)
@@ -182,8 +194,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
// Don't play until first frame so occlusion etc. are correct.
component.Gain = 0f;
length ??= GetAudioLength(component.FileName);
// If audio came into range then start playback at the correct position.
var offset = (Timing.CurTime - component.AudioStart).TotalSeconds % GetAudioLength(component.FileName).TotalSeconds;
var offset = (Timing.CurTime - component.AudioStart).TotalSeconds % length.Value.TotalSeconds;
if (offset > 0)
{
@@ -312,7 +326,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
return;
}
var paramsGain = MathF.Pow(10, component.Params.Volume / 10);
var paramsGain = VolumeToGain(component.Params.Volume);
// Thought I'd never have to manually calculate gain again but this is the least
// unpleasant audio I could get at the moment.
@@ -395,18 +409,13 @@ public sealed partial class AudioSystem : SharedAudioSystem
return false;
}
private bool TryCreateAudioSource(AudioStream stream, [NotNullWhen(true)] out IAudioSource? source)
private bool TryGetAudio(AudioStream stream, [NotNullWhen(true)] out AudioResource? audio)
{
if (!Timing.IsFirstTimePredicted)
{
source = null;
Log.Error($"Tried to create audio source outside of prediction!");
DebugTools.Assert(false);
return false;
}
if (_resourceCache.TryGetResource(stream, out audio))
return true;
source = _audio.CreateAudioSource(stream);
return source != null;
Log.Error($"Server failed to play audio stream {stream.Title}.");
return false;
}
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates,
@@ -423,7 +432,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null)
{
if (Timing.IsFirstTimePredicted || sound == null)
if (Timing.IsFirstTimePredicted && sound != null)
return PlayEntity(sound, Filter.Local(), source, false, audioParams);
return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio....
@@ -431,7 +440,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null)
{
if (Timing.IsFirstTimePredicted || sound == null)
if (Timing.IsFirstTimePredicted && sound != null)
return PlayStatic(sound, Filter.Local(), coordinates, false, audioParams);
return null;
@@ -461,7 +470,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="audioParams"></param>
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, AudioParams? audioParams = null)
public (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, AudioParams? audioParams = null)
{
var (entity, component) = CreateAndStartPlayingStream(audioParams, stream);
component.Global = true;
@@ -496,7 +505,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
/// <param name="stream">The audio stream to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
private (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, AudioParams? audioParams = null)
public (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, AudioParams? audioParams = null)
{
if (TerminatingOrDeleted(entity))
{
@@ -537,7 +546,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
/// <param name="stream">The audio stream to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
private (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, AudioParams? audioParams = null)
public (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
if (TerminatingOrDeleted(coordinates.EntityId))
{
@@ -574,6 +583,16 @@ public sealed partial class AudioSystem : SharedAudioSystem
return PlayGlobal(filename, audioParams);
}
public override void LoadStream<T>(AudioComponent component, T stream)
{
if (stream is AudioStream audioStream)
{
TryGetAudio(audioStream, out var audio);
SetupSource(component, audio!, audioStream.Length);
component.Loaded = true;
}
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null)
{
@@ -608,7 +627,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
{
var audioP = audioParams ?? AudioParams.Default;
var entity = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace);
var comp = SetupAudio(entity, stream.Name!, audioP);
var comp = SetupAudio(entity, null, audioP, stream.Length);
LoadStream(comp, stream);
EntityManager.InitializeAndStartEntity(entity);
var source = comp.Source;
@@ -618,8 +638,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
source.PlaybackPosition = offset;
// For server we will rely on the adjusted one but locally we will have to adjust it ourselves.
ApplyAudioParams(audioP, comp);
comp.Params = audioP;
ApplyAudioParams(comp.Params, comp);
source.StartPlaying();
return (entity, comp);
}

View File

@@ -21,8 +21,6 @@ internal interface IAudioInternal : IAudioManager
/// </summary>
void FlushALDisposeQueues();
IAudioSource? CreateAudioSource(AudioStream stream);
/// <summary>
/// Returns a buffered audio source.
/// </summary>
@@ -62,10 +60,4 @@ internal interface IAudioInternal : IAudioManager
/// Manually calculates the specified gain for an attenuation source with the specified distance.
/// </summary>
float GetAttenuationGain(float distance, float rolloffFactor, float referenceDistance, float maxDistance);
AudioStream LoadAudioOggVorbis(Stream stream, string? name = null);
AudioStream LoadAudioWav(Stream stream, string? name = null);
AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null);
}

View File

@@ -1,3 +1,8 @@
using System;
using System.IO;
using Robust.Client.Audio.Sources;
using Robust.Shared.Audio.Sources;
namespace Robust.Client.Audio;
/// <summary>
@@ -5,5 +10,13 @@ namespace Robust.Client.Audio;
/// </summary>
public interface IAudioManager
{
IAudioSource? CreateAudioSource(AudioStream stream);
AudioStream LoadAudioOggVorbis(Stream stream, string? name = null);
AudioStream LoadAudioWav(Stream stream, string? name = null);
AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null);
void SetMasterGain(float gain);
}

View File

@@ -10,7 +10,7 @@ using Robust.Shared.Maths;
namespace Robust.Client.Audio.Sources;
internal abstract class BaseAudioSource : IAudioSource
public abstract class BaseAudioSource : IAudioSource
{
/*
* This may look weird having all these methods here however
@@ -27,22 +27,25 @@ internal abstract class BaseAudioSource : IAudioSource
/// </summary>
protected int FilterHandle;
protected readonly AudioManager Master;
internal readonly AudioManager Master;
/// <summary>
/// Prior gain that was set.
/// </summary>
private float _gain;
private float _occlusion;
private bool IsEfxSupported => Master.IsEfxSupported;
protected BaseAudioSource(AudioManager master, int sourceHandle)
internal BaseAudioSource(AudioManager master, int sourceHandle)
{
Master = master;
SourceHandle = sourceHandle;
AL.GetSource(SourceHandle, ALSourcef.Gain, out _gain);
}
/// <inheritdoc />
public void Pause()
{
AL.SourcePause(SourceHandle);
@@ -66,6 +69,13 @@ internal abstract class BaseAudioSource : IAudioSource
Playing = false;
}
/// <inheritdoc />
public void Restart()
{
AL.SourceRewind(SourceHandle);
StartPlaying();
}
/// <inheritdoc />
public virtual bool Playing
{
@@ -157,7 +167,22 @@ internal abstract class BaseAudioSource : IAudioSource
}
/// <inheritdoc />
public float Pitch { get; set; }
public float Pitch
{
get
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSourcef.Pitch, out var value);
Master._checkAlError();
return value;
}
set
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.Pitch, value);
Master._checkAlError();
}
}
/// <inheritdoc />
public float Volume
@@ -189,12 +214,12 @@ internal abstract class BaseAudioSource : IAudioSource
{
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
// Default to 0 to avoid spiking audio, just means it will be muted for a frame in this case.
priorOcclusion = _gain == 0 ? 0f : priorGain / _gain;
priorOcclusion = _gain == 0 ? 1f : priorGain / _gain;
}
_gain = value;
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
Master._checkAlError();
Master.LogALError($"Gain is {_gain:0.00} and priorOcclusion is {priorOcclusion:0.00}. EFX supported: {IsEfxSupported}");
}
}
@@ -212,7 +237,7 @@ internal abstract class BaseAudioSource : IAudioSource
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.MaxDistance, value);
Master._checkAlError();
Master.LogALError($"MaxDistance is {value:0.00}");
}
}
@@ -230,7 +255,7 @@ internal abstract class BaseAudioSource : IAudioSource
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.RolloffFactor, value);
Master._checkAlError();
Master.LogALError($"RolloffFactor is {value:0.00}");
}
}
@@ -248,20 +273,14 @@ internal abstract class BaseAudioSource : IAudioSource
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.ReferenceDistance, value);
Master._checkAlError();
Master.LogALError($"ReferenceDistance is {value:0.00}");
}
}
/// <inheritdoc />
public float Occlusion
{
get
{
_checkDisposed();
AL.GetSource(SourceHandle, ALSourcef.MaxDistance, out var value);
Master._checkAlError();
return value;
}
get => _occlusion;
set
{
_checkDisposed();
@@ -276,6 +295,8 @@ internal abstract class BaseAudioSource : IAudioSource
gain *= gain * gain;
AL.Source(SourceHandle, ALSourcef.Gain, _gain * gain);
}
_occlusion = value;
Master._checkAlError();
}
}
@@ -294,7 +315,7 @@ internal abstract class BaseAudioSource : IAudioSource
{
_checkDisposed();
AL.Source(SourceHandle, ALSourcef.SecOffset, value);
Master._checkAlError();
Master._checkAlError($"Tried to set invalid playback position of {value:0.00}");
}
}
@@ -325,9 +346,11 @@ internal abstract class BaseAudioSource : IAudioSource
}
}
public void SetAuxiliary(IAuxiliaryAudio? audio)
void IAudioSource.SetAuxiliary(IAuxiliaryAudio? audio)
{
_checkDisposed();
if (!IsEfxSupported)
return;
if (audio is AuxiliaryAudio impAudio)
{

View File

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

View File

@@ -47,7 +47,8 @@ namespace Robust.Client.Console
}
/// <inheritdoc cref="IClientConsoleHost" />
internal sealed partial class ClientConsoleHost : ConsoleHost, IClientConsoleHost, IConsoleHostInternal, IPostInjectInit
[Virtual]
internal partial class ClientConsoleHost : ConsoleHost, IClientConsoleHost, IConsoleHostInternal, IPostInjectInit
{
[Dependency] private readonly IClientConGroupController _conGroup = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime;
using System.Text;
using System.Text.RegularExpressions;
@@ -198,6 +199,7 @@ namespace Robust.Client.Console.Commands
}
}
#if DEBUG
internal sealed class ShowRayCommand : LocalizedCommands
{
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
@@ -224,6 +226,7 @@ namespace Robust.Client.Console.Commands
mgr.DebugRayLifetime = TimeSpan.FromSeconds(duration);
}
}
#endif
internal sealed class DisconnectCommand : LocalizedCommands
{
@@ -492,9 +495,9 @@ namespace Robust.Client.Console.Commands
}
}
internal static List<(string, string)> PropertyValuesFor(Control control)
internal static List<MemberInfo> GetAllMembers(Control control)
{
var members = new List<(string, string)>();
var members = new List<MemberInfo>();
var type = control.GetType();
foreach (var fieldInfo in type.GetAllFields())
@@ -504,7 +507,7 @@ namespace Robust.Client.Console.Commands
continue;
}
members.Add((fieldInfo.Name, fieldInfo.GetValue(control)?.ToString() ?? "null"));
members.Add(fieldInfo);
}
foreach (var propertyInfo in type.GetAllProperties())
@@ -514,7 +517,19 @@ namespace Robust.Client.Console.Commands
continue;
}
members.Add((propertyInfo.Name, propertyInfo.GetValue(control)?.ToString() ?? "null"));
members.Add(propertyInfo);
}
return members;
}
internal static List<(string, string)> PropertyValuesFor(Control control)
{
var members = new List<(string, string)>();
foreach (var fieldInfo in GetAllMembers(control))
{
members.Add((fieldInfo.Name, fieldInfo.GetValue(control)?.ToString() ?? "null"));
}
foreach (var (attachedProperty, value) in control.AllAttachedProperties)
@@ -526,6 +541,35 @@ namespace Robust.Client.Console.Commands
members.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.Ordinal));
return members;
}
internal static Dictionary<string, List<(string, string)>> PropertyValuesForInheritance(Control control)
{
var returnVal = new Dictionary<string, List<(string, string)>>();
var engine = typeof(Control).Assembly;
foreach (var member in GetAllMembers(control))
{
var type = member.DeclaringType!;
var cname = type.Assembly == engine ? type.Name : type.ToString();
if (type != typeof(Control))
cname = $"Control > {cname}";
returnVal.GetOrNew(cname).Add((member.Name, member.GetValue(control)?.ToString() ?? "null"));
}
foreach (var (attachedProperty, value) in control.AllAttachedProperties)
{
var cname = $"Attached > {attachedProperty.OwningType.Name}";
returnVal.GetOrNew(cname).Add((attachedProperty.Name, value?.ToString() ?? "null"));
}
foreach (var v in returnVal.Values)
{
v.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.Ordinal));
}
return returnVal;
}
}
internal sealed class SetClipboardCommand : LocalizedCommands

View File

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

View File

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

View File

@@ -4,18 +4,17 @@ using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Debugging;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Physics;
using Robust.Shared.Timing;
namespace Robust.Client.Debugging
{
internal sealed class DebugRayDrawingSystem : SharedDebugRayDrawingSystem
{
#if DEBUG
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IGameTiming _gameTimer = default!;
@@ -28,6 +27,8 @@ namespace Robust.Client.Debugging
public Vector2 RayHit;
public TimeSpan LifeTime;
public bool DidActuallyHit;
public bool Server;
public MapId Map;
}
public bool DebugDrawRays
@@ -73,7 +74,8 @@ namespace Robust.Client.Debugging
DidActuallyHit = ev.Results != null,
RayOrigin = ev.Ray.Position,
RayHit = ev.Results?.HitPos ?? ev.Ray.Direction * ev.MaxLength + ev.Ray.Position,
LifeTime = _gameTimer.RealTime + DebugRayLifetime
LifeTime = _gameTimer.RealTime + DebugRayLifetime,
Map = ev.Map
};
_raysWithLifeTime.Add(newRayWithLifetime);
@@ -93,7 +95,9 @@ namespace Robust.Client.Debugging
DidActuallyHit = msg.DidHit,
RayOrigin = msg.RayOrigin,
RayHit = msg.RayHit,
LifeTime = _gameTimer.RealTime + DebugRayLifetime
LifeTime = _gameTimer.RealTime + DebugRayLifetime,
Server = true,
Map = msg.Map
};
_raysWithLifeTime.Add(newRayWithLifetime);
@@ -114,10 +118,20 @@ namespace Robust.Client.Debugging
var handle = args.WorldHandle;
foreach (var ray in _owner._raysWithLifeTime)
{
if (args.MapId != ray.Map)
continue;
Color color;
if (ray.Server)
color = ray.DidActuallyHit ? Color.Cyan : Color.Orange;
else
color = ray.DidActuallyHit ? Color.Blue : Color.Red;
handle.DrawLine(
ray.RayOrigin,
ray.RayHit,
ray.DidActuallyHit ? Color.Yellow : Color.Magenta);
color
);
}
}
@@ -128,5 +142,6 @@ namespace Robust.Client.Debugging
_owner._raysWithLifeTime.RemoveAll(r => r.LifeTime < _owner._gameTimer.RealTime);
}
}
#endif
}
}

View File

@@ -1,82 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="InlineIL" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="SequencePoints">
<xs:annotation>
<xs:documentation>Defines if sequence points should be generated for each emitted IL instruction. Default value: Debug</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="Debug">
<xs:annotation>
<xs:documentation>Insert sequence points in Debug builds only (this is the default).</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Release">
<xs:annotation>
<xs:documentation>Insert sequence points in Release builds only.</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="True">
<xs:annotation>
<xs:documentation>Always insert sequence points.</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="False">
<xs:annotation>
<xs:documentation>Never insert sequence points.</xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="Warnings">
<xs:annotation>
<xs:documentation>Defines how warnings should be handled. Default value: Warnings</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="Warnings">
<xs:annotation>
<xs:documentation>Emit build warnings (this is the default).</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Ignore">
<xs:annotation>
<xs:documentation>Do not emit warnings.</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Errors">
<xs:annotation>
<xs:documentation>Treat warnings as errors.</xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@@ -109,9 +109,15 @@ namespace Robust.Client
{
IsBackground = false,
Priority = priority,
Name = "Game thread",
Name = "Game thread"
};
if (OperatingSystem.IsWindows())
{
// Necessary for CEF to not complain when using CEF debug binaries.
_gameThread.SetApartmentState(ApartmentState.STA);
}
_gameThread.Start();
// Will block until game exit

View File

@@ -67,7 +67,7 @@ namespace Robust.Client.GameObjects
public override void QueueDeleteEntity(EntityUid? uid)
{
if (uid == null)
if (uid == null || uid == EntityUid.Invalid)
return;
if (IsClientSide(uid.Value))
@@ -98,25 +98,38 @@ namespace Robust.Client.GameObjects
base.Dirty(ent, meta);
}
public override EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent? metaDataComponent = null)
/// <inheritdoc />
public override void Dirty<T1, T2>(Entity<T1, T2> ent, MetaDataComponent? meta = null)
{
if (_playerManager.LocalPlayer?.ControlledEntity == uid)
return base.ToPrettyString(uid) with { Session = _playerManager.LocalPlayer.Session };
if (_gameTiming.InPrediction)
base.Dirty(ent, meta);
}
return base.ToPrettyString(uid);
/// <inheritdoc />
public override void Dirty<T1, T2, T3>(Entity<T1, T2, T3> ent, MetaDataComponent? meta = null)
{
if (_gameTiming.InPrediction)
base.Dirty(ent, meta);
}
/// <inheritdoc />
public override void Dirty<T1, T2, T3, T4>(Entity<T1, T2, T3, T4> ent, MetaDataComponent? meta = null)
{
if (_gameTiming.InPrediction)
base.Dirty(ent, meta);
}
public override void RaisePredictiveEvent<T>(T msg)
{
var localPlayer = _playerManager.LocalPlayer;
DebugTools.AssertNotNull(localPlayer);
var session = _playerManager.LocalSession;
DebugTools.AssertNotNull(session);
var sequence = _stateMan.SystemMessageDispatched(msg);
EntityNetManager?.SendSystemNetworkMessage(msg, sequence);
DebugTools.Assert(!_stateMan.IsPredictionEnabled || _gameTiming.InPrediction && _gameTiming.IsFirstTimePredicted || _client.RunLevel != ClientRunLevel.Connected);
var eventArgs = new EntitySessionEventArgs(localPlayer!.Session);
var eventArgs = new EntitySessionEventArgs(session!);
EventBus.RaiseEvent(EventSource.Local, msg);
EventBus.RaiseEvent(EventSource.Local, new EntitySessionMessage<T>(eventArgs, msg));
}

View File

@@ -18,11 +18,5 @@ namespace Robust.Client.GameObjects
public ComponentStateApplyException(string message, Exception inner) : base(message, inner)
{
}
protected ComponentStateApplyException(
SerializationInfo info,
StreamingContext context) : base(info, context)
{
}
}
}

View File

@@ -319,6 +319,7 @@ namespace Robust.Client.GameObjects
Scale = Vector2.One,
Visible = true,
RenderingStrategy = LayerRenderingStrategy.UseSpriteStrategy,
Cycle = false,
});
state = null;
texture = null;
@@ -794,6 +795,7 @@ namespace Robust.Client.GameObjects
}
layer.RenderingStrategy = layerDatum.RenderingStrategy ?? layer.RenderingStrategy;
layer.Cycle = layerDatum.Cycle;
layer.Color = layerDatum.Color ?? layer.Color;
layer._rotation = layerDatum.Rotation ?? layer._rotation;
@@ -1515,6 +1517,19 @@ namespace Robust.Client.GameObjects
[ViewVariables] public float AnimationTime;
[ViewVariables] public int AnimationFrame;
/// <summary>
/// Is the animation currently playing in reverse.
/// </summary>
[ViewVariables] public bool Reversed { get; internal set; }
/// <summary>
/// If every animation delay finishes do we reverse it.
/// </summary>
/// <remarks>
/// Only applies if the state is auto-animated.
/// </remarks>
[ViewVariables] public bool Cycle;
private RSI.State? _actualState;
[ViewVariables] public RSI.State? ActualState => _actualState;
@@ -2033,15 +2048,49 @@ namespace Robust.Client.GameObjects
internal void AdvanceFrameAnimation(RSI.State state)
{
// Can't advance frames without more than 1 delay which is already checked above.
var delayCount = state.DelayCount;
while (AnimationTimeLeft < 0)
{
AnimationFrame += 1;
if (AnimationFrame >= delayCount)
if (Reversed)
{
AnimationFrame = 0;
AnimationTime = -AnimationTimeLeft;
AnimationFrame -= 1;
// Animation finished, do we cycle back to positive or reset.
if (AnimationFrame < 0)
{
if (Cycle)
{
AnimationFrame = 1;
Reversed = false;
}
else
{
AnimationFrame = delayCount - 1;
}
AnimationTime = -AnimationTimeLeft;
}
}
else
{
AnimationFrame += 1;
// Animation finished, do we reverse or reset.
if (AnimationFrame >= delayCount)
{
if (Cycle)
{
AnimationFrame = delayCount - 2;
Reversed = true;
}
else
{
AnimationFrame = 0;
}
AnimationTime = -AnimationTimeLeft;
}
}
AnimationTimeLeft += state.GetDelay(AnimationFrame);

View File

@@ -1,14 +1,11 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Map.Enumerators;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using static Robust.Shared.GameObjects.OccluderComponent;
namespace Robust.Client.GameObjects;
@@ -34,13 +31,12 @@ internal sealed class ClientOccluderSystem : OccluderSystem
SubscribeLocalEvent<OccluderComponent, ComponentShutdown>(OnShutdown);
}
public override void SetEnabled(EntityUid uid, bool enabled, OccluderComponent? comp = null)
public override void SetEnabled(EntityUid uid, bool enabled, OccluderComponent? comp = null, MetaDataComponent? meta = null)
{
if (!Resolve(uid, ref comp, false) || enabled == comp.Enabled)
return;
comp.Enabled = enabled;
Dirty(uid, comp);
base.SetEnabled(uid, enabled, comp, meta);
var xform = Transform(uid);
QueueTreeUpdate(uid, comp, xform);

View File

@@ -54,12 +54,24 @@ namespace Robust.Client.GameObjects
DebugTools.Assert(ExpectedEntities.TryGetValue(netEntity, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(netEntity));
}
private void HandleEntityInitialized(EntityUid uid)
private void HandleEntityInitialized(Entity<MetaDataComponent> ent)
{
if (!RemoveExpectedEntity(GetNetEntity(uid), out var container))
var (uid, meta) = ent;
if (!RemoveExpectedEntity(meta.NetEntity, out var container))
return;
container.Insert(uid, EntityManager, transform: TransformQuery.GetComponent(uid), meta: MetaQuery.GetComponent(uid));
Insert((uid, TransformQuery.GetComponent(uid), MetaQuery.GetComponent(uid), null), container);
}
public override void ShutdownContainer(BaseContainer container)
{
foreach (var ent in container.ExpectedEntities)
{
if (ExpectedEntities.Remove(ent, out var c))
DebugTools.Assert(c == container);
}
base.ShutdownContainer(container);
}
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ref ComponentHandleState args)
@@ -81,17 +93,17 @@ namespace Robust.Client.GameObjects
foreach (var entity in container.ContainedEntities.ToArray())
{
container.Remove(entity,
EntityManager,
TransformQuery.GetComponent(entity),
MetaQuery.GetComponent(entity),
Remove(
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
container,
force: true,
reparent: false);
reparent: false
);
DebugTools.Assert(!container.Contains(entity));
}
container.Shutdown(EntityManager, _netMan);
ShutdownContainer(container);
toDelete.Add(id);
}
@@ -108,7 +120,7 @@ namespace Robust.Client.GameObjects
{
var type = _serializer.FindSerializedType(typeof(BaseContainer), data.ContainerType);
container = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type!, inject:false);
container.Init(id, uid, component);
InitContainer(container, (uid, component), id);
component.Containers.Add(id, container);
}
@@ -132,13 +144,12 @@ namespace Robust.Client.GameObjects
foreach (var entity in toRemove)
{
container.Remove(
entity,
EntityManager,
TransformQuery.GetComponent(entity),
MetaQuery.GetComponent(entity),
Remove(
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
container,
force: true,
reparent: false);
reparent: false
);
DebugTools.Assert(!container.Contains(entity));
}
@@ -188,11 +199,12 @@ namespace Robust.Client.GameObjects
continue;
RemoveExpectedEntity(netEnt, out _);
container.Insert(entity, EntityManager,
TransformQuery.GetComponent(entity),
Insert(
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity), null),
container,
xform,
MetaQuery.GetComponent(entity),
force: true);
force: true
);
DebugTools.Assert(container.Contains(entity));
}
@@ -222,7 +234,7 @@ namespace Robust.Client.GameObjects
return;
}
container.Insert(message.Entity, EntityManager);
Insert(message.Entity, container);
}
public void AddExpectedEntity(NetEntity netEntity, BaseContainer container)
@@ -324,32 +336,30 @@ namespace Robust.Client.GameObjects
if (_pointLightQuery.TryGetComponent(entity, out var light))
_lightSys.SetContainerOccluded(entity, lightOccluded, light);
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))
foreach (var child in xform._children)
{
// 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))
if (manager.TryGetContainer(child, out var container))
{
childSpriteOccluded = childSpriteOccluded || !container.ShowContents;
childLightOccluded = childLightOccluded || container.OccludesLight;
}
UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), childSpriteOccluded, childLightOccluded);
UpdateEntity(child, TransformQuery.GetComponent(child), childSpriteOccluded, childLightOccluded);
}
}
else
{
while (childEnumerator.MoveNext(out var child))
foreach (var child in xform._children)
{
UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), spriteOccluded, lightOccluded);
UpdateEntity(child, TransformQuery.GetComponent(child), spriteOccluded, lightOccluded);
}
}
}

View File

@@ -8,30 +8,28 @@ using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Dynamics;
namespace Robust.Client.GameObjects
namespace Robust.Client.GameObjects;
public sealed class MapSystem : SharedMapSystem
{
public sealed class MapSystem : SharedMapSystem
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IResourceCache _resource = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
public override void Initialize()
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IResourceCache _resource = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
base.Initialize();
_overlayManager.AddOverlay(new TileEdgeOverlay(EntityManager, _resource, _tileDefinitionManager));
}
public override void Initialize()
{
base.Initialize();
_overlayManager.AddOverlay(new TileEdgeOverlay(EntityManager, _mapManager, _resource, _tileDefinitionManager));
}
public override void Shutdown()
{
base.Shutdown();
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
}
public override void Shutdown()
{
base.Shutdown();
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
}
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
{
EnsureComp<PhysicsMapComponent>(uid);
}
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
{
EnsureComp<PhysicsMapComponent>(uid);
}
}

View File

@@ -68,6 +68,10 @@ namespace Robust.Client.GameObjects
return RemCompDeferred<PointLightComponent>(uid);
}
protected override void UpdatePriority(EntityUid uid, SharedPointLightComponent comp, MetaDataComponent meta)
{
}
private void HandleInit(EntityUid uid, PointLightComponent component, ComponentInit args)
{
SetMask(component.MaskPath, component);
@@ -95,28 +99,23 @@ namespace Robust.Client.GameObjects
_lightTree.QueueTreeUpdate(uid, clientComp);
}
public override void SetEnabled(EntityUid uid, bool enabled, SharedPointLightComponent? comp = null)
public override void SetEnabled(EntityUid uid, bool enabled, SharedPointLightComponent? comp = null, MetaDataComponent? meta = null)
{
if (!ResolveLight(uid, ref comp) || enabled == comp.Enabled || comp is not PointLightComponent clientComp)
return;
comp.Enabled = enabled;
RaiseLocalEvent(uid, new PointLightToggleEvent(comp.Enabled));
Dirty(uid, comp);
base.SetEnabled(uid, enabled, comp, meta);
if (!comp.ContainerOccluded)
_lightTree.QueueTreeUpdate(uid, clientComp);
}
public override void SetRadius(EntityUid uid, float radius, SharedPointLightComponent? comp = null)
public override void SetRadius(EntityUid uid, float radius, SharedPointLightComponent? comp = null, MetaDataComponent? meta = null)
{
if (!ResolveLight(uid, ref comp) || MathHelper.CloseToPercent(radius, comp.Radius) ||
comp is not PointLightComponent clientComp)
return;
comp.Radius = radius;
Dirty(uid, comp);
base.SetRadius(uid, radius, comp, meta);
if (clientComp.TreeUid != null)
_lightTree.QueueTreeUpdate(uid, clientComp);
}

View File

@@ -122,14 +122,13 @@ public sealed partial class SpriteSystem
return GetFallbackState();
}
private void OnPrototypesReloaded(PrototypesReloadedEventArgs protoReloaded)
private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
{
// Check if any EntityPrototype has been changed.
if (!protoReloaded.ByType.TryGetValue(typeof(EntityPrototype), out var changedSet))
if (!args.TryGetModified<EntityPrototype>(out var modified))
return;
// Remove all changed prototypes from the cache, if they're there.
foreach (var (prototype, _) in changedSet.Modified)
foreach (var prototype in modified)
{
// Let's be lazy and not regenerate them until something needs them again.
_cachedPrototypeIcons.Remove(prototype);

View File

@@ -58,7 +58,7 @@ namespace Robust.Client.GameObjects
UpdatesAfter.Add(typeof(SpriteTreeSystem));
_proto.PrototypesReloaded += OnPrototypesReloaded;
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
SubscribeLocalEvent<SpriteComponent, SpriteUpdateInertEvent>(QueueUpdateInert);
SubscribeLocalEvent<SpriteComponent, ComponentInit>(OnInit);
@@ -75,7 +75,6 @@ namespace Robust.Client.GameObjects
public override void Shutdown()
{
base.Shutdown();
_proto.PrototypesReloaded -= OnPrototypesReloaded;
_cfg.UnsubValueChanged(CVars.RenderSpriteDirectionBias, OnBiasChanged);
}

View File

@@ -43,7 +43,7 @@ public sealed class ClientDirtySystem : EntitySystem
return;
// Client-side entity deletion is not supported and will cause errors.
Log.Error($"Predicting the deletion of a networked entity: {ToPrettyString(ev.Entity)}. Trace: {Environment.StackTrace}");
Log.Error($"Predicting the deletion of a networked entity: {ToPrettyString(ev.Entity.Owner, ev.Entity.Comp)}. Trace: {Environment.StackTrace}");
}
private void OnCompRemoved(RemovedComponentEventArgs args)
@@ -71,9 +71,9 @@ public sealed class ClientDirtySystem : EntitySystem
RemovedComponents.Clear();
}
private void OnEntityDirty(EntityUid e)
private void OnEntityDirty(Entity<MetaDataComponent> e)
{
if (_timing.InPrediction && !IsClientSide(e))
if (_timing.InPrediction && !IsClientSide(e, e))
DirtyEntities.Add(e);
}
}

View File

@@ -49,16 +49,16 @@ namespace Robust.Client.GameStates
// Game state dictionaries that get used every tick.
private readonly Dictionary<EntityUid, (NetEntity NetEntity, MetaDataComponent Meta, bool EnteringPvs, GameTick LastApplied, EntityState? curState, EntityState? nextState)> _toApply = new();
private readonly Dictionary<NetEntity, EntityState> _toCreate = new();
private readonly Dictionary<ushort, (IComponent Component, ComponentState? curState, ComponentState? nextState)> _compStateWork = new();
private readonly Dictionary<ushort, (IComponent Component, IComponentState? curState, IComponentState? nextState)> _compStateWork = new();
private readonly Dictionary<EntityUid, HashSet<Type>> _pendingReapplyNetStates = new();
private readonly HashSet<NetEntity> _stateEnts = new();
private readonly List<EntityUid> _toDelete = new();
private readonly List<IComponent> _toRemove = new();
private readonly Dictionary<NetEntity, Dictionary<ushort, ComponentState>> _outputData = new();
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState>> _outputData = new();
private readonly List<(EntityUid, TransformComponent)> _queuedBroadphaseUpdates = new();
private readonly ObjectPool<Dictionary<ushort, ComponentState>> _compDataPool =
new DefaultObjectPool<Dictionary<ushort, ComponentState>>(new DictPolicy<ushort, ComponentState>(), 256);
private readonly ObjectPool<Dictionary<ushort, IComponentState>> _compDataPool =
new DefaultObjectPool<Dictionary<ushort, IComponentState>>(new DictPolicy<ushort, IComponentState>(), 256);
private uint _metaCompNetId;
@@ -257,7 +257,7 @@ namespace Robust.Client.GameStates
public void UpdateFullRep(GameState state, bool cloneDelta = false)
=> _processor.UpdateFullRep(state, cloneDelta);
public Dictionary<NetEntity, Dictionary<ushort, ComponentState>> GetFullRep()
public Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep()
=> _processor.GetFullRep();
private void HandlePvsLeaveMessage(MsgStateLeavePvs message)
@@ -343,7 +343,7 @@ namespace Robust.Client.GameStates
}
// If we were waiting for a new state, we are now applying it.
if (_processor.WaitingForFull)
if (curState.FromSequence == GameTick.Zero)
{
_processor.OnFullStateReceived();
_timing.LastProcessedTick = curState.ToSequence;
@@ -351,7 +351,10 @@ namespace Robust.Client.GameStates
PartialStateReset(curState, true);
}
else
{
DebugTools.Assert(!_processor.WaitingForFull);
_timing.LastProcessedTick += 1;
}
_timing.CurTick = _timing.LastRealTick = _timing.LastProcessedTick;
@@ -532,7 +535,6 @@ namespace Robust.Client.GameStates
using var __ = _timing.StartStateApplicationArea();
// This is terrible, and I hate it. This also needs to run even when prediction is disabled.
_entitySystemManager.GetEntitySystem<SharedGridTraversalSystem>().QueuedEvents.Clear();
_entitySystemManager.GetEntitySystem<TransformSystem>().Reset();
if (!PredictionNeedsResetting)
@@ -665,14 +667,13 @@ namespace Robust.Client.GameStates
foreach (var netEntity in createdEntities)
{
var (createdEntity, meta) = _entityManager.GetEntityData(netEntity);
var (_, meta) = _entityManager.GetEntityData(netEntity);
var compData = _compDataPool.Get();
_outputData.Add(netEntity, compData);
foreach (var (netId, component) in meta.NetComponents)
{
if (!component.NetSyncEnabled)
continue;
DebugTools.Assert(component.NetSyncEnabled);
var state = _entityManager.GetComponentState(bus, component, null, GameTick.Zero);
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
@@ -778,15 +779,13 @@ namespace Robust.Client.GameStates
newMeta.LastStateApplied = curState.ToSequence;
// Check if there's any component states awaiting this entity.
if (_entityManager.PendingNetEntityStates.TryGetValue(es.NetEntity, out var value))
if (_entityManager.PendingNetEntityStates.Remove(es.NetEntity, out var value))
{
foreach (var (type, owner) in value)
{
var pending = _pendingReapplyNetStates.GetOrNew(owner);
pending.Add(type);
}
_entityManager.PendingNetEntityStates.Remove(es.NetEntity);
}
}
@@ -850,6 +849,17 @@ namespace Robust.Client.GameStates
if (!metas.TryGetComponent(uid, out var meta))
continue;
// It may also have been queued for deletion, in which case its last server state entry has already been removed.
// I love me some spaghetti order-of-operation dependent code
if (!_processor._lastStateFullRep.ContainsKey(meta.NetEntity))
{
DebugTools.Assert(curState.EntityDeletions.Value.Contains(meta.NetEntity));
continue;
}
DebugTools.Assert(!curState.EntityDeletions.Value.Contains(meta.NetEntity));
// State already being re-applied so don't bulldoze it.
ref var state = ref CollectionsMarshal.GetValueRefOrAddDefault(_toApply, uid, out var exists);
@@ -983,16 +993,15 @@ namespace Robust.Client.GameStates
xformSys.DetachParentToNull(ent, xform);
// Then detach all children.
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
foreach (var child in xform._children)
{
xformSys.DetachParentToNull(child.Value, xforms.GetComponent(child.Value), xform);
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
if (deleteClientChildren
&& !deleteClientEntities // don't add duplicates
&& _entities.IsClientSide(child.Value))
&& _entities.IsClientSide(child))
{
_toDelete.Add(child.Value);
_toDelete.Add(child);
}
}
@@ -1040,7 +1049,7 @@ namespace Robust.Client.GameStates
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
xformSys.DetachParentToNull(child.Value, xforms.GetComponent(child.Value), xform);
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
}
// Finally, delete the entity.
@@ -1131,7 +1140,7 @@ namespace Robust.Client.GameStates
(containerMeta.Flags & MetaDataFlags.Detached) == 0 &&
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container, null, true))
{
container.Remove(ent.Value, _entities, xform, meta, false, true);
containerSys.Remove((ent.Value, xform, meta), container, false, true);
}
meta._flags |= MetaDataFlags.Detached;
@@ -1219,7 +1228,9 @@ namespace Robust.Client.GameStates
foreach (var (id, comp) in meta.NetComponents)
{
if (comp.NetSyncEnabled && !curState.NetComponents.Contains(id))
DebugTools.Assert(comp.NetSyncEnabled);
if (!curState.NetComponents.Contains(id))
_toRemove.Add(comp);
}
@@ -1429,7 +1440,7 @@ namespace Robust.Client.GameStates
void _recursiveRemoveState(NetEntity netEntity, TransformComponent xform, EntityQuery<MetaDataComponent> metaQuery, EntityQuery<TransformComponent> xformQuery)
{
_processor._lastStateFullRep.Remove(netEntity);
foreach (var child in xform.ChildEntities)
foreach (var child in xform._children)
{
if (xformQuery.TryGetComponent(child, out var childXform) &&
metaQuery.TryGetComponent(child, out var childMeta))

View File

@@ -32,7 +32,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// This dictionary stores the full most recently received server state of any entity. This is used whenever predicted entities get reset.
/// </summary>
internal readonly Dictionary<NetEntity, Dictionary<ushort, ComponentState>> _lastStateFullRep
internal readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState>> _lastStateFullRep
= new();
/// <inheritdoc />
@@ -212,7 +212,7 @@ Had full state: {LastFullState != null}"
{
if (!_lastStateFullRep.TryGetValue(entityState.NetEntity, out var compData))
{
compData = new Dictionary<ushort, ComponentState>();
compData = new Dictionary<ushort, IComponentState>();
_lastStateFullRep.Add(entityState.NetEntity, compData);
}
@@ -391,7 +391,7 @@ Had full state: {LastFullState != null}"
LastFullStateRequested = null;
}
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, ComponentState>> implicitData)
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState>> implicitData)
{
foreach (var (netEntity, implicitEntState) in implicitData)
{
@@ -425,18 +425,18 @@ Had full state: {LastFullState != null}"
}
}
public Dictionary<ushort, ComponentState> GetLastServerStates(NetEntity netEntity)
public Dictionary<ushort, IComponentState> GetLastServerStates(NetEntity netEntity)
{
return _lastStateFullRep[netEntity];
}
public Dictionary<NetEntity, Dictionary<ushort, ComponentState>> GetFullRep()
public Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep()
{
return _lastStateFullRep;
}
public bool TryGetLastServerStates(NetEntity entity,
[NotNullWhen(true)] out Dictionary<ushort, ComponentState>? dictionary)
[NotNullWhen(true)] out Dictionary<ushort, IComponentState>? dictionary)
{
return _lastStateFullRep.TryGetValue(entity, out dictionary);
}

View File

@@ -113,7 +113,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// Returns the full collection of cached game states that are used to reset predicted entities.
/// </summary>
Dictionary<NetEntity, Dictionary<ushort, ComponentState>> GetFullRep();
Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep();
/// <summary>
/// This will perform some setup in order to reset the game to an earlier state. To fully reset the state

View File

@@ -83,13 +83,13 @@ namespace Robust.Client.GameStates
/// The data to merge.
/// It's a dictionary of entity ID -> (component net ID -> ComponentState)
/// </param>
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, ComponentState>> data);
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState>> data);
/// <summary>
/// Get the last state data from the server for an entity.
/// </summary>
/// <returns>Dictionary (net ID -> ComponentState)</returns>
Dictionary<ushort, ComponentState> GetLastServerStates(NetEntity entity);
Dictionary<ushort, IComponentState> GetLastServerStates(NetEntity entity);
/// <summary>
/// Calculate the number of applicable states in the game state buffer from a given tick.
@@ -99,6 +99,6 @@ namespace Robust.Client.GameStates
int GetApplicableStateCount(GameTick? fromTick);
bool TryGetLastServerStates(NetEntity entity,
[NotNullWhen(true)] out Dictionary<ushort, ComponentState>? dictionary);
[NotNullWhen(true)] out Dictionary<ushort, IComponentState>? dictionary);
}
}

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
@@ -18,10 +20,15 @@ namespace Robust.Client.Graphics.Clyde
private readonly Dictionary<EntityUid, Dictionary<Vector2i, MapChunkData>> _mapChunkData =
new();
/// <summary>
/// To avoid spamming errors we'll just log it once and move on.
/// </summary>
private HashSet<Type> _erroredGridOverlays = new();
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)
private void _drawGrids(Viewport viewport, Box2 worldAABB, Box2Rotated worldBounds, IEye eye)
{
var mapId = eye.Position.MapId;
if (!_mapManager.MapExists(mapId))
@@ -30,27 +37,35 @@ namespace Robust.Client.Graphics.Clyde
mapId = MapId.Nullspace;
}
SetTexture(TextureUnit.Texture0, _tileDefinitionManager.TileTextureAtlas);
SetTexture(TextureUnit.Texture1, _lightingReady ? viewport.LightRenderTarget.Texture : _stockTextureWhite);
var gridProgram = ActivateShaderInstance(_defaultShader.Handle).Item1;
SetupGlobalUniformsImmediate(gridProgram, (ClydeTexture) _tileDefinitionManager.TileTextureAtlas);
gridProgram.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
gridProgram.SetUniformTextureMaybe(UniILightTexture, TextureUnit.Texture1);
gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1));
var grids = new List<Entity<MapGridComponent>>();
_mapManager.FindGridsIntersecting(mapId, worldBounds, ref grids);
var requiresFlush = true;
GLShaderProgram gridProgram = default!;
var gridOverlays = GetOverlaysForSpace(OverlaySpace.WorldSpaceGrids);
foreach (var mapGrid in grids)
{
if (!_mapChunkData.ContainsKey(mapGrid))
if (!_mapChunkData.TryGetValue(mapGrid, out var data))
{
continue;
}
if (requiresFlush)
{
SetTexture(TextureUnit.Texture0, _tileDefinitionManager.TileTextureAtlas);
SetTexture(TextureUnit.Texture1, _lightingReady ? viewport.LightRenderTarget.Texture : _stockTextureWhite);
gridProgram = ActivateShaderInstance(_defaultShader.Handle).Item1;
SetupGlobalUniformsImmediate(gridProgram, (ClydeTexture) _tileDefinitionManager.TileTextureAtlas);
gridProgram.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
gridProgram.SetUniformTextureMaybe(UniILightTexture, TextureUnit.Texture1);
gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1));
}
var transform = _entityManager.GetComponent<TransformComponent>(mapGrid);
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
var enumerator = mapGrid.Comp.GetMapChunks(worldBounds);
var data = _mapChunkData[mapGrid];
while (enumerator.MoveNext(out var chunk))
{
@@ -72,6 +87,31 @@ namespace Robust.Client.Graphics.Clyde
GL.DrawElements(GetQuadGLPrimitiveType(), datum.TileCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
CheckGlError();
}
requiresFlush = false;
foreach (var overlay in gridOverlays)
{
if (overlay is not IGridOverlay iGrid)
{
if (!_erroredGridOverlays.Add(overlay.GetType()))
{
_clydeSawmill.Error($"Tried to render grid overlay {overlay.GetType()} that doesn't implement {nameof(IGridOverlay)}");
}
continue;
}
iGrid.Grid = mapGrid;
iGrid.RequiresFlush = false;
RenderSingleWorldOverlay(overlay, viewport, OverlaySpace.WorldSpaceGrids, worldAABB, worldBounds);
requiresFlush |= iGrid.RequiresFlush;
}
if (requiresFlush)
{
FlushRenderQueue();
}
}
CullEmptyChunks();

View File

@@ -497,7 +497,7 @@ namespace Robust.Client.Graphics.Clyde
using (DebugGroup("Grids"))
using (_prof.Group("Grids"))
{
_drawGrids(viewport, worldBounds, eye);
_drawGrids(viewport, worldAABB, worldBounds, eye);
}
// We will also render worldspace overlays here so we can do them under / above entities as necessary

View File

@@ -38,6 +38,11 @@ namespace Robust.Client.Graphics.Clyde
_clyde.DrawSetModelTransform(matrix);
}
public Matrix3 GetModelTransform()
{
return _clyde.DrawGetModelTransform();
}
public void SetProjView(in Matrix3 proj, in Matrix3 view)
{
_clyde.DrawSetProjViewTransform(proj, view);
@@ -222,7 +227,14 @@ namespace Robust.Client.Graphics.Clyde
var clydeShader = (ClydeShaderInstance?) shader;
_clyde.DrawUseShader(clydeShader?.Handle ?? _clyde._defaultShader.Handle);
_clyde.DrawUseShader(clydeShader ?? _clyde._defaultShader);
}
public ShaderInstance? GetShader()
{
return _clyde._queuedShaderInstance == _clyde._defaultShader
? null
: _clyde._queuedShaderInstance;
}
public void Viewport(Box2i viewport)
@@ -285,11 +297,21 @@ namespace Robust.Client.Graphics.Clyde
_renderHandle.SetModelTransform(matrix);
}
public override Matrix3 GetTransform()
{
return _renderHandle.GetModelTransform();
}
public override void UseShader(ShaderInstance? shader)
{
_renderHandle.UseShader(shader);
}
public override ShaderInstance? GetShader()
{
return _renderHandle.GetShader();
}
public override void DrawPrimitives(DrawPrimitiveTopology primitiveTopology, Texture texture,
ReadOnlySpan<DrawVertexUV2DColor> vertices)
{
@@ -380,11 +402,21 @@ namespace Robust.Client.Graphics.Clyde
_renderHandle.SetModelTransform(matrix);
}
public override Matrix3 GetTransform()
{
return _renderHandle.GetModelTransform();
}
public override void UseShader(ShaderInstance? shader)
{
_renderHandle.UseShader(shader);
}
public override ShaderInstance? GetShader()
{
return _renderHandle.GetShader();
}
public override void DrawCircle(Vector2 position, float radius, Color color, bool filled = true)
{
int divisions = Math.Max(16,(int)(radius * 16));

View File

@@ -78,7 +78,9 @@ namespace Robust.Client.Graphics.Clyde
// private LoadedTexture? _batchLoadedTexture;
// Contains the shader instance that's currently being used by the (queue) stage for new commands.
private ClydeHandle _queuedShader;
private ClydeHandle _queuedShader => _queuedShaderInstance.Handle;
private ClydeShaderInstance _queuedShaderInstance = default!;
// Current projection & view matrices that are being used ot render.
// This gets updated to keep track during (queue) and (misc), but not during (submit).
@@ -314,7 +316,7 @@ namespace Robust.Client.Graphics.Clyde
// Reset renderer state.
_currentMatrixModel = Matrix3.Identity;
_queuedShader = _defaultShader.Handle;
_queuedShaderInstance = _defaultShader;
SetScissorFull(null);
}
@@ -443,9 +445,13 @@ namespace Robust.Client.Graphics.Clyde
_isStencilling = false;
}
if (!instance.ParametersDirty)
if (instance.Parameters.Count == 0)
return (program, instance);
if (shader.LastInstance == instance && !instance.ParametersDirty)
return (program, instance);
shader.LastInstance = instance;
instance.ParametersDirty = false;
int textureUnitVal = 0;
@@ -531,6 +537,11 @@ namespace Robust.Client.Graphics.Clyde
_currentMatrixModel = matrix;
}
private Matrix3 DrawGetModelTransform()
{
return _currentMatrixModel;
}
private void DrawSetProjViewTransform(in Matrix3 proj, in Matrix3 view)
{
BreakBatch();
@@ -700,9 +711,9 @@ namespace Robust.Client.Graphics.Clyde
_currentScissorState = scissorBox;
}
private void DrawUseShader(ClydeHandle handle)
private void DrawUseShader(ClydeShaderInstance instance)
{
_queuedShader = handle;
_queuedShaderInstance = instance;
}
private void DrawClear(Color color, int stencil, ClearBufferMask mask)
@@ -875,7 +886,7 @@ namespace Robust.Client.Graphics.Clyde
SetScissorFull(null);
BindRenderTargetFull(_mainWindow!.RenderTarget);
_batchMetaData = null;
_queuedShader = _defaultShader.Handle;
_queuedShaderInstance = _defaultShader;
GL.Viewport(0, 0, _mainWindow!.FramebufferSize.X, _mainWindow!.FramebufferSize.Y);
}

View File

@@ -48,6 +48,10 @@ namespace Robust.Client.Graphics.Clyde
[ViewVariables]
public string? Name;
// Last instance that used this shader.
// Used to ensure that shader uniforms get updated.
public LoadedShaderInstance? LastInstance;
}
private sealed class LoadedShaderInstance
@@ -158,7 +162,7 @@ namespace Robust.Client.Graphics.Clyde
_defaultShader = (ClydeShaderInstance) InstanceShader(defaultLoadedShader);
_queuedShader = _defaultShader.Handle;
_queuedShaderInstance = _defaultShader;
}
private string ReadEmbeddedShader(string fileName)

View File

@@ -198,6 +198,7 @@ namespace Robust.Client.Graphics.Clyde
}
IRenderTexture IClydeViewport.RenderTarget => RenderTarget;
IRenderTexture IClydeViewport.LightRenderTarget => LightRenderTarget;
public IEye? Eye { get; set; }
}
}

View File

@@ -76,6 +76,7 @@ namespace Robust.Client.Graphics.Clyde
private Thread? _gameThread;
private ISawmill _clydeSawmill = default!;
private ISawmill _sawmillOgl = default!;
private IBindingsContext _glBindingsContext = default!;
@@ -91,6 +92,7 @@ namespace Robust.Client.Graphics.Clyde
public bool InitializePreWindowing()
{
_clydeSawmill = _logManager.GetSawmill("clyde");
_sawmillOgl = _logManager.GetSawmill("clyde.ogl");
_cfg.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);

View File

@@ -463,6 +463,9 @@ namespace Robust.Client.Graphics.Clyde
public IRenderTexture RenderTarget { get; } =
new DummyRenderTexture(Vector2i.One, new DummyTexture(Vector2i.One));
public IRenderTexture LightRenderTarget { get; } =
new DummyRenderTexture(Vector2i.One, new DummyTexture(Vector2i.One));
public IEye? Eye { get; set; }
public Vector2i Size { get; }
public Color? ClearColor { get; set; } = Color.Black;

View File

@@ -20,11 +20,5 @@ namespace Robust.Client.Graphics.Clyde
public ShaderCompilationException(string message, Exception inner) : base(message, inner)
{
}
protected ShaderCompilationException(
SerializationInfo info,
StreamingContext context) : base(info, context)
{
}
}
}

View File

@@ -137,5 +137,70 @@ highp vec4 zTexture(highp vec2 uv)
return zTextureSpec(TEXTURE, uv);
}
// -- color --
// Grayscale function for the ITU's Rec BT-709. Primarily intended for HDTVs, but standard sRGB monitors are coincidentally extremely close.
highp float zGrayscale_BT709(highp vec3 col) {
return dot(col, vec3(0.2126, 0.7152, 0.0722));
}
// Grayscale function for the ITU's Rec BT-601, primarily intended for SDTV, but amazing for a handful of niche use-cases.
highp float zGrayscale_BT601(highp vec3 col) {
return dot(col, vec3(0.299, 0.587, 0.114));
}
// If you don't have any reason to be specifically using the above grayscale functions, then you should default to this.
highp float zGrayscale(highp vec3 col) {
return zGrayscale_BT709(col);
}
// -- noise --
//zRandom, zNoise, and zFBM are derived from https://godotshaders.com/snippet/2d-noise/ and https://godotshaders.com/snippet/fractal-brownian-motion-fbm/
highp vec2 zRandom(highp vec2 uv){
uv = vec2( dot(uv, vec2(127.1,311.7) ),
dot(uv, vec2(269.5,183.3) ) );
return -1.0 + 2.0 * fract(sin(uv) * 43758.5453123);
}
highp float zNoise(highp vec2 uv) {
highp vec2 uv_index = floor(uv);
highp vec2 uv_fract = fract(uv);
highp vec2 blur = smoothstep(0.0, 1.0, uv_fract);
return mix( mix( dot( zRandom(uv_index + vec2(0.0,0.0) ), uv_fract - vec2(0.0,0.0) ),
dot( zRandom(uv_index + vec2(1.0,0.0) ), uv_fract - vec2(1.0,0.0) ), blur.x),
mix( dot( zRandom(uv_index + vec2(0.0,1.0) ), uv_fract - vec2(0.0,1.0) ),
dot( zRandom(uv_index + vec2(1.0,1.0) ), uv_fract - vec2(1.0,1.0) ), blur.x), blur.y) * 0.5 + 0.5;
}
highp float zFBM(highp vec2 uv) {
const int octaves = 6;
highp float amplitude = 0.5;
highp float frequency = 3.0;
highp float value = 0.0;
for(int i = 0; i < octaves; i++) {
value += amplitude * zNoise(frequency * uv);
amplitude *= 0.5;
frequency *= 2.0;
}
return value;
}
// -- generative --
// Function that creates a circular gradient. Screenspace shader bread n butter.
highp float zCircleGradient(highp vec2 ps, highp vec2 coord, highp float maxi, highp float radius, highp float dist, highp float power) {
highp float rad = (radius * ps.y) * 0.001;
highp float aspectratio = ps.x / ps.y;
highp vec2 totaldistance = ((ps * 0.5) - coord) / (rad * ps);
totaldistance.x *= aspectratio;
highp float length = (length(totaldistance) * ps.y) - dist;
return pow(clamp(length, 0.0, maxi), power);
}
// -- Utilities End --

View File

@@ -1,3 +1,4 @@
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Robust.Shared;
@@ -65,7 +66,7 @@ namespace Robust.Client.Graphics.Clyde
return MouseButtonMap[button];
}
private static readonly Dictionary<GlfwButton, Button> MouseButtonMap = new()
private static readonly FrozenDictionary<GlfwButton, Button> MouseButtonMap = new Dictionary<GlfwButton, Button>()
{
{GlfwButton.Left, Button.Left},
{GlfwButton.Middle, Button.Middle},
@@ -75,10 +76,10 @@ namespace Robust.Client.Graphics.Clyde
{GlfwButton.Button6, Button.Button6},
{GlfwButton.Button7, Button.Button7},
{GlfwButton.Button8, Button.Button8},
};
}.ToFrozenDictionary();
private static readonly Dictionary<GlfwKey, Key> KeyMap;
private static readonly Dictionary<Key, GlfwKey> KeyMapReverse;
private static readonly FrozenDictionary<GlfwKey, Key> KeyMap;
private static readonly FrozenDictionary<Key, GlfwKey> KeyMapReverse;
internal static Key ConvertGlfwKey(GlfwKey key)
@@ -218,14 +219,16 @@ namespace Robust.Client.Graphics.Clyde
{GlfwKey.F24, Key.F24},
{GlfwKey.Pause, Key.Pause},
{GlfwKey.World1, Key.World1},
};
}.ToFrozenDictionary();
KeyMapReverse = new Dictionary<Key, GlfwKey>();
var keyMapReverse = new Dictionary<Key, GlfwKey>();
foreach (var (key, value) in KeyMap)
{
KeyMapReverse[value] = key;
keyMapReverse[value] = key;
}
KeyMapReverse = keyMapReverse.ToFrozenDictionary();
}
}
}

View File

@@ -10,8 +10,11 @@ using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp.PixelFormats;
using TerraFX.Interop.Windows;
using TerraFX.Interop.Xlib;
using GlfwImage = OpenToolkit.GraphicsLibraryFramework.Image;
using Monitor = OpenToolkit.GraphicsLibraryFramework.Monitor;
using Window = OpenToolkit.GraphicsLibraryFramework.Window;
using X11Window = TerraFX.Interop.Xlib.Window;
namespace Robust.Client.Graphics.Clyde
{
@@ -468,6 +471,11 @@ namespace Robust.Client.Graphics.Clyde
GLFW.MaximizeWindow(window);
}
if ((parameters.Styles & OSWindowStyles.NoTitleBar) != 0)
{
GLFW.WindowHint(WindowHintBool.Decorated, false);
}
if ((parameters.Styles & OSWindowStyles.NoTitleOptions) != 0)
{
if (OperatingSystem.IsWindows())
@@ -481,6 +489,35 @@ namespace Robust.Client.Graphics.Clyde
// Cast to long here to work around a bug in rider with nint bitwise operators.
(nint)((long)Windows.GetWindowLongPtrW(hWnd, GWL.GWL_STYLE) & ~WS.WS_SYSMENU));
}
else if (OperatingSystem.IsLinux())
{
try
{
var x11Window = (X11Window)GLFW.GetX11Window(window);
var x11Display = (Display*) GLFW.GetX11Display(window);
DebugTools.Assert(x11Window != X11Window.NULL);
// https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html#idm46181547486832
var newPropValString = Marshal.StringToCoTaskMemUTF8("_NET_WM_WINDOW_TYPE_DIALOG");
var newPropVal = Xlib.XInternAtom(x11Display, (sbyte*)newPropValString, Xlib.False);
DebugTools.Assert(newPropVal != Atom.NULL);
var propNameString = Marshal.StringToCoTaskMemUTF8("_NET_WM_WINDOW_TYPE");
#pragma warning disable CA1806
// [display] [window] [property] [type] [format (8, 16,32)] [mode] [data] [element count]
Xlib.XChangeProperty(x11Display, x11Window,
Xlib.XInternAtom(x11Display, (sbyte*)propNameString, Xlib.False), // should never be null; part of spec
Xlib.XA_ATOM, 32, Xlib.PropModeReplace,
(byte*)&newPropVal, 1);
#pragma warning restore CA1806
Marshal.FreeCoTaskMem(newPropValString);
Marshal.FreeCoTaskMem(propNameString);
}
catch (EntryPointNotFoundException)
{
_sawmill.Warning("OSWindowStyles.NoTitleOptions not implemented on this windowing manager");
}
}
else
{
_sawmill.Warning("OSWindowStyles.NoTitleOptions not implemented on this platform");
@@ -500,6 +537,25 @@ namespace Robust.Client.Graphics.Clyde
GWLP.GWLP_HWNDPARENT,
ownerHWnd);
}
else if (OperatingSystem.IsLinux())
{
try
{
var x11Display = (Display*) GLFW.GetX11Display(window);
var thisWindow = (X11Window)GLFW.GetX11Window(window);
var parentWindow = (X11Window)GLFW.GetX11Window(ownerWindow);
DebugTools.Assert(thisWindow != X11Window.NULL);
DebugTools.Assert(parentWindow != X11Window.NULL);
#pragma warning disable CA1806
Xlib.XSetTransientForHint(x11Display, thisWindow, parentWindow);
#pragma warning restore CA1806
}
catch (EntryPointNotFoundException)
{
_sawmill.Warning("owner windows not implemented on this windowing manager");
}
}
else
{
_sawmill.Warning("owner windows not implemented on this platform");
@@ -598,7 +654,7 @@ namespace Robust.Client.Graphics.Clyde
private static void WinThreadGetClipboard(CmdGetClipboard cmd)
{
var clipboard = GLFW.GetClipboardString((Window*) cmd.Window);
var clipboard = GLFW.GetClipboardString((Window*) cmd.Window) ?? "";
// Don't have to care about synchronization I don't think so just fire this immediately.
cmd.Tcs.TrySetResult(clipboard);
}

View File

@@ -125,12 +125,6 @@ namespace Robust.Client.Graphics.Clyde
public GlfwException(string message, Exception inner) : base(message, inner)
{
}
protected GlfwException(
SerializationInfo info,
StreamingContext context) : base(info, context)
{
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using Robust.Client.Input;
@@ -16,7 +17,7 @@ internal partial class Clyde
{
// Indices are values of SDL_Scancode
private static readonly Key[] KeyMap;
private static readonly Dictionary<Key, SDL_Scancode> KeyMapReverse;
private static readonly FrozenDictionary<Key, SDL_Scancode> KeyMapReverse;
private static readonly Button[] MouseButtonMap;
// TODO: to avoid having to ask the windowing thread, key names are cached.
@@ -202,15 +203,17 @@ internal partial class Clyde
MapKey(SDL_SCANCODE_F24, Key.F24);
MapKey(SDL_SCANCODE_PAUSE, Key.Pause);
KeyMapReverse = new Dictionary<Key, SDL_Scancode>();
var keyMapReverse = new Dictionary<Key, SDL_Scancode>();
for (var code = 0; code < KeyMap.Length; code++)
{
var key = KeyMap[code];
if (key != Key.Unknown)
KeyMapReverse[key] = (SDL_Scancode) code;
keyMapReverse[key] = (SDL_Scancode) code;
}
KeyMapReverse = keyMapReverse.ToFrozenDictionary();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void MapKey(SDL_Scancode code, Key key)
{

View File

@@ -55,8 +55,12 @@ namespace Robust.Client.Graphics
public abstract void SetTransform(in Matrix3 matrix);
public abstract Matrix3 GetTransform();
public abstract void UseShader(ShaderInstance? shader);
public abstract ShaderInstance? GetShader();
// ---- DrawPrimitives: Vector2 API ----
/// <summary>

View File

@@ -15,7 +15,8 @@ namespace Robust.Client.Graphics
/// which can be either stretched or tiled to fill up
/// the space the box is being drawn in.
/// </summary>
public sealed class StyleBoxTexture : StyleBox
[Virtual]
public class StyleBoxTexture : StyleBox
{
public StyleBoxTexture()
{

View File

@@ -17,6 +17,7 @@ namespace Robust.Client.Graphics
/// The render target that is rendered to when rendering this viewport.
/// </summary>
IRenderTexture RenderTarget { get; }
IRenderTexture LightRenderTarget { get; }
IEye? Eye { get; set; }
Vector2i Size { get; }

View File

@@ -0,0 +1,14 @@
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
namespace Robust.Client.Graphics;
public abstract class GridOverlay : Overlay, IGridOverlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpaceGrids;
public Entity<MapGridComponent> Grid { get; set; }
public bool RequiresFlush { get; set; }
}

View File

@@ -0,0 +1,18 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
namespace Robust.Client.Graphics;
/// <summary>
/// Marks this overlay as implementing per-grid rendering.
/// </summary>
public interface IGridOverlay
{
Entity<MapGridComponent> Grid { get; set; }
/// <summary>
/// Should we flush the render or can we keep going.
/// </summary>
public bool RequiresFlush { get; set; }
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Utility;
@@ -227,7 +228,7 @@ namespace Robust.Client.Graphics
}
[SuppressMessage("ReSharper", "StringLiteralTypo")]
private static readonly Dictionary<ShaderDataType, string> _nativeTypes = new()
private static readonly FrozenDictionary<ShaderDataType, string> _nativeTypes = new Dictionary<ShaderDataType, string>()
{
{ShaderDataType.Void, "void"},
{ShaderDataType.Bool, "bool"},
@@ -252,7 +253,7 @@ namespace Robust.Client.Graphics
{ShaderDataType.Sampler2D, "sampler2D"},
{ShaderDataType.ISampler2D, "isampler2D"},
{ShaderDataType.USampler2D, "usampler2D"},
};
}.ToFrozenDictionary();
}
internal enum ShaderLightMode : byte

View File

@@ -1,3 +1,4 @@
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -637,7 +638,7 @@ namespace Robust.Client.Graphics
Colon,
}
private static readonly Dictionary<Symbols, string> _symbolStringMap = new()
private static readonly FrozenDictionary<Symbols, string> _symbolStringMap = new Dictionary<Symbols, string>()
{
{Symbols.Semicolon, ";\n"},
{Symbols.Comma, ","},
@@ -679,6 +680,6 @@ namespace Robust.Client.Graphics
{Symbols.GreaterOrEq, ">="},
{Symbols.QuestionMark, "?"},
{Symbols.Colon, ":"},
};
}.ToFrozenDictionary();
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
@@ -536,17 +537,17 @@ namespace Robust.Client.Graphics
}
[SuppressMessage("ReSharper", "StringLiteralTypo")]
private static readonly Dictionary<string, ShaderPrecisionQualifier> _shaderTypePrecisionMap =
new()
private static readonly FrozenDictionary<string, ShaderPrecisionQualifier> _shaderTypePrecisionMap =
new Dictionary<string, ShaderPrecisionQualifier>()
{
{"lowp", ShaderPrecisionQualifier.Low},
{"mediump", ShaderPrecisionQualifier.Medium},
{"highp", ShaderPrecisionQualifier.High}
};
}.ToFrozenDictionary();
[SuppressMessage("ReSharper", "StringLiteralTypo")]
private static readonly Dictionary<string, ShaderDataType> _shaderTypeMap =
new()
private static readonly FrozenDictionary<string, ShaderDataType> _shaderTypeMap =
new Dictionary<string, ShaderDataType>()
{
{"void", ShaderDataType.Void},
{"bool", ShaderDataType.Bool},
@@ -571,7 +572,7 @@ namespace Robust.Client.Graphics
{"sampler2D", ShaderDataType.Sampler2D},
{"isampler2D", ShaderDataType.ISampler2D},
{"usampler2D", ShaderDataType.USampler2D},
};
}.ToFrozenDictionary();
[PublicAPI]
internal readonly struct TextFileRange

View File

@@ -59,6 +59,11 @@ namespace Robust.Client.Graphics
/// <summary>
/// Hide title buttons such as close and minimize.
/// </summary>
NoTitleOptions = 1 << 0
NoTitleOptions = 1 << 0,
/// <summary>
/// Completely hide the title bar
/// </summary>
NoTitleBar = 1 << 1,
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using Robust.Shared.Localization;
@@ -28,7 +29,7 @@ namespace Robust.Client.Input
return _mouseKeyMap[button];
}
private static readonly Dictionary<Button, Keyboard.Key> _mouseKeyMap = new()
private static readonly FrozenDictionary<Button, Keyboard.Key> _mouseKeyMap = new Dictionary<Button, Keyboard.Key>()
{
{Button.Left, Keyboard.Key.MouseLeft},
{Button.Middle, Keyboard.Key.MouseMiddle},
@@ -40,7 +41,7 @@ namespace Robust.Client.Input
{Button.Button8, Keyboard.Key.MouseButton8},
{Button.Button9, Keyboard.Key.MouseButton9},
{Button.LastButton, Keyboard.Key.Unknown},
};
}.ToFrozenDictionary();
}

View File

@@ -1,12 +1,9 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
namespace Robust.Client.Map;
@@ -14,21 +11,15 @@ namespace Robust.Client.Map;
/// <summary>
/// Draws border sprites for tiles that support them.
/// </summary>
public sealed class TileEdgeOverlay : Overlay
public sealed class TileEdgeOverlay : GridOverlay
{
private readonly IEntityManager _entManager;
private readonly IMapManager _mapManager;
private readonly IResourceCache _resource;
private readonly ITileDefinitionManager _tileDefManager;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities;
private List<Entity<MapGridComponent>> _grids = new();
public TileEdgeOverlay(IEntityManager entManager, IMapManager mapManager, IResourceCache resource, ITileDefinitionManager tileDefManager)
public TileEdgeOverlay(IEntityManager entManager, IResourceCache resource, ITileDefinitionManager tileDefManager)
{
_entManager = entManager;
_mapManager = mapManager;
_resource = resource;
_tileDefManager = tileDefManager;
ZIndex = -1;
@@ -39,94 +30,90 @@ public sealed class TileEdgeOverlay : Overlay
if (args.MapId == MapId.Nullspace)
return;
_grids.Clear();
_mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds, ref _grids);
var mapSystem = _entManager.System<SharedMapSystem>();
var xformSystem = _entManager.System<SharedTransformSystem>();
foreach (var grid in _grids)
var tileSize = Grid.Comp.TileSize;
var tileDimensions = new Vector2(tileSize, tileSize);
var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(Grid.Owner);
args.WorldHandle.SetTransform(worldMatrix);
var localAABB = invMatrix.TransformBox(args.WorldBounds);
var enumerator = mapSystem.GetLocalTilesEnumerator(Grid.Owner, Grid, localAABB, false);
while (enumerator.MoveNext(out var tileRef))
{
var tileSize = grid.Comp.TileSize;
var tileDimensions = new Vector2(tileSize, tileSize);
var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(grid.Owner);
args.WorldHandle.SetTransform(worldMatrix);
var localAABB = invMatrix.TransformBox(args.WorldBounds);
var tileDef = _tileDefManager[tileRef.Tile.TypeId];
var enumerator = mapSystem.GetLocalTilesEnumerator(grid.Owner, grid.Comp, localAABB, false);
if (tileDef.EdgeSprites.Count == 0)
continue;
while (enumerator.MoveNext(out var tileRef))
// Get what tiles border us to determine what sprites we need to draw.
for (var x = -1; x <= 1; x++)
{
var tileDef = _tileDefManager[tileRef.Tile.TypeId];
if (tileDef.EdgeSprites.Count == 0)
continue;
// Get what tiles border us to determine what sprites we need to draw.
for (var x = -1; x <= 1; x++)
for (var y = -1; y <= 1; y++)
{
for (var y = -1; y <= 1; y++)
if (x == 0 && y == 0)
continue;
var neighborIndices = new Vector2i(tileRef.GridIndices.X + x, tileRef.GridIndices.Y + y);
var neighborTile = mapSystem.GetTileRef(Grid.Owner, Grid, neighborIndices);
var neighborDef = _tileDefManager[neighborTile.Tile.TypeId];
// If it's the same tile then no edge to be drawn.
if (tileRef.Tile.TypeId == neighborTile.Tile.TypeId)
continue;
// Don't draw if the the neighbor tile edges should draw over us (or if we have the same priority)
if (neighborDef.EdgeSprites.Count != 0 && neighborDef.EdgeSpritePriority >= tileDef.EdgeSpritePriority)
continue;
var direction = new Vector2i(x, y).AsDirection();
// No edge tile
if (!tileDef.EdgeSprites.TryGetValue(direction, out var edgePath))
continue;
var texture = _resource.GetResource<TextureResource>(edgePath);
var box = Box2.FromDimensions(neighborIndices, tileDimensions);
var angle = Angle.Zero;
// If we ever need one for both cardinals and corners then update this.
switch (direction)
{
if (x == 0 && y == 0)
continue;
var neighborIndices = new Vector2i(tileRef.GridIndices.X + x, tileRef.GridIndices.Y + y);
var neighborTile = mapSystem.GetTileRef(grid.Owner, grid.Comp, neighborIndices);
var neighborDef = _tileDefManager[neighborTile.Tile.TypeId];
// If it's the same tile then no edge to be drawn.
if (tileRef.Tile.TypeId == neighborTile.Tile.TypeId)
continue;
// Don't draw if the the neighbor tile edges should draw over us (or if we have the same priority)
if (neighborDef.EdgeSprites.Count != 0 && neighborDef.EdgeSpritePriority >= tileDef.EdgeSpritePriority)
continue;
var direction = new Vector2i(x, y).AsDirection();
// No edge tile
if (!tileDef.EdgeSprites.TryGetValue(direction, out var edgePath))
continue;
var texture = _resource.GetResource<TextureResource>(edgePath);
var box = Box2.FromDimensions(neighborIndices, tileDimensions);
var angle = Angle.Zero;
// If we ever need one for both cardinals and corners then update this.
switch (direction)
{
// Corner sprites
case Direction.SouthEast:
break;
case Direction.NorthEast:
angle = new Angle(MathF.PI / 2f);
break;
case Direction.NorthWest:
angle = new Angle(MathF.PI);
break;
case Direction.SouthWest:
angle = new Angle(MathF.PI * 1.5f);
break;
// Edge sprites
case Direction.South:
break;
case Direction.East:
angle = new Angle(MathF.PI / 2f);
break;
case Direction.North:
angle = new Angle(MathF.PI);
break;
case Direction.West:
angle = new Angle(MathF.PI * 1.5f);
break;
}
if (angle == Angle.Zero)
args.WorldHandle.DrawTextureRect(texture.Texture, box);
else
args.WorldHandle.DrawTextureRect(texture.Texture, new Box2Rotated(box, angle, box.Center));
// Corner sprites
case Direction.SouthEast:
break;
case Direction.NorthEast:
angle = new Angle(MathF.PI / 2f);
break;
case Direction.NorthWest:
angle = new Angle(MathF.PI);
break;
case Direction.SouthWest:
angle = new Angle(MathF.PI * 1.5f);
break;
// Edge sprites
case Direction.South:
break;
case Direction.East:
angle = new Angle(MathF.PI / 2f);
break;
case Direction.North:
angle = new Angle(MathF.PI);
break;
case Direction.West:
angle = new Angle(MathF.PI * 1.5f);
break;
}
if (angle == Angle.Zero)
args.WorldHandle.DrawTextureRect(texture.Texture, box);
else
args.WorldHandle.DrawTextureRect(texture.Texture, new Box2Rotated(box, angle, box.Center));
RequiresFlush = true;
}
}
}

View File

@@ -99,8 +99,8 @@ public sealed partial class PhysicsSystem
if (xform.MapUid is not { } map)
continue;
if (maps.Add(map) && TryComp(map, out PhysicsMapComponent? physMap) &&
TryComp(map, out MapComponent? mapComp))
if (maps.Add(map) && PhysMapQuery.TryGetComponent(map, out var physMap) &&
MapQuery.TryGetComponent(map, out var mapComp))
_broadphase.FindNewContacts(physMap, mapComp.MapId);
contacts.AddRange(physics.Contacts);

View File

@@ -44,7 +44,7 @@ namespace Robust.Client.Placement.Modes
var closestEntity = snapToEntities[0];
var closestTransform = pManager.EntityManager.GetComponent<TransformComponent>(closestEntity);
if (!pManager.EntityManager.TryGetComponent<SpriteComponent?>(closestEntity, out var component) || component.BaseRSI == null)
if (!pManager.EntityManager.TryGetComponent(closestEntity, out SpriteComponent? component) || component.BaseRSI == null)
{
return;
}

View File

@@ -162,11 +162,14 @@ namespace Robust.Client.Player
return true;
}
if (!EntManager.EnsureComponent(uid.Value, out EyeComponent eye))
if (!EntManager.HasComponent<EyeComponent>(uid.Value))
{
if (_client.RunLevel != ClientRunLevel.SinglePlayerGame)
Sawmill.Warning($"Attaching local player to an entity {EntManager.ToPrettyString(uid)} without an eye. This eye will not be netsynced and may cause issues.");
var eye = (EyeComponent) Factory.GetComponent(typeof(EyeComponent));
eye.Owner = uid.Value;
eye.NetSyncEnabled = false;
EntManager.AddComponent(uid.Value, eye);
}
Sawmill.Info($"Attaching local player to {EntManager.ToPrettyString(uid)}.");

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Client.Audio;
using Robust.Client.Graphics;
using Robust.Shared.ContentPack;
using Robust.Shared.Utility;
@@ -24,6 +25,8 @@ public interface IResourceCache : IResourceManager
bool TryGetResource<T>(ResPath path, [NotNullWhen(true)] out T? resource)
where T : BaseResource, new();
bool TryGetResource(AudioStream stream, [NotNullWhen(true)] out AudioResource? resource);
void ReloadResource<T>(string path)
where T : BaseResource, new();

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using Robust.Client.Audio;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -45,13 +47,13 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
{
if (useFallback && resource.Fallback != null)
{
Logger.Error(
Sawmill.Error(
$"Exception while loading resource {typeof(T)} at '{path}', resorting to fallback.\n{Environment.StackTrace}\n{e}");
return GetResource<T>(resource.Fallback.Value, false);
}
else
{
Logger.Error(
Sawmill.Error(
$"Exception while loading resource {typeof(T)} at '{path}', no fallback available\n{Environment.StackTrace}\n{e}");
throw;
}
@@ -81,11 +83,23 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
cache[path] = resource;
return true;
}
catch
catch (FileNotFoundException)
{
resource = null;
return false;
}
catch (Exception e)
{
Sawmill.Error($"Exception while loading resource {typeof(T)} at '{path}'\n{e}");
resource = null;
return false;
}
}
public bool TryGetResource(AudioStream stream, [NotNullWhen(true)] out AudioResource? resource)
{
resource = new AudioResource(stream);
return true;
}
public void ReloadResource<T>(string path) where T : BaseResource, new()
@@ -109,7 +123,7 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
}
catch (Exception e)
{
Logger.Error($"Exception while reloading resource {typeof(T)} at '{path}'\n{e}");
Sawmill.Error($"Exception while reloading resource {typeof(T)} at '{path}'\n{e}");
throw;
}
}

View File

@@ -12,6 +12,11 @@ public sealed class AudioResource : BaseResource
{
public AudioStream AudioStream { get; private set; } = default!;
public void Load(AudioStream stream)
{
AudioStream = stream;
}
public override void Load(IDependencyCollection dependencies, ResPath path)
{
var cache = dependencies.Resolve<IResourceManager>();
@@ -39,6 +44,13 @@ public sealed class AudioResource : BaseResource
}
}
public AudioResource(AudioStream stream) : base()
{
AudioStream = stream;
}
public AudioResource() : base(){}
public static implicit operator AudioStream(AudioResource res)
{
return res.AudioStream;

View File

@@ -16,7 +16,7 @@
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' == 'True'" PrivateAssets="compile" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' != 'True'" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.NFluidsynth" Version="0.1.1" PrivateAssets="compile" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.9" />
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" PrivateAssets="compile" />
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" PrivateAssets="compile" />
@@ -25,6 +25,8 @@
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" PrivateAssets="compile" />
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" Version="1.2.0" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0" />
<PackageReference Include="TerraFX.Interop.Xlib" Version="6.4.0" />
</ItemGroup>
<ItemGroup Condition="'$(EnableClientScripting)' == 'True'">
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" PrivateAssets="compile" />
@@ -53,6 +55,7 @@
<RobustLinkRoots Include="Robust.Client" />
<RobustLinkRoots Include="Robust.Shared" />
<RobustLinkAssemblies Include="TerraFX.Interop.Windows" />
<RobustLinkAssemblies Include="TerraFX.Interop.Xlib" />
<RobustLinkAssemblies Include="OpenToolkit.Graphics" />
</ItemGroup>

View File

@@ -24,7 +24,10 @@ namespace Robust.Client.UserInterface
}
}
[ViewVariables]
public ICollection<string> StyleClasses { get; }
[ViewVariables(VVAccess.ReadOnly)]
public IReadOnlyCollection<string> StylePseudoClass => _stylePseudoClass;
[ViewVariables]

View File

@@ -820,8 +820,18 @@ namespace Robust.Client.UserInterface
return;
}
// If it was at the top index and we re-add it there then don't throw.
Parent._orderedChildren.RemoveAt(posInParent);
Parent._orderedChildren.Insert(position, this);
if (position == Parent._orderedChildren.Count)
{
Parent._orderedChildren.Add(this);
}
else
{
Parent._orderedChildren.Insert(position, this);
}
Parent.ChildMoved(this, posInParent, position);
}

View File

@@ -1,6 +1,11 @@
using System;
using System.Collections.Generic;
using Robust.Client.Audio;
using Robust.Client.ResourceManagement;
using Robust.Shared.ContentPack;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface.Controls
@@ -106,9 +111,24 @@ namespace Robust.Client.UserInterface.Controls
}
}
/// <summary>
/// Sets the button's press state and also handles click sounds.
/// </summary>
/// <returns></returns>
public void SetClickPressed(bool value)
{
Pressed = value;
if (Pressed != value)
return;
UserInterfaceManager.ClickSound();
}
/// <summary>
/// Whether key functions other than <see cref="EngineKeyFunctions.UIClick"/> trigger the button.
/// </summary>
[ViewVariables]
public bool EnableAllKeybinds
{
get => _enableAllKeybinds;
@@ -220,7 +240,7 @@ namespace Robust.Client.UserInterface.Controls
// Can't un press a radio button directly.
if (Group == null || !Pressed)
{
Pressed = !Pressed;
SetClickPressed(!Pressed);
OnPressed?.Invoke(buttonEventArgs);
OnToggled?.Invoke(new ButtonToggledEventArgs(Pressed, this, args));
UnsetOtherGroupButtons();
@@ -261,7 +281,11 @@ namespace Robust.Client.UserInterface.Controls
{
if (args.Function == EngineKeyFunctions.UIClick && ToggleMode && _attemptingPress == 1)
{
Pressed = !Pressed;
SetClickPressed(!Pressed);
}
else
{
UserInterfaceManager.ClickSound();
}
OnPressed?.Invoke(buttonEventArgs);
@@ -316,6 +340,11 @@ namespace Robust.Client.UserInterface.Controls
{
base.MouseEntered();
if (!Disabled)
{
UserInterfaceManager.HoverSound();
}
var drawMode = DrawMode;
_beingHovered = true;
if (drawMode != DrawMode)

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Log;
namespace Robust.Client.UserInterface.Controls;
@@ -15,6 +17,15 @@ public sealed class ColorSelectorSliders : Control
set
{
_currentColor = value;
switch (SelectorType)
{
case ColorSelectorType.Rgb:
_colorData = new Vector4(_currentColor.R, _currentColor.G, _currentColor.B, _currentColor.A);
break;
case ColorSelectorType.Hsv:
_colorData = Color.ToHsv(value);
break;
}
Update();
}
}
@@ -24,6 +35,15 @@ public sealed class ColorSelectorSliders : Control
get => _currentType;
set
{
switch ((_currentType, value))
{
case (ColorSelectorType.Rgb, ColorSelectorType.Hsv):
_colorData = Color.ToHsv(Color);
break;
case (ColorSelectorType.Hsv, ColorSelectorType.Rgb):
_colorData = new Vector4(_currentColor.R, _currentColor.G, _currentColor.B, _currentColor.A);
break;
}
_currentType = value;
UpdateType();
Update();
@@ -45,6 +65,7 @@ public sealed class ColorSelectorSliders : Control
private bool _updating = false;
private Color _currentColor = Color.White;
private Vector4 _colorData;
private ColorSelectorType _currentType = ColorSelectorType.Rgb;
private bool _isAlphaVisible = false;
@@ -68,12 +89,19 @@ public sealed class ColorSelectorSliders : Control
private OptionButton _typeSelector;
private List<ColorSelectorType> _types = new();
private static ShaderInstance _shader = default!;
private ColorSelectorStyleBox _topStyle;
private ColorSelectorStyleBox _middleStyle;
private ColorSelectorStyleBox _bottomStyle;
public ColorSelectorSliders()
{
_topColorSlider = new ColorableSlider
{
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
BackgroundStyleBoxOverride = _topStyle = new(),
MaxValue = 1.0f
};
@@ -81,6 +109,7 @@ public sealed class ColorSelectorSliders : Control
{
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
BackgroundStyleBoxOverride = _middleStyle = new(),
MaxValue = 1.0f
};
@@ -88,6 +117,7 @@ public sealed class ColorSelectorSliders : Control
{
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
BackgroundStyleBoxOverride = _bottomStyle = new(),
MaxValue = 1.0f
};
@@ -212,7 +242,7 @@ public sealed class ColorSelectorSliders : Control
rootBox.AddChild(bodyBox);
UpdateType();
Update();
Color = _currentColor;
}
private void UpdateType()
@@ -222,6 +252,11 @@ public sealed class ColorSelectorSliders : Control
_topSliderLabel.Text = labels.topLabel;
_middleSliderLabel.Text = labels.middleLabel;
_bottomSliderLabel.Text = labels.bottomLabel;
bool hsv = SelectorType == ColorSelectorType.Hsv;
_topStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Hue : ColorSelectorStyleBox.ColorSliderPreset.Red);
_middleStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Saturation : ColorSelectorStyleBox.ColorSliderPreset.Green);
_bottomStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Value : ColorSelectorStyleBox.ColorSliderPreset.Blue);
}
private void Update()
@@ -232,43 +267,41 @@ public sealed class ColorSelectorSliders : Control
return;
_updating = true;
_topColorSlider.SetColor(_currentColor);
_middleColorSlider.SetColor(_currentColor);
_bottomColorSlider.SetColor(_currentColor);
_topStyle.SetBaseColor(_colorData);
_middleStyle.SetBaseColor(_colorData);
_bottomStyle.SetBaseColor(_colorData);
switch (SelectorType)
{
case ColorSelectorType.Rgb:
_topColorSlider.Value = Color.R;
_middleColorSlider.Value = Color.G;
_bottomColorSlider.Value = Color.B;
_topColorSlider.Value = _colorData.X;
_middleColorSlider.Value = _colorData.Y;
_bottomColorSlider.Value = _colorData.Z;
_topInputBox.Value = (int)(Color.R * 255.0f);
_middleInputBox.Value = (int)(Color.G * 255.0f);
_bottomInputBox.Value = (int)(Color.B * 255.0f);
_topInputBox.Value = (int)(_colorData.X * 255.0f);
_middleInputBox.Value = (int)(_colorData.Y * 255.0f);
_bottomInputBox.Value = (int)(_colorData.Z * 255.0f);
break;
case ColorSelectorType.Hsv:
Vector4 color = Color.ToHsv(Color);
// dumb workaround because the formula for
// HSV calculation results in a negative
// number in any value past 300 degrees
if (color.X > 0)
if (_colorData.X > 0)
{
_topColorSlider.Value = color.X;
_topInputBox.Value = (int)(color.X * 360.0f);
_topColorSlider.Value = _colorData.X;
_topInputBox.Value = (int)(_colorData.X * 360.0f);
}
else
{
_topInputBox.Value = (int)(_topColorSlider.Value * 360.0f);
}
_middleColorSlider.Value = color.Y;
_bottomColorSlider.Value = color.Z;
_middleColorSlider.Value = _colorData.Y;
_bottomColorSlider.Value = _colorData.Z;
_middleInputBox.Value = (int)(color.Y * 100.0f);
_bottomInputBox.Value = (int)(color.Z * 100.0f);
_middleInputBox.Value = (int)(_colorData.Y * 100.0f);
_bottomInputBox.Value = (int)(_colorData.Z * 100.0f);
break;
@@ -361,25 +394,16 @@ public sealed class ColorSelectorSliders : Control
return;
}
switch (SelectorType)
_colorData = new Vector4(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value);
_currentColor = SelectorType switch
{
case ColorSelectorType.Rgb:
Color rgbColor = new Color(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value);
ColorSelectorType.Hsv => Color.FromHsv(_colorData),
_ => new Color(_colorData.X, _colorData.Y, _colorData.Z, _colorData.W)
};
_currentColor = rgbColor;
Update();
OnColorChanged!(rgbColor);
break;
case ColorSelectorType.Hsv:
Color hsvColor = Color.FromHsv(new Vector4(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value));
_currentColor = hsvColor;
Update();
OnColorChanged!(hsvColor);
break;
}
Update();
OnColorChanged?.Invoke(_currentColor);
}
private enum ColorSliderOrder

View File

@@ -0,0 +1,130 @@
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
namespace Robust.Client.UserInterface.Controls;
/// <summary>
/// Style box for colouring sliders and 2-d colour selectors. E.g., this could be used to draw the typical HSV colour
/// selection rainbow.
/// </summary>
public sealed class ColorSelectorStyleBox : StyleBoxTexture
{
public const string TexturePath = "/Textures/Interface/Nano/slider_fill.svg.96dpi.png";
public static ProtoId<ShaderPrototype> Prototype = "ColorPicker";
private ShaderInstance _shader;
/// <summary>
/// Base background colour.
/// </summary>
public Vector4 BaseColor;
/// <summary>
/// Colour to add to the background colour along the X-axis.
/// I.e., from left to right the background colour will vary from (BaseColour) to (BaseColour + XAxis)
/// </summary>
public Vector4 XAxis;
/// <summary>
/// Colour to add to the background colour along the y-axis.
/// I.e., from left to right the background colour will vary from (BaseColour) to (BaseColour + XAxis)
/// </summary>
public Vector4 YAxis;
/// <summary>
/// If true, then <see cref="BaseColor"/>, <see cref="XAxis"/>, and <see cref="YAxis"/> will be interpreted as HSVa
/// colours.
/// </summary>
public bool Hsv;
public ColorSelectorStyleBox(ColorSliderPreset preset = ColorSliderPreset.Red)
{
Texture = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(TexturePath);
_shader = IoCManager.Resolve<IPrototypeManager>().Index(Prototype).InstanceUnique();
SetPatchMargin(Margin.All, 12);
ConfigureSlider(preset);
}
protected override void DoDraw(DrawingHandleScreen handle, UIBox2 box, float uiScale)
{
var old = handle.GetShader();
handle.UseShader(_shader);
var globalPixelPos = handle.GetTransform().Transform(default);
_shader.SetParameter("size", box.Size);
_shader.SetParameter("offset", globalPixelPos);
_shader.SetParameter("xAxis", XAxis);
_shader.SetParameter("yAxis", YAxis);
_shader.SetParameter("baseColor", BaseColor);
_shader.SetParameter("hsv", Hsv);
base.DoDraw(handle, box, uiScale);
handle.UseShader(old);
}
public void ConfigureSlider(ColorSliderPreset preset)
{
Hsv = preset > ColorSliderPreset.Blue;
if (preset == ColorSliderPreset.HueValue)
{
XAxis = new(1, 0, 0, 0); // Hue;
YAxis = new(0, 0, 1, 0); // value;
return;
}
YAxis = default;
XAxis = preset switch
{
ColorSliderPreset.Red or ColorSliderPreset.Hue => new(1, 0, 0, 0),
ColorSliderPreset.Green or ColorSliderPreset.Saturation => new(0, 1, 0, 0),
_ => new(0, 0, 1, 0),
};
}
/// <summary>
/// Helper method that sets the base color by taking in some color and removing the components that are controlled by the x and y axes.
/// </summary>
public void SetBaseColor(Color color)
{
var colorData = Hsv
? Color.ToHsv(color)
: new Vector4(color.R, color.G, color.B, color.A);
SetBaseColor(colorData);
}
/// <summary>
/// Helper method that sets the base color by taking in some color and removing the components that are controlled by the x and y axes.
/// </summary>
public void SetBaseColor(Vector4 colorData)
{
BaseColor = colorData - colorData * XAxis - colorData * YAxis;
}
public enum ColorSliderPreset : byte
{
// Horizontal red slider
Red = 1,
// Horizontal green slider
Green = 2,
// Horizontal blue slider
Blue = 3,
// Horizontal hue slider
Hue = 4,
// Horizontal situation slider
Saturation = 5,
// Horizontal saturation slider
Value = 6,
// 2-D hue-value box
HueValue = 7
}
}

View File

@@ -14,6 +14,7 @@ namespace Robust.Client.UserInterface.Controls
[Virtual]
public class MenuBar : PanelContainer
{
public const string StyleClassMenuBarPopup = "menuBarPopup";
private readonly List<Menu> _menus = new();
private readonly List<MenuTopButton> _buttons = new();
private readonly BoxContainer _hBox;
@@ -40,7 +41,8 @@ namespace Robust.Client.UserInterface.Controls
Orientation = LayoutOrientation.Vertical,
MinSize = new Vector2(300, 0)
})
}
},
StyleClasses = { StyleClassMenuBarPopup }
};
_popup.OnPopupHide += PopupHidden;
UserInterfaceManager.ModalRoot.AddChild(_popup);

View File

@@ -1,4 +1,5 @@
using System.Numerics;
using System;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Maths;
@@ -14,6 +15,9 @@ namespace Robust.Client.UserInterface.Controls
public const string StylePropertyFill = "fill";
public const string StylePropertyGrabber = "grabber";
public event Action<Slider>? OnGrabbed;
public event Action<Slider>? OnReleased;
protected readonly PanelContainer _foregroundPanel;
protected readonly PanelContainer _backgroundPanel;
protected readonly PanelContainer _fillPanel;
@@ -135,6 +139,7 @@ namespace Robust.Client.UserInterface.Controls
HandlePositionChange(args.RelativePosition);
_grabbed = true;
OnGrabbed?.Invoke(this);
}
protected internal override void KeyBindUp(GUIBoundKeyEventArgs args)
@@ -144,6 +149,7 @@ namespace Robust.Client.UserInterface.Controls
if (args.Function != EngineKeyFunctions.UIClick) return;
_grabbed = false;
OnReleased?.Invoke(this);
}
protected internal override void MouseMove(GUIMouseMoveEventArgs args)

View File

@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -23,6 +24,9 @@ namespace Robust.Client.UserInterface.Controls
[ViewVariables]
public Entity<SpriteComponent, TransformComponent>? Entity { get; private set; }
[ViewVariables]
public NetEntity? NetEnt { get; private set; }
/// <summary>
/// This field configures automatic scaling of the sprite. This automatic scaling is done before
/// applying the explicitly set scale <see cref="SpriteView.Scale"/>.
@@ -120,13 +124,29 @@ namespace Robust.Client.UserInterface.Controls
RectClipContent = true;
}
public SpriteView(EntityUid uid, IEntityManager entMan)
public SpriteView(EntityUid? uid, IEntityManager entMan)
{
_entMan = entMan;
RectClipContent = true;
SetEntity(uid);
}
public SpriteView(NetEntity uid, IEntityManager entMan)
{
_entMan = entMan;
RectClipContent = true;
SetEntity(uid);
}
public void SetEntity(NetEntity netEnt)
{
if (netEnt == NetEnt)
return;
Entity = null;
NetEnt = netEnt;
}
public void SetEntity(EntityUid? uid)
{
if (Entity?.Owner == uid)
@@ -136,10 +156,12 @@ namespace Robust.Client.UserInterface.Controls
|| !_entMan.TryGetComponent(uid, out TransformComponent? xform))
{
Entity = null;
NetEnt = null;
return;
}
Entity = new(uid.Value, sprite, xform);
NetEnt = _entMan.GetNetEntity(uid);
}
protected override Vector2 MeasureOverride(Vector2 availableSize)
@@ -151,13 +173,10 @@ namespace Robust.Client.UserInterface.Controls
private void UpdateSize()
{
if (Entity is not { } ent)
{
_spriteSize = default;
if (!ResolveEntity(out _, out var sprite, out _))
return;
}
var spriteBox = ent.Comp1.CalculateRotatedBoundingBox(default, _worldRotation ?? Angle.Zero, _eyeRotation)
var spriteBox = sprite.CalculateRotatedBoundingBox(default, _worldRotation ?? Angle.Zero, _eyeRotation)
.CalcBoundingBox();
if (!SpriteOffset)
@@ -199,17 +218,9 @@ namespace Robust.Client.UserInterface.Controls
internal override void DrawInternal(IRenderHandle renderHandle)
{
if (Entity == null)
if (!ResolveEntity(out var uid, out var sprite, out var xform))
return;
var (uid, sprite, xform) = Entity.Value;
if (sprite.Deleted)
{
SetEntity(null);
return;
}
_sprite ??= _entMan.System<SpriteSystem>();
_transform ??= _entMan.System<TransformSystem>();
@@ -239,5 +250,34 @@ namespace Robust.Client.UserInterface.Controls
renderHandle.DrawEntity(uid, position, scale, _worldRotation, _eyeRotation, OverrideDirection, sprite, xform, _transform);
world.Modulate = oldModulate;
}
private bool ResolveEntity(
out EntityUid uid,
[NotNullWhen(true)] out SpriteComponent? sprite,
[NotNullWhen(true)] out TransformComponent? xform)
{
if (Entity != null)
{
(uid, sprite, xform) = Entity.Value;
return true;
}
sprite = null;
xform = null;
uid = default;
if (NetEnt == null)
return false;
if (!_entMan.TryGetEntity(NetEnt, out var ent))
return false;
SetEntity(ent);
if (Entity == null)
return false;
(uid, sprite, xform) = Entity.Value;
return true;
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Numerics;
@@ -89,6 +90,8 @@ public sealed class TextEdit : Control
internal bool DebugOverlay;
private Vector2? _lastDebugMousePos;
public event Action<TextEditEventArgs>? OnTextChanged;
public TextEdit()
{
IoCManager.InjectDependencies(this);
@@ -228,7 +231,7 @@ public sealed class TextEdit : Control
private bool IsPlaceholderVisible => Rope.IsNullOrEmpty(_textRope) && _placeholder != null;
// Table used by cursor movement system, see below.
private static readonly Dictionary<BoundKeyFunction, MoveType> MoveTypeMap = new()
private static readonly FrozenDictionary<BoundKeyFunction, MoveType> MoveTypeMap = new Dictionary<BoundKeyFunction, MoveType>()
{
// @formatter:off
{ EngineKeyFunctions.TextCursorLeft, MoveType.Left },
@@ -249,7 +252,7 @@ public sealed class TextEdit : Control
{ EngineKeyFunctions.TextCursorSelectBegin, MoveType.BeginOfLine | MoveType.SelectFlag },
{ EngineKeyFunctions.TextCursorSelectEnd, MoveType.EndOfLine | MoveType.SelectFlag },
// @formatter:on
};
}.ToFrozenDictionary();
protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
{
@@ -314,7 +317,7 @@ public sealed class TextEdit : Control
if (changed)
{
_selectionStart = _cursorPosition;
// OnTextChanged?.Invoke(new LineEditEventArgs(this, _text));
OnTextChanged?.Invoke(new TextEditEventArgs(this, _textRope));
// _updatePseudoClass();
// OnBackspace?.Invoke(new LineEditBackspaceEventArgs(oldText, _text, cursor, selectStart));
}
@@ -348,7 +351,7 @@ public sealed class TextEdit : Control
if (changed)
{
_selectionStart = _cursorPosition;
// OnTextChanged?.Invoke(new LineEditEventArgs(this, _text));
OnTextChanged?.Invoke(new TextEditEventArgs(this, _textRope));
// _updatePseudoClass();
}
@@ -381,7 +384,10 @@ public sealed class TextEdit : Control
}
if (changed)
{
_selectionStart = _cursorPosition;
OnTextChanged?.Invoke(new TextEditEventArgs(this, _textRope));
}
InvalidateHorizontalCursorPos();
args.Handle();
@@ -410,7 +416,10 @@ public sealed class TextEdit : Control
}
if (changed)
{
_selectionStart = _cursorPosition;
OnTextChanged?.Invoke(new TextEditEventArgs(this, _textRope));
}
InvalidateHorizontalCursorPos();
args.Handle();
@@ -747,6 +756,7 @@ public sealed class TextEdit : Control
var startPos = _cursorPosition;
TextRope = Rope.Insert(TextRope, startPos.Index, ev.Text);
OnTextChanged?.Invoke(new TextEditEventArgs(this, _textRope));
_selectionStart = _cursorPosition = new CursorPos(startPos.Index + startChars, LineBreakBias.Top);
_imeData = (startPos, ev.Text.Length);
@@ -843,6 +853,7 @@ public sealed class TextEdit : Control
var upper = SelectionUpper.Index;
TextRope = Rope.ReplaceSubstring(TextRope, lower, upper - lower, text);
OnTextChanged?.Invoke(new TextEditEventArgs(this, _textRope));
_selectionStart = _cursorPosition = new CursorPos(lower + text.Length, LineBreakBias.Top);
// OnTextChanged?.Invoke(new LineEditEventArgs(this, _text));
@@ -1440,6 +1451,12 @@ public sealed class TextEdit : Control
AbortIme(delete: false);
}
public sealed class TextEditEventArgs(TextEdit control, Rope.Node textRope) : EventArgs
{
public TextEdit Control { get; } = control;
public Rope.Node TextRope { get; } = textRope;
}
/// <summary>
/// Specifies which line the cursor is positioned at when on a word-wrapping break.
/// </summary>

View File

@@ -1,4 +1,5 @@
using Robust.Client.Graphics;
using Robust.Shared;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
@@ -13,5 +14,17 @@ namespace Robust.Client.UserInterface.Controls
public override float UIScale => UIScaleSet;
internal float UIScaleSet { get; set; }
public override IClydeWindow Window { get; }
/// <summary>
/// Disable automatic scaling of window <see cref="UIScale"/> based on resolution.
/// </summary>
/// <remarks>
/// <para>
/// Disabled by default for non-main windows as those most likely are smaller popup windows,
/// that won't make sense with the default parameters.
/// </para>
/// </remarks>
/// <seealso cref="CVars.ResAutoScaleEnabled"/>
public bool DisableAutoScaling { get; set; } = true;
}
}

View File

@@ -39,7 +39,7 @@ namespace Robust.Client.UserInterface.CustomControls
// And also if Update() stops firing due to an exception loop the console will still work.
// (At least from the main thread, which is what's throwing the exceptions..)
[GenerateTypedNameReferences]
public sealed partial class DebugConsole : Control, IDebugConsoleView, IPostInjectInit
public sealed partial class DebugConsole : Control, IDebugConsoleView
{
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IResourceManager _resourceManager = default!;
@@ -49,7 +49,7 @@ namespace Robust.Client.UserInterface.CustomControls
private static readonly ResPath HistoryPath = new("/debug_console_history.json");
private readonly ConcurrentQueue<FormattedMessage> _messageQueue = new();
private ISawmill _logger = default!;
private readonly ISawmill _logger;
public DebugConsole()
{
@@ -57,6 +57,8 @@ namespace Robust.Client.UserInterface.CustomControls
IoCManager.InjectDependencies(this);
_logger = _logMan.GetSawmill("dbgconsole");
InitCompletions();
CommandBar.OnTextChanged += OnCommandChanged;
@@ -282,10 +284,5 @@ namespace Robust.Client.UserInterface.CustomControls
}
});
}
void IPostInjectInit.PostInject()
{
_logger = _logMan.GetSawmill("dbgconsole");
}
}
}

View File

@@ -1,15 +1,7 @@
using System;
using System.Collections.Generic;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
using Robust.Client.Graphics;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using X86Aes = System.Runtime.Intrinsics.X86.Aes;
using ArmAes = System.Runtime.Intrinsics.Arm.Aes;
namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls;
@@ -36,139 +28,6 @@ internal sealed class DebugSystemPanel : PanelContainer
HorizontalAlignment = HAlignment.Left;
var version = typeof(DebugSystemPanel).Assembly.GetName().Version;
contents.Text = @$"OS: {RuntimeInformation.OSDescription} {RuntimeInformation.OSArchitecture}
.NET Runtime: {RuntimeInformation.FrameworkDescription} {RuntimeInformation.RuntimeIdentifier}
Server GC: {GCSettings.IsServerGC}
Processor: {Environment.ProcessorCount}x {SystemInformation.GetProcessorModel()}
Architecture: {RuntimeInformation.ProcessArchitecture}
Robust Version: {version}
Compile Options: {GetCompileOptions()}
Intrinsics: {GetIntrinsics()}";
}
private static string GetCompileOptions()
{
var options = new List<string>();
#if DEVELOPMENT
options.Add("DEVELOPMENT");
#endif
#if FULL_RELEASE
options.Add("FULL_RELEASE");
#endif
#if TOOLS
options.Add("TOOLS");
#endif
#if DEBUG
options.Add("DEBUG");
#endif
#if RELEASE
options.Add("RELEASE");
#endif
#if EXCEPTION_TOLERANCE
options.Add("EXCEPTION_TOLERANCE");
#endif
#if CLIENT_SCRIPTING
options.Add("CLIENT_SCRIPTING");
#endif
#if USE_SYSTEM_SQLITE
options.Add("USE_SYSTEM_SQLITE");
#endif
return string.Join(';', options);
}
private static string GetIntrinsics()
{
var options = new List<string>();
// - No put the oof back, hello?
// x86
if (X86Aes.IsSupported)
options.Add(nameof(X86Aes));
if (Avx.IsSupported)
options.Add(nameof(Avx));
if (Avx2.IsSupported)
options.Add(nameof(Avx2));
if (Bmi1.IsSupported)
options.Add(nameof(Bmi1));
if (Bmi2.IsSupported)
options.Add(nameof(Bmi2));
if (Fma.IsSupported)
options.Add(nameof(Fma));
if (Lzcnt.IsSupported)
options.Add(nameof(Lzcnt));
if (Pclmulqdq.IsSupported)
options.Add(nameof(Pclmulqdq));
if (Popcnt.IsSupported)
options.Add(nameof(Popcnt));
if (Sse.IsSupported)
options.Add(nameof(Sse));
if (Sse2.IsSupported)
options.Add(nameof(Sse2));
if (Sse3.IsSupported)
options.Add(nameof(Sse3));
if (Ssse3.IsSupported)
options.Add(nameof(Ssse3));
if (Sse41.IsSupported)
options.Add(nameof(Sse41));
if (Sse42.IsSupported)
options.Add(nameof(Sse42));
if (X86Base.IsSupported)
options.Add(nameof(X86Base));
// ARM
if (AdvSimd.IsSupported)
options.Add(nameof(AdvSimd));
if (ArmAes.IsSupported)
options.Add(nameof(ArmAes));
if (ArmBase.IsSupported)
options.Add(nameof(ArmBase));
if (Crc32.IsSupported)
options.Add(nameof(Crc32));
if (Dp.IsSupported)
options.Add(nameof(Dp));
if (Rdm.IsSupported)
options.Add(nameof(Rdm));
if (Sha1.IsSupported)
options.Add(nameof(Sha1));
if (Sha256.IsSupported)
options.Add(nameof(Sha256));
return string.Join(';', options);
contents.Text = string.Join('\n', RuntimeInformationPrinter.GetInformationDump());
}
}

View File

@@ -1,28 +1,29 @@
<Control xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Robust.Client.UserInterface.DevWindowTabUI">
<BoxContainer Orientation="Vertical">
<SplitContainer VerticalExpand="True" Orientation="Horizontal">
<BoxContainer Orientation="Vertical" MinWidth="250">
<!--
<BoxContainer Orientation="Horizontal">
<Button Name="RefreshButton" Text="{Loc 'dev-window-ui-refresh'}" />
x:Class="Robust.Client.UserInterface.DevWindowTabUI"
xmlns:gfx="clr-namespace:Robust.Client.Graphics">
<PanelContainer HorizontalExpand="True" VerticalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#25252add" ContentMarginTopOverride="3" />
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical">
<PanelContainer Name="TopbarButtons" HorizontalExpand="True">
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
<Button Name="ControlPicker" ToggleMode="True" Text="Inspect Element" />
<Button Name="RefreshPropertiesButton" Text="Refresh Props" />
</BoxContainer>
-->
<Button Name="ControlPicker" ToggleMode="True" Text="Inspect" />
<ScrollContainer VerticalExpand="True">
<BoxContainer Name="ControlTreeRoot" Orientation="Vertical" MouseFilter="Stop" />
</PanelContainer>
<SplitContainer HorizontalExpand="True" VerticalExpand="True"
Orientation="Horizontal" ResizeMode="RespectChildrenMinSize">
<ScrollContainer>
<PanelContainer Name="TreeRootBG">
<BoxContainer Name="ControlTreeRoot" Orientation="Vertical" MouseFilter="Stop" />
</PanelContainer>
</ScrollContainer>
</BoxContainer>
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Button Name="RefreshPropertiesButton" Text="Refresh" />
</BoxContainer>
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
<GridContainer Name="ControlProperties" Columns="2" />
<ScrollContainer HScrollEnabled="False">
<BoxContainer Name="ControlProperties" Orientation="Vertical" MouseFilter="Stop" />
</ScrollContainer>
</BoxContainer>
</SplitContainer>
</BoxContainer>
</SplitContainer>
</BoxContainer>
</PanelContainer>
</Control>

View File

@@ -10,174 +10,223 @@ using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Robust.Client.UserInterface
namespace Robust.Client.UserInterface;
[GenerateTypedNameReferences]
public sealed partial class DevWindowTabUI : Control
{
[GenerateTypedNameReferences]
public sealed partial class DevWindowTabUI : Control
[Dependency] private readonly IClydeInternal _clyde = default!;
[Dependency] private readonly IInputManager _input = default!;
public Control? SelectedControl { get; private set; }
private Dictionary<Control, DevWindowUITreeEntry> ControlMap { get; } = new();
private Control? LastHoveredControl { get; set; }
public event Action? SelectedControlChanged;
public DevWindowTabUI()
{
[Dependency] private readonly IClydeInternal _clyde = default!;
[Dependency] private readonly IInputManager _input = default!;
InitializeComponent();
public Control? SelectedControl { get; private set; }
private Dictionary<Control, DevWindowUITreeEntry> ControlMap { get; } = new();
private Control? LastHoveredControl { get; set; }
public event Action? SelectedControlChanged;
public DevWindowTabUI()
TopbarButtons.PanelOverride = new StyleBoxFlat
{
InitializeComponent();
BorderThickness = new Thickness(0, 0, 0, 1),
BorderColor = Color.Gray,
ContentMarginLeftOverride = 3,
ContentMarginRightOverride = 3,
ContentMarginBottomOverride = 1,
ContentMarginTopOverride = 3,
};
TreeRootBG.PanelOverride = new StyleBoxFlat
{
BorderThickness = new Thickness(0, 0, 1, 0),
BorderColor = Color.Gray,
ContentMarginLeftOverride = 3,
ContentMarginBottomOverride = 3,
};
}
private void InitializeComponent()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
ControlTreeRoot.OnKeyBindDown += ControlTreeRootOnKeyBindDown;
RefreshPropertiesButton.OnPressed += _ => Refresh();
}
private Control? GetControlUnderMouse()
{
return UserInterfaceManager.MouseGetControl(_input.MouseScreenPosition);
}
private void OnMouseMove(MouseMoveEventArgs obj)
{
if (!ControlPicker.Pressed)
return;
var controlUnderMouse = GetControlUnderMouse();
if (LastHoveredControl == controlUnderMouse)
return;
LastHoveredControl = controlUnderMouse;
Stack<Control> entryStack = new();
DevWindowUITreeEntry? entry = null;
var control = controlUnderMouse;
while (control != null)
{
if (ControlMap.TryGetValue(control, out entry))
break;
entryStack.Push(control);
control = control.Parent;
}
private void InitializeComponent()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
if (entry == null)
return;
ControlTreeRoot.OnKeyBindDown += ControlTreeRootOnKeyBindDown;
RefreshPropertiesButton.OnPressed += _ => Refresh();
if (entryStack.Count > 0)
entry.Open();
foreach (var subEntry in entryStack)
{
ControlMap[subEntry].Open();
}
private Control? GetControlUnderMouse()
SelectControl(controlUnderMouse);
}
private bool OnUIKeyBindStateChanged(BoundKeyEventArgs arg)
{
if (arg.Function != EngineKeyFunctions.UIClick)
return false;
if (!ControlPicker.Pressed)
return false;
var control = GetControlUnderMouse();
if (control == ControlPicker)
return false;
ControlPicker.Pressed = false;
return true;
}
private void ControlTreeRootOnKeyBindDown(GUIBoundKeyEventArgs obj)
{
if (obj.Function != EngineKeyFunctions.UIClick)
return;
obj.Handle();
SelectControl(null);
}
protected override void EnteredTree()
{
base.EnteredTree();
// Load tree roots.
foreach (var root in UserInterfaceManager.AllRoots)
{
return UserInterfaceManager.MouseGetControl(_input.MouseScreenPosition);
var entry = new DevWindowUITreeEntry(this, root);
ControlTreeRoot.AddChild(entry);
}
private void OnMouseMove(MouseMoveEventArgs obj)
{
if (!ControlPicker.Pressed)
return;
UserInterfaceManager.OnPostDrawUIRoot += OnPostDrawUIRoot;
_clyde.MouseMove += OnMouseMove;
_input.UIKeyBindStateChanged += OnUIKeyBindStateChanged;
}
var controlUnderMouse = GetControlUnderMouse();
if (LastHoveredControl == controlUnderMouse)
return;
protected override void ExitedTree()
{
base.ExitedTree();
LastHoveredControl = controlUnderMouse;
// Clear tree children.
ControlTreeRoot.RemoveAllChildren();
Stack<Control> entryStack = new();
DevWindowUITreeEntry? entry = null;
var control = controlUnderMouse;
while (control != null)
{
if (ControlMap.TryGetValue(control, out entry))
break;
UserInterfaceManager.OnPostDrawUIRoot -= OnPostDrawUIRoot;
_clyde.MouseMove -= OnMouseMove;
_input.UIKeyBindStateChanged -= OnUIKeyBindStateChanged;
}
entryStack.Push(control);
control = control.Parent;
}
private void OnPostDrawUIRoot(PostDrawUIRootEventArgs eventArgs)
{
if (SelectedControl == null || eventArgs.Root != SelectedControl.Root)
return;
if (entry == null)
return;
var rect = UIBox2i.FromDimensions(SelectedControl.GlobalPixelPosition, SelectedControl.PixelSize);
eventArgs.DrawingHandle.DrawRect(rect, Color.Cyan.WithAlpha(0.35f));
}
if (entryStack.Count > 0)
entry.Open();
internal void EntryAdded(DevWindowUITreeEntry entry)
{
ControlMap[entry.VisControl] = entry;
}
foreach (var subEntry in entryStack)
{
ControlMap[subEntry].Open();
}
SelectControl(controlUnderMouse);
}
private bool OnUIKeyBindStateChanged(BoundKeyEventArgs arg)
{
if (arg.Function != EngineKeyFunctions.UIClick)
return false;
if (!ControlPicker.Pressed)
return false;
var control = GetControlUnderMouse();
if (control == ControlPicker)
return false;
ControlPicker.Pressed = false;
return true;
}
private void ControlTreeRootOnKeyBindDown(GUIBoundKeyEventArgs obj)
{
if (obj.Function != EngineKeyFunctions.UIClick)
return;
obj.Handle();
public void EntryRemoved(DevWindowUITreeEntry entry)
{
if (SelectedControl == entry.VisControl)
SelectControl(null);
}
protected override void EnteredTree()
ControlMap.Remove(entry.VisControl);
}
public void SelectControl(Control? control)
{
SelectedControl = control;
SelectedControlChanged?.Invoke();
Refresh();
}
private void Refresh()
{
ControlProperties.RemoveAllChildren();
if (SelectedControl == null)
return;
var props = GuiDumpCommand.PropertyValuesForInheritance(SelectedControl);
foreach (var (classname, values) in props)
{
base.EnteredTree();
// Load tree roots.
foreach (var root in UserInterfaceManager.AllRoots)
ControlProperties.AddChild(new PanelContainer
{
var entry = new DevWindowUITreeEntry(this, root);
ControlTreeRoot.AddChild(entry);
}
UserInterfaceManager.OnPostDrawUIRoot += OnPostDrawUIRoot;
_clyde.MouseMove += OnMouseMove;
_input.UIKeyBindStateChanged += OnUIKeyBindStateChanged;
}
protected override void ExitedTree()
{
base.ExitedTree();
// Clear tree children.
ControlTreeRoot.RemoveAllChildren();
UserInterfaceManager.OnPostDrawUIRoot -= OnPostDrawUIRoot;
_clyde.MouseMove -= OnMouseMove;
_input.UIKeyBindStateChanged -= OnUIKeyBindStateChanged;
}
private void OnPostDrawUIRoot(PostDrawUIRootEventArgs eventArgs)
{
if (SelectedControl == null || eventArgs.Root != SelectedControl.Root)
return;
var rect = UIBox2i.FromDimensions(SelectedControl.GlobalPixelPosition, SelectedControl.PixelSize);
eventArgs.DrawingHandle.DrawRect(rect, Color.Cyan.WithAlpha(0.35f));
}
internal void EntryAdded(DevWindowUITreeEntry entry)
{
ControlMap[entry.VisControl] = entry;
}
public void EntryRemoved(DevWindowUITreeEntry entry)
{
if (SelectedControl == entry.VisControl)
SelectControl(null);
ControlMap.Remove(entry.VisControl);
}
public void SelectControl(Control? control)
{
SelectedControl = control;
SelectedControlChanged?.Invoke();
Refresh();
}
private void Refresh()
{
ControlProperties.RemoveAllChildren();
if (SelectedControl == null)
return;
var props = GuiDumpCommand.PropertyValuesFor(SelectedControl);
foreach (var (prop, value) in props)
Children = { new Label
{
Text = classname,
Margin = new Thickness(3, 2)
}},
PanelOverride = new StyleBoxFlat
{
BorderThickness = new Thickness(0, 1),
BorderColor = Color.Gray,
BackgroundColor = Color.FromHex("#555555"),
}
});
foreach (var (prop, value) in values)
{
ControlProperties.AddChild(new Label { Text = prop });
ControlProperties.AddChild(new Label { Text = value });
ControlProperties.AddChild(new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
SeparationOverride = 3,
Margin = new Thickness(3, 1),
Children =
{
new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label { Text = $"{prop}", FontColorOverride = Color.GreenYellow },
new Label { Text = ":" }, // this is for the non colored ":", intentional
}
},
new Label { Text = $"{value}" },
}
});
}
}
}

View File

@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using Robust.Client.Audio.Sources;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared;
using Robust.Shared.Audio.Sources;
using Robust.Shared.Map;
namespace Robust.Client.UserInterface
@@ -119,6 +121,20 @@ namespace Robust.Client.UserInterface
void DeferAction(Action action);
public event Action<Control>? OnKeyBindDown;
void SetClickSound(IAudioSource? source);
/// <summary>
/// Plays the UI click sound if relevant
/// </summary>
void ClickSound();
void SetHoverSound(IAudioSource? source);
/// <summary>
/// Plays the UI hover sound if relevant.
/// </summary>
void HoverSound();
}
public readonly struct PostDrawUIRootEventArgs

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