Compare commits

...

175 Commits

Author SHA1 Message Date
Pieter-Jan Briers
b201f10c76 Reduce some dictionary allocations in RSI loading. 2021-05-02 01:08:51 +02:00
Pieter-Jan Briers
a9208c0d29 Reduce allocations in RSIResource.LoadRsiMetadata somewhat.
Using stackallocs for the meta.json file.
2021-05-02 00:55:01 +02:00
Pieter-Jan Briers
b8cc01d872 Remove unecessary array allocation from ResourcePath constructor. 2021-05-02 00:16:52 +02:00
Pieter-Jan Briers
2d827890e9 Fix nullable errors reported by Rider. 2021-05-02 00:00:40 +02:00
Pieter-Jan Briers
f86d6ccd3c Remvoe unused .dll.config and app.config. 2021-05-02 00:00:04 +02:00
Vera Aguilera Puerto
967b76483a Fix rendering tree system crash involving nullspace.
Nullspace is a valid map, so we need to account for that!
2021-04-30 01:11:16 +02:00
Vera Aguilera Puerto
ef2c0ad8cf Fix the comp lifetime ComponentRemove event
Now, event tables DispatchComponent takes in the component reference directly.
2021-04-30 01:09:50 +02:00
metalgearsloth
9ae1352030 Enable nullables as errors in Robust (#1732) 2021-04-29 15:56:05 -06:00
SweptWasTaken
d8d9b271cc Updates sandbox.yml 2021-04-28 14:31:46 -07:00
Acruid
122acc5fd5 SnapGridComponent Removal (#1720)
* Removed SnapGridOffset, there is only center now.

* SnapGridComponent methods are now static.

* Removed SnapGridComponent.OnPositionChanged.

* Refactored static functions off SnapGridComponent to MapGrid.
Refactored away usages of SnapGridComponent.Position.

* Added Transform.Anchored for checking if an entity is a tile entity.
More refactoring for static MapGrid functions.

* Static snapgrid methods on MapGrid are no longer static.

* Removed IMapGrid.SnapSize, it is now always equal to the IMapGrid.TileSize.

* Add setter to ITransformComponent.Anchored.
Removed direct references to SnapGridComponent from content.

* Grid functions now deal with EntityUids instead of SnapGridComponents.
Began renaming public API functions from SnapGrid to Anchor.

* Add some unit tests.

* SnapGridComponent now anchors itself in startup, instead of Initialize (sending directed events in init is an error).
2021-04-28 10:24:11 -07:00
metalgearsloth
32dea84196 Fix physics build warnings (#1731) 2021-04-28 15:29:51 +02:00
DrSmugleaf
91d58dbca4 Make serialization work with backing fields automatically (#1727)
* Make serialization work with backing fields automatically

* Fix not taking priorities into account, make the test fail when that is the case

* Turn fieldDefs back into a list

* Format it better

* Remove GetInheritanceBehaviour, expose immutable array of base field definitions
2021-04-27 14:11:06 +02:00
MehimoNemo
c54b1572f5 Unbuckling Closes Inventory UI (#1723)
* pain

* Add test for parent changed when not at 0,0

* Fix map loading of grids

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
2021-04-26 12:26:29 -07:00
DrSmugleaf
1fa979c0f6 Add serialization manager shutdown method and initialize benchmark (#1728) 2021-04-25 13:00:31 -07:00
metalgearsloth
760599171d Obsolete collision interfaces (#1725) 2021-04-25 12:58:30 -07:00
metalgearsloth
10d295d535 Basic Collisionmanager tests (#1722)
* Add some unit tests for CollisionManager
2021-04-25 12:49:45 -07:00
ShadowCommander
0047c5000f Fix placement of grid tiles on the edge of a grid (#1724)
* Fix placement of grid tiles on the edge of a grid
2021-04-25 12:48:26 -07:00
metalgearsloth
810a6d190f Add MapId error to tp command (#1726)
Previously it would just dump the exception
2021-04-25 04:12:28 -07:00
metalgearsloth
197227dcf6 Fix showbb
matrices momento
2021-04-25 19:03:19 +10:00
Swept
fa23ec8fc6 Updates the README.md 2021-04-23 19:23:54 +00:00
Vera Aguilera Puerto
6506171ea0 Server ContentStart.StartLibrary method.
This will skip the FULL_RELEASE check on games specifically using RobustToolbox as a library.
2021-04-23 20:37:45 +02:00
Vera Aguilera Puerto
8bd1e72e9f Adds GameController options for games using RobustToolbox as a library. (#1711) 2021-04-23 00:05:42 +02:00
Acruid
4ce6629ace RSIs fail to load if they have duplicate states defined in their JSON. (#1701) 2021-04-22 15:21:34 -06:00
Vera Aguilera Puerto
f9ef605903 Add optional AwaitEvent methods that take a Type instead of using generics. 2021-04-21 15:29:43 +02:00
Vera Aguilera Puerto
c6b74e998f Use System.Numerics in a few Box2 methods to speed them up (#1708) 2021-04-19 17:18:09 +02:00
Vera Aguilera Puerto
c4946b8466 Viewport Improvements (#1528)
Co-authored-by: 20kdc <asdd2808@gmail.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-04-19 09:47:20 +02:00
Pieter-Jan Briers
ffa908bf27 More workarounding for Rider Avalonia faking 2021-04-19 01:47:33 +02:00
Pieter-Jan Briers
0d37ff3f20 Fix reporting of fatal GLFW errors on window creation. 2021-04-19 01:44:00 +02:00
Pieter-Jan Briers
7aecdcf70a Improved soft shadows. 2021-04-19 01:42:59 +02:00
Vera Aguilera Puerto
70f82d6db8 Add directed start/end collision events. (#1710) 2021-04-17 13:08:11 +02:00
Vera Aguilera Puerto
20b7870739 PlayerSession is now correctly setup on singleplayer 2021-04-17 01:52:07 +02:00
Vera Aguilera Puerto
172639baea Fix bug where TickUpdate is never ran in singleplayer mode. 2021-04-16 13:37:01 +02:00
metalgearsloth
6038483b1e DebugDrawing for DistanceJoints (#1703) 2021-04-16 10:55:31 +02:00
Vera Aguilera Puerto
39d98d591c Adds singleplayer support (#1704) 2021-04-16 10:53:59 +02:00
Vera Aguilera Puerto
01c2fc0730 ClydeTileDefinitionManager no longer throws when uninitialized, or when there are no tile definitions. (#1705) 2021-04-16 10:53:49 +02:00
ike709
1884bb0067 Fixes DateTimeOffset sandbox whitelisting (#1706) 2021-04-16 01:39:18 +02:00
Vera Aguilera Puerto
1c368bbaa8 Remove useless debug prototypes. 2021-04-15 20:44:25 +02:00
metalgearsloth
d16078a35f Don't reset sleeptimer on building physics islands 2021-04-15 23:18:05 +10:00
Vera Aguilera Puerto
4dd04207ac Shared GameTiming no longer depends on INetManager (#1697)
Adds ClientGameTiming with prediction and local/server time methods.
2021-04-14 11:40:02 -07:00
Vera Aguilera Puerto
02af42da30 Refactors EntityManager to not do any networking. (#1695)
* Refactors EntityManager to not do any networking.
ServerEntityManager and ClientEntityManager now do the networking instead.

* Rename property for "backwards compat."

* Remove comented out code in robust server simulation
2021-04-14 11:39:21 -07:00
Vera Aguilera Puerto
2c75c8b36d Refactors MapManager to not do any networking. (#1696)
* Refactors MapManager to not do any networking.
Now, ServerMapManager and ClientMapManager handle any networking.

* it's christmas in april!

* Remove comented line

* Remove useless seal

* Fix incorrect semicolon

* Event is no longer overriden, has a protected Invoke method instead
2021-04-14 11:13:58 -07:00
Vera Aguilera Puerto
013e6f7ce4 Move INetManager dependency from PrototypeManager to Server/ClientPrototypeManager 2021-04-14 14:55:40 +02:00
Acruid
cbd7b62ad7 Component Lifetime Events (#1660)
* Events are now raised for component OnAdd/Initialize/Startup/Shutdown/OnRemove.
Code cleanup in the Component class.
2021-04-13 17:16:41 -07:00
metalgearsloth
c1396f1c50 Named fixtures (#1684)
* Add named fixtures

Useful for getting specific collisions.

* Final cleanup

* More cleanup
2021-04-13 20:47:13 +10:00
Vera Aguilera Puerto
3ec9e7a734 Remove a few (now irrelevant) comments mentioning Godot. 2021-04-12 20:10:05 +02:00
Vera Aguilera Puerto
3a1e6e84b1 Remove Unused sharer params enum from the Godot era. 2021-04-12 20:09:22 +02:00
Vera Aguilera Puerto
7224419f77 Remove unused AudioMixTarget 2021-04-12 20:03:57 +02:00
Vera Aguilera Puerto
056e4de0c1 Appearance System cleanup.
Removes a nearly 4 years old unused visualizer that had a hardcoded update method in the appearance system, for some reason.
2021-04-12 19:59:25 +02:00
Vera Aguilera Puerto
aa90f22e23 Adds abstract class for entity events that can be cancelled. (#1688) 2021-04-11 18:51:07 +02:00
metalgearsloth
071234095d Don't use linked-list for contact pooling (#1683)
Always seemed icky to me. Aether uses a linked-list (like world contacts) and Farseer just uses a Queue.
2021-04-10 18:33:01 +02:00
Vera Aguilera Puerto
5b06391159 Fix objects of a server-only type not correctly showing up as such in VV. 2021-04-10 17:32:56 +02:00
Vera Aguilera Puerto
8edd44086b AudioSystem and DebugPhysicsIslandSystem unsubscribe from events on shutdown now. 2021-04-09 16:07:44 +02:00
Vera Aguilera Puerto
ccf212e9cb GridTileLookupSystem unsubscribes from events on shutdown. 2021-04-09 13:51:12 +02:00
Vera Aguilera Puerto
493011d1f9 SnapGridSystem uses directed MoveEvent, unsubscribes on shutdown. 2021-04-09 13:24:21 +02:00
Vera Aguilera Puerto
40e193df33 Adds directed/broadcast event for SnapGrid component position change. 2021-04-09 13:21:15 +02:00
Vera Aguilera Puerto
5068294d38 Makes a bunch of TransformComponent events directed. (#1682) 2021-04-08 20:42:46 +02:00
metalgearsloth
24054b5e2f Optimise showbb some more 2021-04-08 18:37:22 +10:00
Paul Ritter
17869c16cd when the bool 2021-04-06 11:58:17 +02:00
metalgearsloth
d8aad89c2f Split entity management from entity queries (#1665)
* Split entity lookups from entitymanager

* Helps if you subscribe dingus

* Handle map changes

* Stacks instead

* Make mapchanges use a queue because it's probably better

Moves likely only care about the latest position

* IoC what you did there

* IoC refactor

* Minor optimisations

* Apply feedback

* My IQ dropped 3 sizes that day

* Rest of acruid's feedback

* final_no_actual commit

* enlightenment?

* Liftoff

* final_commit_v2_actual

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-04-06 13:29:48 +10:00
DrSmugleaf
2a349eb023 Optimize serialization reading, create benchmarks (#1679)
* Add Robust.Benchmarks and read string benchmark

* Separate serialization manager methods, use compiled lambdas to call manager read

4 us > 200 ns

* Add int and data definition with string benchmarks

* Make serialization population use expressions to create definitions

* Make benchmark classes internal and create seed data definition

* Add complex data definition read benchmark

* Create primitive serializers, remove primitive special case

|                 Method |        Mean |     Error |    StdDev |
|----------------------- |------------:|----------:|----------:|
|             ReadString |    227.1 ns |   4.47 ns |   5.65 ns |
|            ReadInteger |    245.4 ns |   4.82 ns |   6.26 ns |
|  ReadDataDefWithString |    804.7 ns |  15.27 ns |  16.34 ns |
| ReadSeedDataDefinition | 15,846.8 ns | 312.89 ns | 773.39 ns |

* Remove testing code

* Setup delegates during initialize

* Revert "Setup delegates during initialize"

This reverts commit 7ff4d4eaaa.

* Store delegates in a concurrent dictionary because I really cannot be arsed to generate them on initialize at this point
2021-04-05 14:50:33 +02:00
Vera Aguilera Puerto
47ad07b3d2 Adds directed event for when an entity's BodyType changes. (#1681)
Removes old Anchored C# event.
2021-04-05 13:17:09 +02:00
ShadowCommander
aacf6522b4 Remove NetId requirement for local event subscriptions (#1675) 2021-04-02 18:45:59 -07:00
ShadowCommander
c73d27b9ae Add RSI path to error log (#1676) 2021-04-02 16:45:21 -07:00
Vera Aguilera Puerto
f068b30a7c Adds Prototype Id Validator for Dictionaries whose keys are prototype IDs. (#1673) 2021-04-02 15:47:48 +02:00
Vera Aguilera Puerto
5400dddcfc Fix ComponentDependencies tests for debug & release 2021-04-02 13:56:42 +02:00
metalgearsloth
6cf5fdc5d6 Grid-trees for rendering (#1666) 2021-04-02 20:25:16 +11:00
Vera Aguilera Puerto
5d46663881 Fix ComponentDependencies tests 2021-03-31 22:17:55 +02:00
Acruid
8e0f227940 PVS Bugfixes 1: The Debuggening (#1671)
* Wrapped the parallel GetMail function in a try/catch.
Added a hack to the ViewCulling leave message that skips ents that don't exist.
Always send ALL map and grid entities to the client.
More info logging about adding/removing maps/grids.

* Will now still send required map critical entities even if client is not attached to an entity.
PvsEnabled and PvsRange are now writeable.
2021-03-31 21:56:11 +02:00
metalgearsloth
73a13fff9a Fix grid bounds upon deserialization 2021-03-31 19:33:19 +11:00
DrSmugleaf
de2e505a12 Make content able to choose which log level leads to test failures (#1670)
* Make content able to choose which log level leads to test failures

* Now make it make sense
2021-03-31 19:26:38 +11:00
DrSmugleaf
a9f7c7a76f Fix no HWId userdata error in integration tests (#1667) 2021-03-30 15:39:43 +02:00
Vera Aguilera Puerto
37401c26c9 Adds a custom editor for Prototypes to ViewVariables. (#1663)
Also improves VV a bit.
2021-03-30 15:33:15 +02:00
Vera Aguilera Puerto
528cd1e0e5 Fix fullscreen crash 2021-03-30 15:32:15 +02:00
Pieter-Jan Briers
2959456bec Fix integration test networking. 2021-03-30 13:28:26 +02:00
Metal Gear Sloth
8951712495 Add NaN guards to physics 2021-03-30 21:59:15 +11:00
metalgearsloth
d8612aff64 Re-implement inertia (#1652)
* Implement inertia

* actual SPEEN

* Sync mass

* bitcoin miner

* I am le dumb

* also dis

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-30 21:42:05 +11:00
Acruid
e16732eb7b Network View Bubble (#1629)
* Adds barbones culling.

* Visibility culling and recursive parent ent additions.
DebugEntityNetView improvements.
Visibility moved from session to eyecomponent.

* Multiple viewport support.

* Perf improvements.

* Removed old netbubble system from ServerEntityManager.
Supports old NaN system for entities leaving view.
Supports old SendFullMap optimization for anchored, non-updating Entities.

* Fixes size of netView box.

* Remove empty EntityManager.Update method.
Switching ViewCulling back to PLINQ.
2021-03-29 16:17:34 -07:00
Acruid
91f61bb9de Reverts component NetId storage in ComponentManager back to the way Acruid originally designed it.
Removes NetId methods from IEntity, content does not need to be messing with them.
Fixes bug in DeleteComponent where the ComponentDeleted event was not being raised if a component did not have a NetId.
2021-03-29 03:40:48 -07:00
Pieter-Jan Briers
ddc91d05ec Some work towards multi-monitor support in Clyde.
Most of this was me experimenting with GLFW, but I figured I'd still commit it.
2021-03-28 21:23:38 +02:00
Acruid
ef22842b90 Fixes bug where FirstTimePredicted was not being set properly for the first predicted frame. 2021-03-27 20:16:04 -07:00
Pieter-Jan Briers
303e2152d2 UIScale now updates dynamically.
So if you move the window between different monitors with different scaling, the game updates.
2021-03-28 01:55:35 +01:00
Vera Aguilera Puerto
37fc0d0d2a Set correct class constrains for prototype id list serializers 2021-03-27 22:47:24 +01:00
Vera Aguilera Puerto
53987e1e5d Adds prototype "Variant" helper methods to IPrototypeManager (#1662) 2021-03-27 22:40:07 +01:00
metalgearsloth
3216d7770b Fix net.rate cvar warning (#1659)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-27 02:14:45 -07:00
Acruid
3203ca2ff4 Removed Control.Update from the UI system. UI Controls have no business running code in simulation updates.
Refactored the client update loop so that the GameStateManager is in full control of the simulation update.
2021-03-26 17:46:34 -07:00
metalgearsloth
e22254cd51 Clear velocities on container insertion (#1653)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-26 22:39:11 +01:00
Acruid
7ed722f669 Visibility moved from session to EyeComponent (#1657) 2021-03-26 22:38:45 +01:00
DrSmugleaf
4864096b2a Add prototype id list serializer and tests (#1658)
* Add prototype id list serializer and tests

* Bring old .Value code back

* Paul made me do this
2021-03-26 20:52:22 +01:00
Acruid
5161385de4 Removed unused Update and Resize code from GameStates. Presenters can get resize events from the interface manager (hint: you won't ever need to), and there is no reason for a UI Presenter to do anything in simulation ticks (UI should be event driven, not polling data every frame). 2021-03-25 14:01:51 -07:00
Acruid
98e009b38f Removed the GameController dependency from Clyde.
Removed the ConfigurationManager dependency from FontManager.
2021-03-25 11:36:57 -07:00
Vera Aguilera Puerto
3863ab8f62 Adds PrototypeIdHashSetSerializer for HashSet<string> prototype ID validation (#1656)
* Adds PrototypeIdHashSetSerializer for HashSet<string> prototype ID validation

* Paul changes

* cleanup, better stuff
2021-03-25 14:26:14 +01:00
Metal Gear Sloth
f576eb5125 Optimise showbb 2021-03-25 23:32:06 +11:00
Acruid
314742ccd8 NullableHelper tests now properly set up their required DI container instead of reusing the container from whatever test was ran before it. Service Locator anti-pattern :( 2021-03-25 02:02:09 -07:00
Acruid
f9074811f9 Adds constructor injection to the IoCManager & DependencyCollection. 2021-03-25 01:16:08 -07:00
Pieter-Jan Briers
5f3e1eb378 Frame graph now shows when GCs occur. 2021-03-25 02:24:38 +01:00
Pieter-Jan Briers
3c1ee20ca1 A 2021-03-25 02:05:28 +01:00
Pieter-Jan Briers
3768f5e68e Remove allocs from ContainerSlot.ContainedEntities. 2021-03-25 01:56:06 +01:00
Pieter-Jan Briers
765a560380 Fix integer overflow breaking Lidgren metrics. 2021-03-25 01:47:45 +01:00
Metal Gear Sloth
39ae3ac653 Optimise physics do not research 2021-03-24 22:35:17 +11:00
Pieter-Jan Briers
e48f4027e5 Probably fix running Robust directly for some people. 2021-03-23 21:28:36 +01:00
ShadowCommander
2fa1e98faf Fix CopyWithTypeSerializer not copying when null (#1651) 2021-03-22 11:02:32 +01:00
Pieter-Jan Briers
cedfa0ee2f Nothing to see here. 2021-03-21 20:37:10 +01:00
Acruid
92f44b390e SoundSystem Improvements (#1649) 2021-03-21 16:35:52 +01:00
Pieter-Jan Briers
65a42f9209 Prototype reloading now fires an event. 2021-03-21 16:25:52 +01:00
Acruid
ebf53248cf TestLogHandler now fails the test if a warning or higher is logged. 2021-03-19 13:42:13 -07:00
Acruid
289f637e8a Entity Lifetime Levels (#1644)
* Added an entity lifetime levels property.
Added exception when recursively deleting an entity.

* Add a directed event 'EntityTerminatingEvent' for right before an entity is deleted.

* Added MapInit lifestage to entities.
2021-03-18 22:53:05 -07:00
metalgearsloth
d7c13f30c8 Fix showbb awake (#1632)
* Fix showbb awake

* Slight tweak

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-17 13:58:26 -07:00
Vera Aguilera Puerto
0dac17ae5e ConfigurationManager.OnValueChanged's invokeImmediately now accounts for overriden values correctly. 2021-03-17 20:22:44 +01:00
Pieter-Jan Briers
9a19a774fa Use stencil test to cull FOV-hidden lights early.
Massive shader optimization.
2021-03-17 13:16:47 +01:00
Pieter-Jan Briers
81f49d5eb2 Fix moving to the end of a textbox. 2021-03-17 01:22:45 +01:00
DrSmugleaf
4f3b4ac2d2 Changes for content server nullability (#1642) 2021-03-16 15:47:49 +01:00
Pieter-Jan Briers
e428056b52 Rldrsc now works with textures. 2021-03-16 12:39:19 +01:00
DrSmugleaf
8dc9d2989a Fix not being able to use shared entity systems in update order (#1638) 2021-03-16 12:38:31 +01:00
Paul
fd8c90dcbb reverting cringe (moved controller metrics cvar get to server) 2021-03-16 12:05:56 +01:00
Vera Aguilera Puerto
ffe4e5a8ab Add Enabled property to CollisionWake component. (#1641)
* Add Enabled property to CollisionWake component.

* Set property in HandleComponentState
2021-03-16 11:38:46 +01:00
Paul Ritter
6e5026d270 adds prometheus logging to physicscontrollers (#1640) 2021-03-16 11:32:46 +01:00
metalgearsloth
946c4166dc Move RootControl frameupdate to after queue (#1625)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-16 09:15:18 +01:00
Paul Ritter
7d2fb85a04 adds custom typeserializers (#1636)
retires DataFieldWithConstantAttribute & DataFieldWithFlagAttribute in favor of new customtypeserializers
adds prototypeidvalidation, just needs to be added to the corresponding fields
fixes some behaviour in yamllinter
2021-03-15 13:24:29 +01:00
metalgearsloth
d6ec078519 Fix static sleeping crash (#1630)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-14 12:44:49 +11:00
DrSmugleaf
32256fc4d9 Remove printing ticks in integration tests (#1627) 2021-03-13 20:12:49 +01:00
DrSmugleaf
37bbdfe7ff Fix serialization logging not printing messages (#1628) 2021-03-13 20:12:41 +01:00
metalgearsloth
c906675cdf Set collidable on CollisionWake removal (#1626)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-13 20:13:00 +11:00
DrSmugleaf
90bb5574c1 Add a CVar to disable texture preloading for tests (#1623) 2021-03-13 13:25:10 +11:00
Acruid
7b50dcd969 Removed IEntityManager.SpawnEntityNoMapInit. Every entity spawned into an uninitialized map does not have mapinit ran, so this is useless. 2021-03-11 22:17:31 -08:00
Pieter-Jan Briers
8d82f48a8f Various GLES fixes. 2021-03-11 13:06:30 +01:00
Pieter-Jan Briers
469f9fd219 Remove #line directives from shaders.
They hurt debugging more than they helped.
2021-03-11 13:06:18 +01:00
Pieter-Jan Briers
1a5783ab4e Probably fix tests 2021-03-11 11:47:58 +01:00
Clyybber
3d25886d79 Set velocity for audio sources, enabling doppler effect (#1622) 2021-03-11 11:44:01 +01:00
Pieter-Jan Briers
516b2cd372 Handle surrogate pairs correctly in LineEdit. 2021-03-10 16:55:12 +01:00
Pieter-Jan Briers
3cfcfa0be2 Render fallback character for unavailable characters. 2021-03-10 16:54:52 +01:00
Pieter-Jan Briers
69328087bd Added AsRune property to TextEventArgs 2021-03-10 16:54:13 +01:00
Pieter-Jan Briers
1bf8b2a52b Use Rune for rendering text instead of char.
Fixes crashes with surrogates.
2021-03-09 23:25:27 +01:00
Pieter-Jan Briers
fc6dc6f4e1 Add/fix Rune APIs for sandbox. 2021-03-09 23:24:33 +01:00
Pieter-Jan Briers
31c1feca4e Debug console history improvements.
No longer blows up if history cannot be read/written thanks to file locking.

Made it more async so it won't waste main thread init time.
2021-03-09 22:28:58 +01:00
Pieter-Jan Briers
3ed1eef2ab Fix build. 2021-03-09 21:44:46 +01:00
Pieter-Jan Briers
1394a017bb Fix IL verification throwing if a verifier error does not need to be formatted. 2021-03-09 21:41:50 +01:00
metalgearsloth
6b0670d5f1 Break joints on container insertion; semi-related to break pulling on container insertion (#1620)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-09 11:51:57 -08:00
metalgearsloth
f573331541 Fix physics joint disconnect spam (#1619)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-09 11:51:35 -08:00
metalgearsloth
a7218cd3b8 Make Visible a Shared Property for sprites (#1615)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-09 11:51:16 -08:00
Acruid
f7e8178736 Added new ComponentEvents system in IEventBus. (#1601)
* Added new ComponentEventBus, combined it with IEventBus.

* Removed all traces of IEntity from ComponentDependencies.
Removed IEntityManager dependency from ComponentManager.

* Added entity create/delete events to IEntityManager.

* ComponentEvents now use EntitySystemMessages instead of their custom ComponentEvent class.

* Component events are now just overloads of entity events.

* Removed obsolete EntitySystemMessage, now everything uses the base EntityEventArgs.

* Add a bool argument for if the message should be broadcast as well as directed.
Fix ordering and init issues of events in EntityManager.

* Changed names from Component/Entity events to Directed/Broadcast.

* Fix bugs and unit tests.
2021-03-09 11:02:24 -08:00
Pieter-Jan Briers
31f921e4aa Use ProfileOptimization to speed up startup. 2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
aa1c25637c Allow disabling nvidia optimus via env var. 2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
71f2c48463 Call GC.Collect after game init.
Cleans up any gen 2 garbage from init and the stutter shouldn't be the end of the world.
2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
d65f4ca898 RSI & texture preloading.
All RSIs and textures are now loaded ahead of time in client startup. This is well threaded and is extremely fast.
2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
b35568ffe5 Disable path case checks by default.
The idea was that these are Task.Run'd so don't influence performance. That was before we started threading the hell out of startup.

We're getting more stuff like YAML linting now which should hopefully be able to catch 99% of this. And louder because it was always just a warning before.
2021-03-09 12:29:59 +01:00
Acruid
a0d241e551 Removes some things that should not have been in the last PR. 2021-03-09 02:06:13 -08:00
GraniteSidewalk
33a6934582 Large number of changes to Shaders and Overlays (#1387)
* AAAAAAAAAAAAA

* Organization

* Still doesnt work

* Formatting

* It works!!

* More changes to everything

* Beginning of changes to overlays

* Makes the overlay manager GUID based (also it was very messy, still messy but i fixed some of it)

* Stencils are easy

* Questionable changes to overlays

* Minor change to HLR

* Fixed duplicate overlays when calling some commands (Like showbb)

* Fixes misleading message

* Adds a variety of worldspaces for overlays to choose from

* Caching

* Address reviews

* Merging pains

* ah.

* ahhhhh

* minor overlaymanager changes

* Work

* fix

* Merge??

* Fixes null errors

* Force update

* Delete whatever the fuck this is?

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-03-09 01:52:16 -08:00
metalgearsloth
f237a8bbbc Optimise static body sleeping (#1618)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-09 19:47:51 +11:00
Pieter-Jan Briers
4bc775c01c RSI loader improvements:
1. Stop using NJsonSchema, it didn't do anything useful.
2. Use System.Text.Json instead of Newtonsoft.Json.
3. General cleanup of the code, using arrays instead of lists, etc...
2021-03-08 11:18:19 +01:00
Pieter-Jan Briers
93b4d81505 Optimize ImageSharp blitting. 2021-03-08 11:15:33 +01:00
Pieter-Jan Briers
0afb85a09e Fix some missing re-pooling of ImageSharp images. 2021-03-08 09:45:22 +01:00
Metal Gear Sloth
7b9315cea4 Significantly lower physics speedcap 2021-03-08 15:46:50 +11:00
Metal Gear Sloth
dc3af45096 Fix anchored message 2021-03-08 15:00:08 +11:00
metalgearsloth
00ce0179ae Allow kinematic controllers to have an impulse applied (#1612)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-08 12:10:20 +11:00
ShadowCommander
81947ba3d8 Fix buckling (#1611) 2021-03-07 15:25:11 -08:00
DrSmugleaf
49327279d0 Fix nullability errors in physics ContactHead code (#1609) 2021-03-07 23:15:55 +01:00
Metal Gear Sloth
0936cf3c7f Fix CanCollide serialization 2021-03-08 04:06:21 +11:00
Metal Gear Sloth
43b75a69c2 Fix contact overlap 2021-03-08 03:48:30 +11:00
metalgearsloth
c17c8d7a11 Physics (#1605)
* Physics worlds

* Paul's a good boy

* Build working

* Ingame and not lagging to hell

* Why didn't you commit ahhhhh

* Hard collisions working

* Solver parity

* Decent broadphase work done

* BroadPhase outline done

* BroadPhase working

* waiting for pvs

* Fix static PVS AABB

* Stop static bodies from awakening

* Optimise a bunch of stuff

* Even more broadphase stuff

* I'm fucking stupid

* Optimise fixture updates

* Collision solver start

* Building

* A is for Argumentative

* Fix contact caching island flags

* Circle shapes actually workeded

* Damping

* DS2 consumables only

* Slightly more stable

* Even slightlier more stablier

* VV your heart out

* Initial joint support

* 90% of joints I just wanted to push as I'd scream if I lost progress

* JOINT PURGATORY

* Joints barely functional lmao

* Okay these joints slightly more functional

* Remove station FrictionJoint

* Also that

* Some Box2D ports

* Cleanup mass

* Edge shape

* Active contacts

* Fix active contacts

* Optimise active contacts even more

* Boxes be stacking

* I would die for smug oh my fucking god

* In which everything is fixed

* Distance joints working LETS GO

* Remove frequency on distancejoint

* Fix some stuff and break joints

* Crashing fixed mehbeh

* ICollideSpecial and more resilience

* auto-clear

* showbb vera

* Slap that TODO in there

* Fix restartround crash

* Random fixes

* Fix fixture networking

* Add intersection method for broadphase

* Fix contacts

* Licenses done

* Optimisations

* Fix wall clips

* Config caching for island

* allocations optimisations

* Optimise casts

* Optimise events queue for physics

* Contact manager optimisations

* Optimise controllers

* Sloth joint or something idk

* Controller graph

* Remove content cvar

* Random cleanup

* Finally remove VirtualController

* Manifold structs again

* Optimise this absolute retardation

* Optimise

* fix license

* Cleanup physics interface

* AHHHHHHHHHHHHH

* Fix collisions again

* snivybus

* Fix potential nasty manifold bug

* Tests go snivy

* Disable prediction for now

* Spans

* Fix ShapeTypes

* fixes

* ch ch changeesss

* Kinematic idea

* Prevent static bodies from waking

* Pass WorldAABB to MoveEvent

* Fix collisions

* manifold structs fucking WOOORRKKKINNGGG

* Better pushing

* Fix merge ickies

* Optimise MoveEvents

* Use event for collisions performance

* Fix content tests

* Do not research tests

* Fix most conflicts

* Paul's trying to kill me

* Maybe collisions work idk

* Make us whole again

* Smug is also trying to kill me

* nani

* shitty collisions

* Settling

* Do not research collisions

* SHIP IT

* Fix joints

* PVS moment

* Fix other assert

* Fix locker collisions

* serializable sleeptime

* Aether2D contacts

* Physics is no longer crashing (and burning)

* Add to the TODO list

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-08 03:19:01 +11:00
Paul
223fd8126f copy fix 2021-03-06 14:47:44 +01:00
Paul
1d5559be4a makes typevalidator its own interface 2021-03-05 15:02:03 +01:00
Paul
0b749ff8bb makes prototypeinheritance opt in 2021-03-05 11:13:00 +01:00
Paul
069fa89fcb adds Try variants to FirstOrNull & FirstOrDefault
fixes ientity serialization when loading the map
2021-03-05 10:07:18 +01:00
Paul Ritter
80f9f24243 Serialization v3 aka constant suffering (#1606)
* oops

* fixes serialization il

* copytest

* typo & misc fixes

* 139 moment

* boxing

* mesa dum

* stuff

* goodbye bad friend

* last commit before the big (4) rewrite

* adds datanodes

* kills yamlobjserializer in favor of the new system

* adds more serializers, actually implements them & removes most of the last of the old system

* changed yamlfieldattribute namespace

* adds back iselfserialize

* refactors consts&flags

* renames everything to data(field/definition)

* adds afterserialization

* help

* dataclassgen

* fuggen help me mannen

* Fix most errors on content

* Fix engine errors except map loader

* maploader & misc fix

* misc fixes

* thing

* help

* refactors datanodes

* help me mannen

* Separate ITypeSerializer into reader and writer

* Convert all type serializers

* priority

* adds alot

* il fixes

* adds robustgen

* argh

* adds array & enum serialization

* fixes dataclasses

* adds vec2i / misc fixes

* fixes inheritance

* a very notcursed todo

* fixes some custom dataclasses

* push dis

* Remove data classes

* boutta box

* yes

* Add angle and regex serializer tests

* Make TypeSerializerTest abstract

* sets up ioc etc

* remove pushinheritance

* fixes

* Merge fixes, fix yaml hot reloading

* General fixes2

* Make enum serialization ignore case

* Fix the tag not being copied in data nodes

* Fix not properly serializing flag enums

* Fix component serialization on startup

* Implement ValueDataNode ToString

* Serialization IL fixes, fix return and string equality

* Remove async from prototype manager

* Make serializing unsupported node as enum exception more descriptive

* Fix serv3 tryread casting to serializer instead of reader

* Add constructor for invalid node type exception

* Temporary fix for SERV3: Turn populate delegate into regular code

* Fix not copying the data of non primitive types

* Fix not using the data definition found in copying

* Make ISerializationHooks require explicit implementations

* Add test for serialization inheritance

* Improve IsOverridenIn method

* Fix error message when a data definition is null

* Add method to cast a read value in Serv3Manager

* Rename IServ3Manager to ISerializationManager

* Rename usages of serv3manager, add generic copy method

* Fix IL copy method lookup

* Rename old usages of serv3manager

* Add ITypeCopier

* resistance is futile

* we will conquer this codebase

* Add copy method to all serializers

* Make primitive mismatch error message more descriptive

* bing bong im going to freacking heck

* oopsie moment

* hello are you interested in my wares

* does generic serializers under new architecture

* Convert every non generic serializer to the new format, general fixes

* Update usgaes of generic serializers, cleanup

* does some pushinheritance logic

* finishes pushinheritance FRAMEWORK

* shed

* Add box2, color and component registry serializer tests

* Create more deserialized types and store prototypes with their deserialized results

* Fixes and serializer updates

* Add serialization manager extensions

* adds pushinheritance

* Update all prototypes to have a parent and have consistent id/parent properties

* Fix grammar component serialization

* Add generic serializer tests

* thonk

* Add array serializer test

* Replace logger warning calls with exceptions

* fixes

* Move redundant methods to serialization manager extensions, cleanup

* Add array serialization

* fixes context

* more fixes

* argh

* inheritance

* this should do it

* fixes

* adds copiers & fixes some stuff

* copiers use context v1

* finishing copy context

* more context fixes

* Test fixes

* funky maps

* Fix server user interface component serialization

* Fix value tuple serialization

* Add copying for value types and arrays. Fix copy internal for primitives, enums and strings

* fixes

* fixes more stuff

* yes

* Make abstract/interface skips debugs instead of warnings

* Fix typo

* Make some dictionaries readonly

* Add checks for the serialization manager initializing and already being initialized

* Add base type required and usage for MeansDataDefinition and ImplicitDataDefinitionForInheritorsAttribute

* copy by ref

* Fix exception wording

* Update data field required summary with the new forbidden docs

* Use extension in map loader

* wanna erp

* Change serializing to not use il temporarily

* Make writing work with nullable types

* pushing

* check

* cuddling slaps HARD

* Add serialization priority test

* important fix

* a serialization thing

* serializer moment

* Add validation for some type serializers

* adds context

* moar context

* fixes

* Do the thing for appearance

* yoo lmao

* push haha pp

* Temporarily make copy delegate regular c# code

* Create deserialized component registry to handle not inheriting conflicting references

* YAML LINTER BABY

* ayes

* Fix sprite component norot not being default true like in latest master

* Remove redundant todos

* Add summary doc to every ISerializationManager method

* icon fixes

* Add skip hook argument to readers and copiers

* Merge fixes

* Fix ordering of arguments in read and copy reflection call

* Fix user interface components deserialization

* pew pew

* i am going to HECK

* Add MustUseReturnValue to copy-over methods

* Make serialization log calls use the same sawmill

* gamin

* Fix doc errors in ISerializationManager.cs

* goodbye brave soldier

* fixes

* WIP merge fixes and entity serialization

* aaaaaaaaaaaaaaa

* aaaaaaaaaaaaaaa

* adds inheritancebehaviour

* test/datafield fixes

* forgot that one

* adds more verbose validation

* This fixes the YAML hot reloading

* Replace yield break with Enumerable.Empty

* adds copiers

* aaaaaaaaaaaaa

* array fix
priority fix
misc fixes

* fix(?)

* fix.

* funny map serialization (wip)

* funny map serialization (wip)

* Add TODO

* adds proper info the validation

* Make yaml linter 5 times faster (~80% less execution time)

* Improves the error message for missing fields in the linter

* Include component name in unknown component type error node

* adds alwaysrelevant usa

* fixes mapsaving

* moved surpressor to analyzers proj

* warning cleanup & moves surpressor

* removes old msbuild targets

* Revert "Make yaml linter 5 times faster (~80% less execution time)"

This reverts commit 2ee4cc2c26.

* Add serialization to RobustServerSimulation and mock reflection methods
Fixes container tests

* Fix nullability warnings

* Improve yaml linter message feedback

* oops moment

* Add IEquatable, IComparable, ToString and operators to DataPosition
Rename it to NodeMark
Make it a readonly struct

* Remove try catch from enum parsing

* Make dependency management in serialization less bad

* Make dependencies an argument instead of a property on the serialization manager

* Clean up type serializers

* Improve validation messages and resourc epath checking

* Fix sprite error message

* reached perfection

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
Co-authored-by: Vera Aguilera Puerto <zddm@outlook.es>
2021-03-04 15:59:14 -08:00
Pieter-Jan Briers
93018c9843 Silence localization warnings on client again. 2021-03-03 16:02:30 +01:00
Pieter-Jan Briers
e2675271d0 Parallelize assembly sandbox checking harder. 2021-03-03 16:02:12 +01:00
Pieter-Jan Briers
d1f7edecef Use Directory.EnumerateFiles in PathHelpers.GetFiles.
Significant improvement in startup time.
2021-03-03 10:52:05 +01:00
Pieter-Jan Briers
b5a3c0b988 Do not load files under Locale/ not ending with .ftl.
Will ignore stuff like .DS_Store/.directory/thumbs.db
2021-03-02 21:22:44 +01:00
Acruid
06e62b031a SoundSystem (#1604)
* Adds the SoundSystem static proxy class for the AudioSystem.
Added a shared IAudioSystem interface for the future.

* Moved ConnectedClient property from IPlayerSession down to ICommonSession.

* Connected up the SoundSystem to the client/server AudioSystems.

* Converted client calls over to the new system.

* Marked the old serverside functions to play sound obsolete, use the new ones from the IAudioSystem.

* Added ISharedPlayerManager to the IoC registration.
2021-03-01 20:22:28 -08:00
Acruid
24707b7385 Shared Containers (#1579)
* Added a basic server simulation framework for help with tests.

* Moved as much as possible to Robust.Shared/Containers.
Moved ContainerSlot from content to engine.

* Moved ClientContainer to shared.

* Merged client/server ContainerManagerComponents into a single shared version.

* ContainerManagerComponent is now implicitly registered with the attributes.

* Migrated to 2021 serialization technology.

* Existing Unit Tests work.

* More tests coverage.
Fixed bug with transferring items between containers.

* Container Type info is now sent over the network.

* Merge client/server container systems.

* Code cleanup.

* Attempted to fix dictionary serialization.
Logs warning when trying to check if an unknown GridId is paused.

* Remove OldCode.
2021-03-01 15:19:59 -08:00
Pieter-Jan Briers
ab95f39f9f Localize SS14Window 2021-03-01 00:45:36 +01:00
Pieter-Jan Briers
cdd38abab5 Fix two shutdown crashes by removing IDisposable managers. 2021-02-28 23:10:03 +01:00
Pieter-Jan Briers
d751c0b3ab Revert "Physics (#1602)"
This reverts commit fefcc7cba3.
2021-02-28 18:45:18 +01:00
Pieter-Jan Briers
2ace0e9e5a Expose Patreon tier info from auth server. 2021-02-28 18:45:01 +01:00
Pieter-Jan Briers
31716f5104 Work around Roslyn scripting bug with ref structs. 2021-02-28 18:45:01 +01:00
593 changed files with 22867 additions and 13151 deletions

View File

@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>

2
Avalonia.Base/README.md Normal file
View File

@@ -0,0 +1,2 @@
See `Robust.Client/UserInterface/XAML/RiderNotes.md` for why this project exists.
We are not actually using Avalonia (yet).

View File

@@ -4,5 +4,6 @@
<TargetFramework>net5.0</TargetFramework>
<LangVersion>9</LangVersion>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
</PropertyGroup>
</Project>

View File

@@ -14,12 +14,6 @@
<PackageReference Condition="'$(TargetFramework)' == 'net472'" Include="System.Memory" Version="4.5.3" />
</ItemGroup>
<ItemGroup>
<Content Include="OpenToolkit.GraphicsLibraryFramework.dll.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
</ItemGroup>

View File

@@ -1,5 +0,0 @@
<configuration>
<!-- I actually have no idea whether this works on FreeBSD but it can't hurt to set it as such. -->
<dllmap os="linux,freebsd" dll="glfw3.dll" target="glfw.so.3" />
<dllmap os="osx" dll="glfw3.dll" target="glfw.3.dylib" />
</configuration>

View File

@@ -1,25 +1,16 @@
![Robust Toolbox](https://raw.githubusercontent.com/space-wizards/asset-dump/3dd3078e49e3a7e06709a6e0fc6e3223d8d44ca2/robust.png)
[![Build status](https://ci.appveyor.com/api/projects/status/ygb7t8hsj3wt7pnm/branch/master?svg=true)](https://ci.appveyor.com/project/Silvertorch5/space-station-14/branch/master)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=ss14&metric=coverage)](https://sonarcloud.io/dashboard?id=ss14)
Robust Toolbox is an engine primarily being developed for [Space Station 14](https://github.com/space-wizards/space-station-14), although we're working on making it usable for both [singleplayer](https://github.com/space-wizards/RobustToolboxTemplateSingleplayer) and [multiplayer](https://github.com/space-wizards/RobustToolboxTemplate) projects.
Robust Toolbox is a client/server backend for [Space Station 14](https://github.com/space-wizards/space-station-14).
Use the [content repo](https://github.com/space-wizards/space-station-14) for actual development, even if you're modifying the engine itself.
### *This repository is the *engine* section of SS14. This is the base engine all SS14 servers will be built on. As such, it does not start on its own: it needs the [content repo](https://github.com/space-wizards/space-station-14). Use said repo for actual development, even if you're modifying the engine itself. Think of Robust Toolbox as BYOND in the context of Spacestation 13.*
## Project Links
## Getting in Touch
[Website](https://spacestation14.io/) | [Discord](https://discord.gg/t2jac3p) | [Forum](https://forum.spacestation14.io/) | [Steam](https://store.steampowered.com/app/1255460/Space_Station_14/) | [Standalone Download](https://spacestation14.io/about/nightlies/)
* Website: [spacestation14.io](https://spacestation14.io/)
* Discord: [Invite Link](https://discord.gg/t2jac3p)
* IRC: `irc.rizon.net#spacebus`
* Code Analysis: [Sonar Cloud](https://sonarcloud.io/dashboard?id=ss14)
* Automatic Content Builds: [builds.spacestation14.io](https://builds.spacestation14.io/jenkins/)
## Documentation/Wiki
The IRC is setup to relay back and forth to the Discord server so [IRC nerds](https://xkcd.com/1782/) will not be left out.
## Documentation
We have various technical documentation articles on the [HackMD Wiki](https://hackmd.io/@ss14/docs/%2F%40ss14%2Ftechnical-documentation-overview).
The [HackMD Wiki](https://hackmd.io/@ss14/docs/wiki) has documentation on SS14s content, engine, game design and more. We also have lots of resources for new contributors to the project.
## Contributing
@@ -27,8 +18,8 @@ We are happy to accept contributions from anybody. Get in Discord or IRC if you
## Building
**In practice, you usually don't build this repository directly.**
This repository is the **engine** part of SS14. It's the base engine all SS14 servers will be built on. As such, it does not start on its own: it needs the [content repo](https://github.com/space-wizards/space-station-14). Think of Robust Toolbox as BYOND in the context of Spacestation 13.
## Legal Info
See `legal.md` for licenses and copyright.
See [legal.md](https://github.com/space-wizards/RobustToolbox/blob/master/legal.md) for licenses and copyright.

View File

@@ -0,0 +1 @@
ss14window-placeholder-title = Exemplary Window Title Here

View File

@@ -1,40 +0,0 @@
- type: entity
id: debugRotation1
name: dbg_rotation1
components:
- type: Clickable
- type: InteractionOutline
- type: Sprite
netsync: false
visible: true
sprite: debugRotation.rsi
state: direction1
placement:
mode: AlignTileAny
- type: entity
id: debugRotation4
name: dbg_rotation4
components:
- type: Clickable
- type: InteractionOutline
- type: Sprite
netsync: false
visible: true
sprite: debugRotation.rsi
state: direction4
placement:
mode: AlignTileAny
- type: entity
id: debugRotationTex
name: dbg_rotationTex
components:
- type: Clickable
- type: InteractionOutline
- type: Sprite
netsync: false
visible: true
texture: debugRotation.rsi/direction1.png
placement:
mode: AlignTileAny

View File

@@ -1,4 +0,0 @@
- type: entity
name: blank entity
id: BlankEntity
abstract: true

View File

@@ -28,5 +28,10 @@ void fragment()
highp float occlusion = ChebyshevUpperBound(occlDist, ourDist);
if (occlusion >= 1.0)
{
discard;
}
COLOR = vec4(0.0, 0.0, 0.0, 1.0 - occlusion);
}

View File

@@ -1,5 +1,11 @@
#include "/Shaders/Internal/light_shared.swsl"
highp vec4 calcGaussianWeights(highp float sigma, highp vec4 offset)
{
highp vec4 eExp = offset * offset / (2.0 * sigma * sigma);
return exp(-eExp) / (sigma * sqrt(2.0 * PI));
}
highp float createOcclusion(highp vec2 diff)
{
// Calculate vector perpendicular to light vector.
@@ -8,23 +14,57 @@ highp float createOcclusion(highp vec2 diff)
highp float ourDist = length(diff);
highp vec2 occlDist = occludeDepth(diff, shadowMap, lightIndex);
// Sample 7 points on a line perpendicular to the light source.
// Depending on the closest point, we change the gaussian weights down below
// to change the "narrowness" of the samples.
perpendicular *= lightSoftness * 1.5;
// Get all the samples we need.
highp vec2 sample1 = occludeDepth(diff, shadowMap, lightIndex);
highp vec2 sample2 = occludeDepth(diff + perpendicular, shadowMap, lightIndex);
highp vec2 sample3 = occludeDepth(diff - perpendicular, shadowMap, lightIndex);
highp vec2 sample4 = occludeDepth(diff + perpendicular * 2.0, shadowMap, lightIndex);
highp vec2 sample5 = occludeDepth(diff - perpendicular * 2.0, shadowMap, lightIndex);
highp vec2 sample6 = occludeDepth(diff + perpendicular * 3.0, shadowMap, lightIndex);
highp vec2 sample7 = occludeDepth(diff - perpendicular * 3.0, shadowMap, lightIndex);
highp float mindist =
min(sample1.x,
min(sample2.x,
min(sample3.x,
min(sample4.x,
min(sample5.x,
min(sample6.x,
sample7.x))))));
mindist = max(0.001, mindist);
// Change soft shadow size based on distance from primary occluder.
highp float distRatio = (ourDist - occlDist.x) / occlDist.x / 2.0;
highp float distRatio = (ourDist - mindist);
perpendicular *= distRatio * lightSoftness;
// Sigma can never be zero so make sure to clamp.
// TODO: Scaling the dist ratio here in a more sane way might make shadows look better buuuut I'm lazy.
// Shadows look pretty nice already.
highp float sigma = max(0.001, distRatio * 0.75);
highp vec4 weights = calcGaussianWeights(sigma, vec4(0.0, 1.0, 2.0, 3.0));
// Totally not hacky PCF on top of VSM.
highp float occlusion = smoothstep(0.1, 1.0, ChebyshevUpperBound(occlDist, ourDist));
// Calculation of gaussian weights here is broken because it doesn't add up to 1.
// Fixing this is hard and if I had to guess too expensive for GPU shaders.
// So instead we add up the total weights and scale the result with that,
// so that we still end up with 0-1.
highp float totalWeigths = weights.x + weights.y * 2.0 + weights.z * 2.0 + weights.w * 2.0;
occlusion += shadowContrib(diff + perpendicular);
occlusion += shadowContrib(diff - perpendicular);
occlusion += shadowContrib(diff + perpendicular * 2.0);
occlusion += shadowContrib(diff - perpendicular * 2.0);
occlusion += shadowContrib(diff + perpendicular * 3.0);
occlusion += shadowContrib(diff - perpendicular * 3.0);
highp float occlusion = 0.0;
return occlusion / 7.0;
// Calculate actual occlusion with new weights.
occlusion += ChebyshevUpperBound(sample1, ourDist) * weights.x;
occlusion += ChebyshevUpperBound(sample2, ourDist) * weights.y;
occlusion += ChebyshevUpperBound(sample3, ourDist) * weights.y;
occlusion += ChebyshevUpperBound(sample4, ourDist) * weights.z;
occlusion += ChebyshevUpperBound(sample5, ourDist) * weights.z;
occlusion += ChebyshevUpperBound(sample6, ourDist) * weights.w;
occlusion += ChebyshevUpperBound(sample7, ourDist) * weights.w;
return occlusion / totalWeigths;
}

View File

@@ -0,0 +1,10 @@
using Microsoft.CodeAnalysis;
namespace Robust.Generators
{
public static class Diagnostics
{
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Generators;
namespace Robust.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class MeansImplicitAssigmentSuppressor : DiagnosticSuppressor
{
const string MeansImplicitAssignmentAttribute = "Robust.Shared.MeansImplicitAssignmentAttribute";
public override void ReportSuppressions(SuppressionAnalysisContext context)
{
var implAttr = context.Compilation.GetTypeByMetadataName(MeansImplicitAssignmentAttribute);
foreach (var reportedDiagnostic in context.ReportedDiagnostics)
{
if(reportedDiagnostic.Id != Diagnostics.MeansImplicitAssignment.SuppressedDiagnosticId) continue;
var node = reportedDiagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(reportedDiagnostic.Location.SourceSpan);
if (node == null) continue;
var symbol = context.GetSemanticModel(reportedDiagnostic.Location.SourceTree).GetDeclaredSymbol(node);
if (symbol == null || !symbol.GetAttributes().Any(a =>
a.AttributeClass?.GetAttributes().Any(attr =>
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, implAttr)) == true))
{
continue;
}
context.ReportSuppression(Suppression.Create(
Diagnostics.MeansImplicitAssignment,
reportedDiagnostic));
}
}
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(Diagnostics.MeansImplicitAssignment);
}
}

View File

@@ -0,0 +1,12 @@
using BenchmarkDotNet.Running;
namespace Robust.Benchmarks
{
internal class Program
{
public static void Main(string[] args)
{
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run();
}
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\MSBuild\Robust.Properties.targets" />
<Import Project="..\MSBuild\Robust.Engine.props" />
<PropertyGroup>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>../bin/Benchmarks</OutputPath>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Robust.Server\Robust.Server.csproj" />
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
</ItemGroup>
<Import Project="..\MSBuild\Robust.Engine.targets" />
</Project>

View File

@@ -0,0 +1,11 @@
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Benchmarks.Serialization
{
[DataDefinition]
public class DataDefinitionWithString
{
[field: DataField("string")]
private string StringField { get; } = default!;
}
}

View File

@@ -0,0 +1,21 @@
using BenchmarkDotNet.Attributes;
using Robust.Shared.Serialization.Manager;
namespace Robust.Benchmarks.Serialization.Initialize
{
public class SerializationInitializeBenchmark : SerializationBenchmark
{
[IterationCleanup]
public void IterationCleanup()
{
SerializationManager.Shutdown();
}
[Benchmark]
public ISerializationManager Initialize()
{
InitializeSerialization();
return SerializationManager;
}
}
}

View File

@@ -0,0 +1,55 @@
using System.IO;
using BenchmarkDotNet.Attributes;
using Robust.Shared.Serialization.Markdown;
using YamlDotNet.RepresentationModel;
namespace Robust.Benchmarks.Serialization.Read
{
public class SerializationReadBenchmark : SerializationBenchmark
{
public SerializationReadBenchmark()
{
InitializeSerialization();
StringDataDefNode = new MappingDataNode();
StringDataDefNode.AddNode(new ValueDataNode("string"), new ValueDataNode("ABC"));
var yamlStream = new YamlStream();
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));
SeedNode = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
}
private ValueDataNode StringNode { get; } = new("ABC");
private ValueDataNode IntNode { get; } = new("1");
private MappingDataNode StringDataDefNode { get; }
private MappingDataNode SeedNode { get; }
[Benchmark]
public string? ReadString()
{
return SerializationManager.ReadValue<string>(StringNode);
}
[Benchmark]
public int? ReadInteger()
{
return SerializationManager.ReadValue<int>(IntNode);
}
[Benchmark]
public DataDefinitionWithString? ReadDataDefinitionWithString()
{
return SerializationManager.ReadValue<DataDefinitionWithString>(StringDataDefNode);
}
[Benchmark]
public SeedDataDefinition? ReadSeedDataDefinition()
{
return SerializationManager.ReadValue<SeedDataDefinition>(SeedNode);
}
}
}

View File

@@ -0,0 +1,184 @@
using System.Collections.Generic;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Benchmarks.Serialization
{
[Prototype("seed")]
public class SeedDataDefinition : IPrototype
{
public const string Prototype = @"
- type: seed
id: tobacco
name: tobacco
seedName: tobacco
displayName: tobacco plant
productPrototypes:
- LeavesTobacco
harvestRepeat: Repeat
lifespan: 75
maturation: 5
production: 5
yield: 2
potency: 20
growthStages: 3
idealLight: 9
idealHeat: 298
chemicals:
chem.Nicotine:
Min: 1
Max: 10
PotencyDivisor: 10";
private const string SeedPrototype = "SeedBase";
[ViewVariables]
[field: DataField("id", required: true)]
public string ID { get; private init; } = default!;
/// <summary>
/// Unique identifier of this seed. Do NOT set this.
/// </summary>
public int Uid { get; internal set; } = -1;
#region Tracking
[ViewVariables] [DataField("name")] public string Name { get; set; } = string.Empty;
[ViewVariables] [DataField("seedName")] public string SeedName { get; set; } = string.Empty;
[ViewVariables]
[DataField("seedNoun")]
public string SeedNoun { get; set; } = "seeds";
[ViewVariables] [DataField("displayName")] public string DisplayName { get; set; } = string.Empty;
[ViewVariables]
[DataField("roundStart")]
public bool RoundStart { get; private set; } = true;
[ViewVariables] [DataField("mysterious")] public bool Mysterious { get; set; }
[ViewVariables] [DataField("immutable")] public bool Immutable { get; set; }
#endregion
#region Output
[ViewVariables]
[DataField("productPrototypes")]
public List<string> ProductPrototypes { get; set; } = new();
[ViewVariables]
[DataField("chemicals")]
public Dictionary<string, SeedChemQuantity> Chemicals { get; set; } = new();
[ViewVariables]
[DataField("consumeGasses")]
public Dictionary<Gas, float> ConsumeGasses { get; set; } = new();
[ViewVariables]
[DataField("exudeGasses")]
public Dictionary<Gas, float> ExudeGasses { get; set; } = new();
#endregion
#region Tolerances
[ViewVariables]
[DataField("nutrientConsumption")]
public float NutrientConsumption { get; set; } = 0.25f;
[ViewVariables] [DataField("waterConsumption")] public float WaterConsumption { get; set; } = 3f;
[ViewVariables] [DataField("idealHeat")] public float IdealHeat { get; set; } = 293f;
[ViewVariables] [DataField("heatTolerance")] public float HeatTolerance { get; set; } = 20f;
[ViewVariables] [DataField("idealLight")] public float IdealLight { get; set; } = 7f;
[ViewVariables] [DataField("lightTolerance")] public float LightTolerance { get; set; } = 5f;
[ViewVariables] [DataField("toxinsTolerance")] public float ToxinsTolerance { get; set; } = 4f;
[ViewVariables]
[DataField("lowPressureTolerance")]
public float LowPressureTolerance { get; set; } = 25f;
[ViewVariables]
[DataField("highPressureTolerance")]
public float HighPressureTolerance { get; set; } = 200f;
[ViewVariables]
[DataField("pestTolerance")]
public float PestTolerance { get; set; } = 5f;
[ViewVariables]
[DataField("weedTolerance")]
public float WeedTolerance { get; set; } = 5f;
#endregion
#region General traits
[ViewVariables]
[DataField("endurance")]
public float Endurance { get; set; } = 100f;
[ViewVariables] [DataField("yield")] public int Yield { get; set; }
[ViewVariables] [DataField("lifespan")] public float Lifespan { get; set; }
[ViewVariables] [DataField("maturation")] public float Maturation { get; set; }
[ViewVariables] [DataField("production")] public float Production { get; set; }
[ViewVariables] [DataField("growthStages")] public int GrowthStages { get; set; } = 6;
[ViewVariables] [DataField("harvestRepeat")] public HarvestType HarvestRepeat { get; set; } = HarvestType.NoRepeat;
[ViewVariables] [DataField("potency")] public float Potency { get; set; } = 1f;
// No, I'm not removing these.
//public PlantSpread Spread { get; set; }
//public PlantMutation Mutation { get; set; }
//public float AlterTemperature { get; set; }
//public PlantCarnivorous Carnivorous { get; set; }
//public bool Parasite { get; set; }
//public bool Hematophage { get; set; }
//public bool Thorny { get; set; }
//public bool Stinging { get; set; }
[DataField("ligneous")]
public bool Ligneous { get; set; }
// public bool Teleporting { get; set; }
// public PlantJuicy Juicy { get; set; }
#endregion
#region Cosmetics
[ViewVariables]
[DataField("plantRsi", required: true)]
public ResourcePath PlantRsi { get; set; } = default!;
[ViewVariables]
[DataField("plantIconState")]
public string PlantIconState { get; set; } = "produce";
[ViewVariables]
[DataField("bioluminescent")]
public bool Bioluminescent { get; set; }
[ViewVariables]
[DataField("bioluminescentColor")]
public Color BioluminescentColor { get; set; } = Color.White;
[ViewVariables]
[DataField("splatPrototype")]
public string? SplatPrototype { get; set; }
#endregion
}
public enum HarvestType
{
NoRepeat,
Repeat
}
public enum Gas {}
[DataDefinition]
public struct SeedChemQuantity
{
[DataField("Min")]
public int Min;
[DataField("Max")]
public int Max;
[DataField("PotencyDivisor")]
public int PotencyDivisor;
}
}

View File

@@ -0,0 +1,43 @@
using System;
using Robust.Server;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.Manager;
namespace Robust.Benchmarks.Serialization
{
public abstract class SerializationBenchmark
{
public SerializationBenchmark()
{
IoCManager.InitThread();
ServerIoC.RegisterIoC();
IoCManager.BuildGraph();
var assemblies = new[]
{
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Shared"),
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Server"),
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Benchmarks")
};
foreach (var assembly in assemblies)
{
IoCManager.Resolve<IConfigurationManagerInternal>().LoadCVarsFromAssembly(assembly);
}
IoCManager.Resolve<IReflectionManager>().LoadAssemblies(assemblies);
SerializationManager = IoCManager.Resolve<ISerializationManager>();
}
protected ISerializationManager SerializationManager { get; }
public void InitializeSerialization()
{
SerializationManager.Initialize();
}
}
}

View File

@@ -13,7 +13,7 @@ namespace Robust.Client.Animations
/// <seealso cref="AnimationPlayerComponent"/>
public sealed class Animation
{
public readonly List<AnimationTrack> AnimationTracks = new();
public List<AnimationTrack> AnimationTracks { get; private set; } = new();
public TimeSpan Length { get; set; }
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Robust.Client.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Player;
namespace Robust.Client.Animations
{
@@ -14,7 +15,7 @@ namespace Robust.Client.Animations
/// <summary>
/// A list of key frames for when to fire flicks.
/// </summary>
public readonly List<KeyFrame> KeyFrames = new();
public List<KeyFrame> KeyFrames { get; private set; } = new();
public override (int KeyFrameIndex, float FramePlayingTime) InitPlayback()
{
@@ -36,8 +37,7 @@ namespace Robust.Client.Animations
var keyFrame = KeyFrames[keyFrameIndex];
EntitySystem.Get<AudioSystem>()
.Play(keyFrame.Resource, entity, keyFrame.AudioParamsFunc.Invoke());
SoundSystem.Play(Filter.Local(), keyFrame.Resource, entity, keyFrame.AudioParamsFunc.Invoke());
}
return (keyFrameIndex, playingTime);

View File

@@ -10,7 +10,7 @@ namespace Robust.Client.Animations
/// </summary>
public abstract class AnimationTrackProperty : AnimationTrack
{
public readonly List<KeyFrame> KeyFrames = new();
public List<KeyFrame> KeyFrames { get; protected set; } = new();
/// <summary>
/// How to interpolate values when between two keyframes.

View File

@@ -15,7 +15,7 @@ namespace Robust.Client.Animations
/// <summary>
/// A list of key frames for when to fire flicks.
/// </summary>
public readonly List<KeyFrame> KeyFrames = new();
public List<KeyFrame> KeyFrames { get; private set; } = new();
// TODO: Should this layer key be per keyframe maybe?
/// <summary>

View File

@@ -11,7 +11,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Utility;
@@ -64,9 +63,11 @@ namespace Robust.Client.Audio.Midi
bool IsAvailable { get; }
public int OcclusionCollisionMask { get; set; }
void Shutdown();
}
internal class MidiManager : IDisposable, IMidiManager
internal class MidiManager : IMidiManager
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
@@ -319,6 +320,11 @@ namespace Robust.Client.Audio.Midi
continue;
}
if (renderer.TrackingEntity != null)
{
renderer.Source.SetVelocity(renderer.TrackingEntity.GlobalLinearVelocity());
}
if (float.IsNaN(pos.Position.X) || float.IsNaN(pos.Position.Y))
{
// just duck out instead of move to NaN
@@ -356,7 +362,7 @@ namespace Robust.Client.Audio.Midi
}
}
public void Dispose()
public void Shutdown()
{
_alive = false;
_midiThread?.Join();

View File

@@ -8,6 +8,7 @@ using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
@@ -96,6 +97,25 @@ namespace Robust.Client
_net.ClientDisconnect(reason);
}
/// <inheritdoc />
public void StartSinglePlayer()
{
DebugTools.Assert(RunLevel < ClientRunLevel.Connecting);
DebugTools.Assert(!_net.IsConnected);
_playMan.Startup();
_playMan.LocalPlayer!.Name = PlayerNameOverride ?? _configManager.GetCVar(CVars.PlayerName);
OnRunLevelChanged(ClientRunLevel.SinglePlayerGame);
GameStartedSetup();
}
/// <inheritdoc />
public void StopSinglePlayer()
{
DebugTools.Assert(RunLevel == ClientRunLevel.SinglePlayerGame);
DebugTools.Assert(!_net.IsConnected);
GameStoppedReset();
}
/// <inheritdoc />
public event EventHandler<RunLevelChangedEventArgs>? RunLevelChanged;
@@ -132,7 +152,7 @@ namespace Robust.Client
var userId = _net.ServerChannel.UserId;
_discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString());
// start up player management
_playMan.Startup(_net.ServerChannel!);
_playMan.Startup();
_playMan.LocalPlayer!.UserId = userId;
_playMan.LocalPlayer.Name = userName;
@@ -150,12 +170,18 @@ namespace Robust.Client
DebugTools.Assert(RunLevel < ClientRunLevel.Connected);
OnRunLevelChanged(ClientRunLevel.Connected);
GameStartedSetup();
PlayerJoinedServer?.Invoke(this, new PlayerEventArgs(session));
}
private void GameStartedSetup()
{
_entityManager.Startup();
_mapManager.Startup();
_timing.ResetSimTime();
_timing.Paused = false;
PlayerJoinedServer?.Invoke(this, new PlayerEventArgs(session));
}
/// <summary>
@@ -189,10 +215,15 @@ namespace Robust.Client
PlayerLeaveServer?.Invoke(this, new PlayerEventArgs(_playMan.LocalPlayer?.Session));
LastDisconnectReason = args.Reason;
GameStoppedReset();
}
private void GameStoppedReset()
{
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
_gameStates.Reset();
_playMan.Shutdown();
IoCManager.Resolve<IEntityLookup>().Shutdown();
_entityManager.Shutdown();
_mapManager.Shutdown();
_discord.ClearPresence();
@@ -249,6 +280,11 @@ namespace Robust.Client
/// The client is now in the game, moving around.
/// </summary>
InGame,
/// <summary>
/// The client is now in singleplayer mode, in-game.
/// </summary>
SinglePlayerGame,
}
/// <summary>

View File

@@ -14,6 +14,7 @@ using Robust.Client.Prototypes;
using Robust.Client.Reflection;
using Robust.Client.ResourceManagement;
using Robust.Client.State;
using Robust.Client.Timing;
using Robust.Client.UserInterface;
using Robust.Client.Utility;
using Robust.Client.ViewVariables;
@@ -24,8 +25,10 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Timing;
namespace Robust.Client
{
@@ -35,8 +38,14 @@ namespace Robust.Client
{
SharedIoC.RegisterIoC();
IoCManager.Register<IGameTiming, ClientGameTiming>();
IoCManager.Register<IClientGameTiming, ClientGameTiming>();
IoCManager.Register<IPrototypeManager, ClientPrototypeManager>();
IoCManager.Register<IMapManager, ClientMapManager>();
IoCManager.Register<IMapManagerInternal, ClientMapManager>();
IoCManager.Register<IClientMapManager, ClientMapManager>();
IoCManager.Register<IEntityManager, ClientEntityManager>();
IoCManager.Register<IEntityLookup, SharedEntityLookup>();
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
IoCManager.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();
@@ -49,10 +58,12 @@ namespace Robust.Client
IoCManager.Register<IResourceCacheInternal, ResourceCache>();
IoCManager.Register<IClientNetManager, NetManager>();
IoCManager.Register<IClientEntityManager, ClientEntityManager>();
IoCManager.Register<IEntityNetworkManager, ClientEntityNetworkManager>();
IoCManager.Register<IClientEntityManagerInternal, ClientEntityManager>();
IoCManager.Register<IEntityNetworkManager, ClientEntityManager>();
IoCManager.Register<IClientGameStateManager, ClientGameStateManager>();
IoCManager.Register<IBaseClient, BaseClient>();
IoCManager.Register<IPlayerManager, PlayerManager>();
IoCManager.Register<ISharedPlayerManager, PlayerManager>();
IoCManager.Register<IStateManager, StateManager>();
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
@@ -62,8 +73,6 @@ namespace Robust.Client
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IFontManager, FontManager>();
IoCManager.Register<IFontManagerInternal, FontManager>();
IoCManager.Register<IMidiManager, MidiManager>();
IoCManager.Register<IAuthManager, AuthManager>();
switch (mode)
@@ -90,8 +99,9 @@ namespace Robust.Client
throw new ArgumentOutOfRangeException();
}
IoCManager.Register<IFontManager, FontManager>();
IoCManager.Register<IFontManagerInternal, FontManager>();
IoCManager.Register<IEyeManager, EyeManager>();
IoCManager.Register<IPlacementManager, PlacementManager>();
IoCManager.Register<IOverlayManager, OverlayManager>();
IoCManager.Register<IOverlayManagerInternal, OverlayManager>();
@@ -99,7 +109,6 @@ namespace Robust.Client
IoCManager.Register<IViewVariablesManagerInternal, ViewVariablesManager>();
IoCManager.Register<IClientConGroupController, ClientConGroupController>();
IoCManager.Register<IScriptClient, ScriptClient>();
//IoCManager.Register<IXamlCompiler, XamlCompiler>();
}
}
}

View File

@@ -12,7 +12,6 @@ using Robust.Client.Input;
using Robust.Client.Debugging;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.ResourceManagement.ResourceTypes;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
@@ -281,12 +280,12 @@ namespace Robust.Client.Console.Commands
internal class SnapGridGetCell : IConsoleCommand
{
public string Command => "sggcell";
public string Help => "sggcell <gridID> <vector2i> [offset]\nThat vector2i param is in the form x<int>,y<int>.";
public string Help => "sggcell <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.";
public string Description => "Lists entities on a snap grid cell.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2 && args.Length != 3)
if (args.Length != 2)
{
shell.WriteLine(Help);
return;
@@ -294,7 +293,6 @@ namespace Robust.Client.Console.Commands
string gridId = args[0];
string indices = args[1];
string offset = args.Length == 3 ? args[2] : "Center";
if (!int.TryParse(args[0], out var id))
{
@@ -308,29 +306,17 @@ namespace Robust.Client.Console.Commands
return;
}
SnapGridOffset selectedOffset;
if (Enum.IsDefined(typeof(SnapGridOffset), offset))
{
selectedOffset = (SnapGridOffset)Enum.Parse(typeof(SnapGridOffset), offset);
}
else
{
shell.WriteError("given offset type is not defined");
return;
}
var mapMan = IoCManager.Resolve<IMapManager>();
if (mapMan.GridExists(new GridId(int.Parse(gridId, CultureInfo.InvariantCulture))))
{
foreach (var entity in
mapMan.GetGrid(new GridId(int.Parse(gridId, CultureInfo.InvariantCulture))).GetSnapGridCell(
mapMan.GetGrid(new GridId(int.Parse(gridId, CultureInfo.InvariantCulture))).GetAnchoredEntities(
new Vector2i(
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
int.Parse(indices.Split(',')[1], CultureInfo.InvariantCulture)),
selectedOffset))
int.Parse(indices.Split(',')[1], CultureInfo.InvariantCulture))))
{
shell.WriteLine(entity.Owner.Uid.ToString());
shell.WriteLine(entity.ToString());
}
}
else

View File

@@ -0,0 +1,44 @@
using System.Linq;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.IoC;
namespace Robust.Client.Console.Commands
{
[UsedImplicitly]
public sealed class LsMonitorCommand : IConsoleCommand
{
public string Command => "lsmonitor";
public string Description => "";
public string Help => "";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var clyde = IoCManager.Resolve<IClyde>();
foreach (var monitor in clyde.EnumerateMonitors())
{
shell.WriteLine(
$"[{monitor.Id}] {monitor.Name}: {monitor.Size.X}x{monitor.Size.Y}@{monitor.RefreshRate}Hz");
}
}
}
[UsedImplicitly]
public sealed class SetMonitorCommand : IConsoleCommand
{
public string Command => "setmonitor";
public string Description => "";
public string Help => "Usage: setmonitor <id>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var clyde = IoCManager.Resolve<IClyde>();
var id = int.Parse(args[0]);
var monitor = clyde.EnumerateMonitors().Single(m => m.Id == id);
clyde.SetWindowMonitor(monitor);
}
}
}

View File

@@ -1,4 +1,5 @@
#if CLIENT_SCRIPTING
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -172,7 +173,7 @@ namespace Robust.Client.Console
else if (ScriptInstanceShared.HasReturnValue(newScript))
{
var msg = new FormattedMessage();
msg.AddText(CSharpObjectFormatter.Instance.FormatObject(_state.ReturnValue));
msg.AddText(ScriptInstanceShared.SafeFormat(_state.ReturnValue));
OutputPanel.AddMessage(msg);
}
@@ -189,7 +190,6 @@ namespace Robust.Client.Console
_autoImportRepeatBuffer = (found.ToArray(), code);
}
private sealed class ScriptGlobalsImpl : ScriptGlobals
{
private readonly ScriptConsoleClient _owner;
@@ -215,7 +215,7 @@ namespace Robust.Client.Console
public override void show(object obj)
{
write(CSharpObjectFormatter.Instance.FormatObject(obj));
write(ScriptInstanceShared.SafeFormat(obj));
}
}
}

View File

@@ -5,10 +5,15 @@ namespace Robust.Client
public static void Start(string[] args)
{
#if FULL_RELEASE
throw new System.InvalidOperationException("ContentStart is not available on a full release.");
throw new System.InvalidOperationException("ContentStart.Start is not available on a full release.");
#else
GameController.Start(args, true);
#endif
}
public static void StartLibrary(string[] args, GameControllerOptions options)
{
GameController.Start(args, true, null, options);
}
}
}

View File

@@ -1,15 +1,18 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Physics.Dynamics.Joints;
using Robust.Shared.Prototypes;
namespace Robust.Client.Debugging
@@ -18,11 +21,10 @@ namespace Robust.Client.Debugging
public class DebugDrawing : IDebugDrawing
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IComponentManager _componentManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
private bool _debugColliders;
@@ -41,14 +43,14 @@ namespace Robust.Client.Debugging
_debugColliders = value;
if (value)
if (value && !_overlayManager.HasOverlay<PhysicsOverlay>())
{
_overlayManager.AddOverlay(new PhysicsOverlay(_componentManager, _eyeManager,
_prototypeManager, _inputManager, _physicsManager));
_overlayManager.AddOverlay(new PhysicsOverlay(_eyeManager,
_prototypeManager, _inputManager, _mapManager));
}
else
{
_overlayManager.RemoveOverlay(nameof(PhysicsOverlay));
_overlayManager.RemoveOverlay<PhysicsOverlay>();
}
}
}
@@ -66,23 +68,22 @@ namespace Robust.Client.Debugging
_debugPositions = value;
if (value)
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
{
_overlayManager.AddOverlay(new EntityPositionOverlay(_entityManager, _eyeManager));
}
else
{
_overlayManager.RemoveOverlay(nameof(EntityPositionOverlay));
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
}
}
}
private class PhysicsOverlay : Overlay
{
private readonly IComponentManager _componentManager;
private readonly IEyeManager _eyeManager;
private readonly IMapManager _mapManager;
private readonly IInputManager _inputManager;
private readonly IPhysicsManager _physicsManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
private readonly ShaderInstance _shader;
@@ -91,13 +92,12 @@ namespace Robust.Client.Debugging
private Vector2 _hoverStartScreen = Vector2.Zero;
private List<IPhysBody> _hoverBodies = new();
public PhysicsOverlay(IComponentManager compMan, IEyeManager eyeMan, IPrototypeManager protoMan, IInputManager inputManager, IPhysicsManager physicsManager)
: base(nameof(PhysicsOverlay))
public PhysicsOverlay(IEyeManager eyeMan, IPrototypeManager protoMan, IInputManager inputManager, IMapManager mapManager)
{
_componentManager = compMan;
_eyeManager = eyeMan;
_inputManager = inputManager;
_physicsManager = physicsManager;
_mapManager = mapManager;
_shader = protoMan.Index<ShaderPrototype>("unshaded").Instance();
var cache = IoCManager.Resolve<IResourceCache>();
@@ -105,22 +105,23 @@ namespace Robust.Client.Debugging
}
/// <inheritdoc />
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
protected internal override void Draw(in OverlayDrawArgs args)
{
switch (currentSpace)
switch (args.Space)
{
case OverlaySpace.ScreenSpace:
DrawScreen((DrawingHandleScreen) handle);
DrawScreen(args);
break;
case OverlaySpace.WorldSpace:
DrawWorld((DrawingHandleWorld) handle);
DrawWorld(args);
break;
}
}
private void DrawScreen(DrawingHandleScreen screenHandle)
private void DrawScreen(in OverlayDrawArgs args)
{
var screenHandle = args.ScreenHandle;
var lineHeight = _font.GetLineHeight(1f);
Vector2 drawPos = _hoverStartScreen + new Vector2(20, 0) + new Vector2(0, -(_hoverBodies.Count * 4 * lineHeight / 2f));
int row = 0;
@@ -133,7 +134,7 @@ namespace Robust.Client.Debugging
row++;
}
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Entity}");
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
row++;
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
row++;
@@ -145,8 +146,9 @@ namespace Robust.Client.Debugging
}
private void DrawWorld(DrawingHandleWorld worldHandle)
private void DrawWorld(in OverlayDrawArgs args)
{
var worldHandle = args.WorldHandle;
worldHandle.UseShader(_shader);
var drawing = new PhysDrawingAdapter(worldHandle);
@@ -160,23 +162,31 @@ namespace Robust.Client.Debugging
if (viewport.IsEmpty()) return;
var mapId = _eyeManager.CurrentMap;
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
var colorEdge = Color.Red.WithAlpha(0.33f);
var drawnJoints = new HashSet<Joint>();
foreach (var physBody in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(mapId, viewport))
{
// all entities have a TransformComponent
var transform = physBody.Entity.Transform;
var transform = physBody.Owner.Transform;
var worldBox = physBody.GetWorldAABB();
var worldBox = physBody.GetWorldAABB(_mapManager);
if (worldBox.IsEmpty()) continue;
var colorEdge = Color.Red.WithAlpha(0.33f);
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
foreach (var fixture in physBody.Fixtures)
{
var shape = fixture.Shape;
var sleepPercent = physBody.Awake ? physBody.SleepTime / sleepThreshold : 1.0f;
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
}
foreach (var joint in physBody.Joints)
{
if (drawnJoints.Contains(joint)) continue;
drawnJoints.Add(joint);
joint.DebugDraw(drawing, in viewport);
}
if (worldBox.Contains(mouseWorldPos))
@@ -193,9 +203,9 @@ namespace Robust.Client.Debugging
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var chr in str)
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, chr, baseLine, 1, Color.White);
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
baseLine += new Vector2(advance, 0);
}
}
@@ -265,17 +275,17 @@ namespace Robust.Client.Debugging
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager) : base(nameof(EntityPositionOverlay))
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager)
{
_entityManager = entityManager;
_eyeManager = eyeManager;
}
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
protected internal override void Draw(in OverlayDrawArgs args)
{
const float stubLength = 0.25f;
var worldHandle = (DrawingHandleWorld) handle;
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
foreach (var entity in _entityManager.GetEntities())
{
var transform = entity.Transform;

View File

@@ -1,10 +1,11 @@
using Robust.Shared.IoC;
using Robust.Shared.IoC;
using Robust.Shared.Network.Messages;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using Robust.Shared.Network;
namespace Robust.Client.Debugging
@@ -38,13 +39,13 @@ namespace Robust.Client.Debugging
_debugDrawRays = value;
if (value)
if (value && !_overlayManager.HasOverlay<DebugDrawRayOverlay>())
{
_overlayManager.AddOverlay(new DebugDrawRayOverlay(this));
}
else
{
_overlayManager.RemoveOverlay(nameof(DebugDrawRayOverlay));
_overlayManager.RemoveOverlay<DebugDrawRayOverlay>();
}
}
}
@@ -81,13 +82,14 @@ namespace Robust.Client.Debugging
private readonly DebugDrawingManager _owner;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugDrawRayOverlay(DebugDrawingManager owner) : base(nameof(DebugDrawRayOverlay))
public DebugDrawRayOverlay(DebugDrawingManager owner)
{
_owner = owner;
}
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
protected internal override void Draw(in OverlayDrawArgs args)
{
var handle = args.WorldHandle;
foreach (var ray in _owner.raysWithLifeTime)
{
handle.DrawLine(

View File

@@ -1,5 +1,30 @@
/*
* Farseer Physics Engine:
* Copyright (c) 2012 Ian Qvist
*
* Original source Box2D:
* Copyright (c) 2006-2011 Erin Catto http://www.box2d.org
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
/* Heavily inspired by Farseer */
using System;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -7,10 +32,9 @@ using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Dynamics.Contacts;
// TODO: Copy farseer licence here coz it's heavily inspired by it.
namespace Robust.Client.Debugging
{
internal sealed class DebugPhysicsSystem : EntitySystem
internal sealed class DebugPhysicsSystem : SharedDebugPhysicsSystem
{
/*
* Used for debugging shapes, controllers, joints, contacts
@@ -32,7 +56,7 @@ namespace Robust.Client.Debugging
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsDebugOverlay(this));
if (value == PhysicsDebugFlags.None)
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(nameof(PhysicsDebugOverlay));
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsDebugOverlay));
_flags = value;
}
@@ -40,17 +64,8 @@ namespace Robust.Client.Debugging
private PhysicsDebugFlags _flags;
public override void Initialize()
public override void HandlePreSolve(Contact contact, in Manifold oldManifold)
{
base.Initialize();
SubscribeLocalEvent<PreSolveMessage>(HandlePreSolve);
}
private void HandlePreSolve(PreSolveMessage message)
{
Contact contact = message.Contact;
Manifold oldManifold = message.OldManifold;
if ((Flags & PhysicsDebugFlags.ContactPoints) != 0)
{
Manifold manifold = contact.Manifold;
@@ -63,9 +78,9 @@ namespace Robust.Client.Debugging
PointState[] state1, state2;
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
Vector2[] points;
Span<Vector2> points = stackalloc Vector2[2];
Vector2 normal;
contact.GetWorldManifold(out normal, out points);
contact.GetWorldManifold(out normal, points);
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
{
@@ -105,16 +120,16 @@ namespace Robust.Client.Debugging
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PhysicsDebugOverlay(DebugPhysicsSystem system) : base(nameof(PhysicsDebugOverlay))
public PhysicsDebugOverlay(DebugPhysicsSystem system)
{
_physics = system;
}
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
protected internal override void Draw(in OverlayDrawArgs args)
{
if (_physics.Flags == PhysicsDebugFlags.None) return;
var worldHandle = (DrawingHandleWorld) handle;
var worldHandle = args.WorldHandle;
if ((_physics.Flags & PhysicsDebugFlags.Shapes) != 0)
{

View File

@@ -1,7 +1,9 @@
using System;
using System.IO;
using System.Management;
using System.Net;
using System.Threading.Tasks;
using Robust.Client.Audio.Midi;
using Robust.Client.Console;
using Robust.Client.GameObjects;
using Robust.Client.GameStates;
@@ -26,6 +28,7 @@ using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -43,9 +46,10 @@ namespace Robust.Client
[Dependency] private readonly IUserInterfaceManagerInternal _userInterfaceManager = default!;
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly ITimerManager _timerManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IOverlayManagerInternal _overlayManager = default!;
@@ -57,16 +61,18 @@ namespace Robust.Client
[Dependency] private readonly IFontManagerInternal _fontManager = default!;
[Dependency] private readonly IModLoaderInternal _modLoader = default!;
[Dependency] private readonly IScriptClient _scriptClient = default!;
[Dependency] private readonly IComponentManager _componentManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IRobustMappedStringSerializer _stringSerializer = default!;
[Dependency] private readonly IAuthManager _authManager = default!;
[Dependency] private readonly IMidiManager _midiManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
private CommandLineArgs? _commandLineArgs;
private bool _disableAssemblyLoadContext;
// Arguments for loader-load. Not used otherwise.
private IMainArgs? _loaderArgs;
public bool ContentStart { get; set; } = false;
public GameControllerOptions Options { get; private set; } = new();
public InitialLaunchState LaunchState { get; private set; } = default!;
public bool LoadConfigAndUserData { get; set; } = true;
@@ -77,6 +83,77 @@ namespace Robust.Client
}
public bool Startup(Func<ILogHandler>? logHandlerFactory = null)
{
if (!StartupSystemSplash(logHandlerFactory))
return false;
// Disable load context usage on content start.
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
_modLoader.SetUseLoadContext(!ContentStart);
_modLoader.SetEnableSandboxing(Options.Sandboxing);
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), Options.ContentModulePrefix))
{
Logger.Fatal("Errors while loading content assemblies.");
return false;
}
foreach (var loadedModule in _modLoader.LoadedModules)
{
_configurationManager.LoadCVarsFromAssembly(loadedModule);
}
IoCManager.Resolve<ISerializationManager>().Initialize();
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
_resourceCache.PreloadTextures();
_userInterfaceManager.Initialize();
_eyeManager.Initialize();
_networkManager.Initialize(false);
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
_serializer.Initialize();
_inputManager.Initialize();
_console.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(Options.PrototypeDirectory);
_prototypeManager.Resync();
_mapManager.Initialize();
_entityManager.Initialize();
IoCManager.Resolve<IEntityLookup>().Initialize();
_gameStateManager.Initialize();
_placementManager.Initialize();
_viewVariablesManager.Initialize();
_scriptClient.Initialize();
_client.Initialize();
_discord.Initialize();
_modLoader.BroadcastRunLevel(ModRunLevel.PostInit);
if (_commandLineArgs?.Username != null)
{
_client.PlayerNameOverride = _commandLineArgs.Username;
}
_authManager.LoadFromEnv();
GC.Collect();
_clyde.Ready();
if (!Options.DisableCommandLineConnect &&
(_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
&& LaunchState.ConnectEndpoint != null)
{
_client.ConnectToServer(LaunchState.ConnectEndpoint);
}
return true;
}
private bool StartupSystemSplash(Func<ILogHandler>? logHandlerFactory)
{
ReadInitialLaunchState();
@@ -96,7 +173,7 @@ namespace Robust.Client
if (LoadConfigAndUserData)
{
var configFile = Path.Combine(userDataDir, "client_config.toml");
var configFile = Path.Combine(userDataDir, Options.ConfigFileName);
if (File.Exists(configFile))
{
// Load config from user data if available.
@@ -116,9 +193,16 @@ namespace Robust.Client
_configurationManager.OverrideConVars(_commandLineArgs.CVars);
}
ProfileOptSetup.Setup(_configurationManager);
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
ProgramShared.DoMounts(_resourceCache, _commandLineArgs?.MountOptions, "Content.Client", _loaderArgs != null);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory,
_loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
if (_loaderArgs != null)
{
_stringSerializer.EnableCaching = false;
@@ -126,71 +210,22 @@ namespace Robust.Client
_modLoader.VerifierExtraLoadHandler = VerifierExtraLoadHandler;
}
_clyde.TextEntered += TextEntered;
_clyde.MouseMove += MouseMove;
_clyde.KeyUp += KeyUp;
_clyde.KeyDown += KeyDown;
_clyde.MouseWheel += MouseWheel;
_clyde.CloseWindow += Shutdown;
// Bring display up as soon as resources are mounted.
if (!_clyde.Initialize())
{
return false;
}
_clyde.SetWindowTitle("Space Station 14");
_fontManager.Initialize();
// Disable load context usage on content start.
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
_modLoader.SetUseLoadContext(!_disableAssemblyLoadContext);
_modLoader.SetEnableSandboxing(true);
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), "Content."))
{
Logger.Fatal("Errors while loading content assemblies.");
return false;
}
foreach (var loadedModule in _modLoader.LoadedModules)
{
_configurationManager.LoadCVarsFromAssembly(loadedModule);
}
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
_userInterfaceManager.Initialize();
_networkManager.Initialize(false);
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
_serializer.Initialize();
_inputManager.Initialize();
_consoleHost.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
_prototypeManager.Resync();
_mapManager.Initialize();
_entityManager.Initialize();
_gameStateManager.Initialize();
_placementManager.Initialize();
_viewVariablesManager.Initialize();
_scriptClient.Initialize();
_client.Initialize();
_discord.Initialize();
_modLoader.BroadcastRunLevel(ModRunLevel.PostInit);
if (_commandLineArgs?.Username != null)
{
_client.PlayerNameOverride = _commandLineArgs.Username;
}
_authManager.LoadFromEnv();
_clyde.Ready();
if ((_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
&& LaunchState.ConnectEndpoint != null)
{
_client.ConnectToServer(LaunchState.ConnectEndpoint);
}
_clyde.SetWindowTitle(Options.DefaultWindowTitle);
_fontManager.SetFontDpi((uint) _configurationManager.GetCVar(CVars.DisplayFontDpi));
return true;
}
@@ -267,17 +302,20 @@ namespace Robust.Client
_modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs);
_timerManager.UpdateTimers(frameEventArgs);
_taskManager.ProcessPendingTasks();
_userInterfaceManager.Update(frameEventArgs);
if (_client.RunLevel >= ClientRunLevel.Connected)
// GameStateManager is in full control of the simulation update in multiplayer.
if (_client.RunLevel == ClientRunLevel.InGame || _client.RunLevel == ClientRunLevel.Connected)
{
_componentManager.CullRemovedComponents();
_gameStateManager.ApplyGameState();
_entityManager.Update(frameEventArgs.DeltaSeconds);
_playerManager.Update(frameEventArgs.DeltaSeconds);
}
_stateManager.Update(frameEventArgs);
// In singleplayer, however, we're in full control instead.
else if (_client.RunLevel == ClientRunLevel.SinglePlayerGame)
{
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds);
_lookup.Update();
}
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
}
@@ -298,11 +336,6 @@ namespace Robust.Client
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs);
}
private void Render()
{
}
internal static void SetupLogging(ILogManager logManager, Func<ILogHandler> logHandlerFactory)
{
logManager.RootSawmill.AddHandler(logHandlerFactory());
@@ -377,6 +410,9 @@ namespace Robust.Client
private void Cleanup()
{
_networkManager.Shutdown("Client shutting down");
_midiManager.Shutdown();
IoCManager.Resolve<IEntityLookup>().Shutdown();
_entityManager.Shutdown();
_clyde.Shutdown();
}

View File

@@ -17,7 +17,6 @@ namespace Robust.Client
RegisterReflection();
}
internal static void RegisterReflection()
{
// Gets a handle to the shared and the current (client) dll.

View File

@@ -19,7 +19,7 @@ namespace Robust.Client
Start(args);
}
public static void Start(string[] args, bool contentStart = false, IMainArgs? loaderArgs=null)
public static void Start(string[] args, bool contentStart = false, IMainArgs? loaderArgs=null, GameControllerOptions? options = null)
{
if (_hasStarted)
{
@@ -30,11 +30,11 @@ namespace Robust.Client
if (CommandLineArgs.TryParse(args, out var parsed))
{
ParsedMain(parsed, contentStart, loaderArgs);
ParsedMain(parsed, contentStart, loaderArgs, options);
}
}
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs)
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions? options)
{
IoCManager.InitThread();
@@ -45,11 +45,13 @@ namespace Robust.Client
var gc = (GameController) IoCManager.Resolve<IGameController>();
gc.SetCommandLineArgs(args);
gc._loaderArgs = loaderArgs;
if(options != null)
gc.Options = options;
// When the game is ran with the startup executable being content,
// we have to disable the separate load context.
// Otherwise the content assemblies will be loaded twice which causes *many* fun bugs.
gc._disableAssemblyLoadContext = contentStart;
gc.ContentStart = contentStart;
if (!gc.Startup())
{
Logger.Fatal("Failed to start game controller!");

View File

@@ -0,0 +1,60 @@
using Robust.Shared;
using Robust.Shared.Utility;
namespace Robust.Client
{
public class GameControllerOptions
{
/// <summary>
/// Whether content sandboxing will be enabled & enforced.
/// </summary>
public bool Sandboxing { get; init; } = true;
// TODO: Expose mounting methods to games using Robust as a library.
/// <summary>
/// Lists of mount options to mount.
/// </summary>
public MountOptions MountOptions { get; init; } = new();
/// <summary>
/// Name the userdata directory will have.
/// </summary>
public string UserDataDirectoryName { get; init; } = "Space Station 14";
/// <summary>
/// Name of the configuration file in the user data directory.
/// </summary>
public string ConfigFileName { get; init; } = "client_config.toml";
// TODO: Define engine branding from json file in resources.
/// <summary>
/// Default window title.
/// </summary>
public string DefaultWindowTitle { get; init; } = "Space Station 14";
/// <summary>
/// Assemblies with this prefix will be loaded.
/// </summary>
public string ContentModulePrefix { get; init; } = "Content.";
/// <summary>
/// Name of the content build directory, for game pack mounting purposes.
/// </summary>
public string ContentBuildDirectory { get; init; } = "Content.Client";
/// <summary>
/// Directory to load all prototypes from.
/// </summary>
public ResourcePath PrototypeDirectory { get; init; } = new(@"/Prototypes/");
/// <summary>
/// Whether to disable mounting the "Resources/" folder on FULL_RELEASE.
/// </summary>
public bool ResourceMountDisabled { get; init; } = false;
/// <summary>
/// Whether to disable command line args server auto-connecting.
/// </summary>
public bool DisableCommandLineConnect { get; init; } = false;
}
}

View File

@@ -1,3 +1,4 @@
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Physics;
@@ -24,6 +25,11 @@ namespace Robust.Client.GameObjects
Register<PhysicsComponent>();
RegisterReference<PhysicsComponent, IPhysBody>();
Register<CollisionWakeComponent>();
Register<ContainerManagerComponent>();
RegisterReference<ContainerManagerComponent, IContainerManager>();
RegisterIgnore("KeyBindingInput");
Register<InputComponent>();
@@ -49,14 +55,10 @@ namespace Robust.Client.GameObjects
Register<AnimationPlayerComponent>();
Register<ContainerManagerComponent>();
RegisterReference<ContainerManagerComponent, IContainerManager>();
Register<TimerComponent>();
#if DEBUG
Register<DebugExceptionOnAddComponent>();
Register<DebugExceptionExposeDataComponent>();
Register<DebugExceptionInitializeComponent>();
Register<DebugExceptionStartupComponent>();
#endif

View File

@@ -1,11 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Prometheus;
using Robust.Client.GameStates;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
@@ -13,303 +19,152 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Manager for entities -- controls things like template loading and instantiation
/// </summary>
public sealed class ClientEntityManager : EntityManager, IClientEntityManager
public sealed class ClientEntityManager : EntityManager, IClientEntityManagerInternal
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IComponentFactory _compFactory = default!;
#if EXCEPTION_TOLERANCE
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
#endif
[Dependency] private readonly IClientNetManager _networkManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private int _nextClientEntityUid = EntityUid.ClientUid + 1;
protected override int NextEntityUid { get; set; } = EntityUid.ClientUid + 1;
public override void Startup()
public override void Initialize()
{
base.Startup();
SetupNetworking();
ReceivedComponentMessage += (_, compMsg) => DispatchComponentMessage(compMsg);
ReceivedSystemMessage += (_, systemMsg) => EventBus.RaiseEvent(EventSource.Network, systemMsg);
if (Started)
{
throw new InvalidOperationException("Startup() called multiple times");
}
EntitySystemManager.Initialize();
Started = true;
base.Initialize();
}
public List<EntityUid> ApplyEntityStates(EntityState[]? curEntStates, IEnumerable<EntityUid>? deletions,
EntityState[]? nextEntStates)
IEntity IClientEntityManagerInternal.CreateEntity(string? prototypeName, EntityUid? uid)
{
var toApply = new Dictionary<IEntity, (EntityState?, EntityState?)>();
var toInitialize = new List<Entity>();
var created = new List<EntityUid>();
deletions ??= new EntityUid[0];
return base.CreateEntity(prototypeName, uid);
}
if (curEntStates != null && curEntStates.Length != 0)
void IClientEntityManagerInternal.InitializeEntity(IEntity entity)
{
EntityManager.InitializeEntity((Entity)entity);
}
void IClientEntityManagerInternal.StartEntity(IEntity entity)
{
base.StartEntity((Entity)entity);
}
#region IEntityNetworkManager impl
public override IEntityNetworkManager EntityNetManager => this;
/// <inheritdoc />
public event EventHandler<NetworkComponentMessage>? ReceivedComponentMessage;
/// <inheritdoc />
public event EventHandler<object>? ReceivedSystemMessage;
private readonly PriorityQueue<(uint seq, MsgEntity msg)> _queue = new(new MessageTickComparer());
private uint _incomingMsgSequence = 0;
/// <inheritdoc />
public void SetupNetworking()
{
_networkManager.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, HandleEntityNetworkMessage);
}
public override void TickUpdate(float frameTime, Histogram? histogram)
{
using (histogram?.WithLabels("EntityNet").NewTimer())
{
foreach (var es in curEntStates)
while (_queue.Count != 0 && _queue.Peek().msg.SourceTick <= _gameStateManager.CurServerTick)
{
//Known entities
if (Entities.TryGetValue(es.Uid, out var entity))
{
toApply.Add(entity, (es, null));
}
else //Unknown entities
{
var metaState = (MetaDataComponentState?) es.ComponentStates
?.FirstOrDefault(c => c.NetID == NetIDs.META_DATA);
if (metaState == null)
{
throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!");
}
var newEntity = CreateEntity(metaState.PrototypeId, es.Uid);
toApply.Add(newEntity, (es, null));
toInitialize.Add(newEntity);
created.Add(newEntity.Uid);
}
var (_, msg) = _queue.Take();
// Logger.DebugS("net.ent", "Dispatching: {0}: {1}", seq, msg);
DispatchMsgEntity(msg);
}
}
if (nextEntStates != null && nextEntStates.Length != 0)
{
foreach (var es in nextEntStates)
{
if (Entities.TryGetValue(es.Uid, out var entity))
{
if (toApply.TryGetValue(entity, out var state))
{
toApply[entity] = (state.Item1, es);
}
else
{
toApply[entity] = (null, es);
}
}
}
}
// Make sure this is done after all entities have been instantiated.
foreach (var kvStates in toApply)
{
var ent = kvStates.Key;
var entity = (Entity) ent;
HandleEntityState(entity.EntityManager.ComponentManager, entity, kvStates.Value.Item1,
kvStates.Value.Item2);
}
foreach (var kvp in toApply)
{
UpdateEntityTree(kvp.Key);
}
foreach (var id in deletions)
{
DeleteEntity(id);
}
#if EXCEPTION_TOLERANCE
HashSet<Entity> brokenEnts = new HashSet<Entity>();
#endif
foreach (var entity in toInitialize)
{
#if EXCEPTION_TOLERANCE
try
{
#endif
InitializeEntity(entity);
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
Logger.ErrorS("state", $"Server entity threw in Init: uid={entity.Uid}, proto={entity.Prototype}\n{e}");
brokenEnts.Add(entity);
}
#endif
}
foreach (var entity in toInitialize)
{
#if EXCEPTION_TOLERANCE
if(brokenEnts.Contains(entity))
continue;
try
{
#endif
StartEntity(entity);
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
Logger.ErrorS("state", $"Server entity threw in Start: uid={entity.Uid}, proto={entity.Prototype}\n{e}");
brokenEnts.Add(entity);
}
#endif
}
foreach (var entity in toInitialize)
{
#if EXCEPTION_TOLERANCE
if(brokenEnts.Contains(entity))
continue;
#endif
UpdateEntityTree(entity);
}
#if EXCEPTION_TOLERANCE
foreach (var entity in brokenEnts)
{
entity.Delete();
}
#endif
return created;
base.TickUpdate(frameTime, histogram);
}
/// <inheritdoc />
public override IEntity CreateEntityUninitialized(string? prototypeName)
public void SendSystemNetworkMessage(EntityEventArgs message)
{
return CreateEntity(prototypeName);
SendSystemNetworkMessage(message, default(uint));
}
public void SendSystemNetworkMessage(EntityEventArgs message, uint sequence)
{
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.SystemMessage;
msg.SystemMessage = message;
msg.SourceTick = _gameTiming.CurTick;
msg.Sequence = sequence;
_networkManager.ClientSendMessage(msg);
}
/// <inheritdoc />
public override IEntity CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates)
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel channel)
{
var newEntity = CreateEntity(prototypeName, GenerateEntityUid());
if (TryGetEntity(coordinates.EntityId, out var entity))
{
newEntity.Transform.AttachParent(entity);
newEntity.Transform.Coordinates = coordinates;
}
return newEntity;
throw new NotSupportedException();
}
/// <inheritdoc />
public override IEntity CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates)
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message)
{
var newEntity = CreateEntity(prototypeName, GenerateEntityUid());
newEntity.Transform.AttachParent(_mapManager.GetMapEntity(coordinates.MapId));
newEntity.Transform.WorldPosition = coordinates.Position;
return newEntity;
if (!component.NetID.HasValue)
throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component));
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.ComponentMessage;
msg.EntityUid = entity.Uid;
msg.NetId = component.NetID.Value;
msg.ComponentMessage = message;
msg.SourceTick = _gameTiming.CurTick;
_networkManager.ClientSendMessage(msg);
}
/// <inheritdoc />
public override IEntity SpawnEntity(string? protoName, EntityCoordinates coordinates)
private void HandleEntityNetworkMessage(MsgEntity message)
{
var newEnt = CreateEntityUninitialized(protoName, coordinates);
InitializeAndStartEntity((Entity) newEnt);
UpdateEntityTree(newEnt);
return newEnt;
}
/// <inheritdoc />
public override IEntity SpawnEntity(string? protoName, MapCoordinates coordinates)
{
var entity = CreateEntityUninitialized(protoName, coordinates);
InitializeAndStartEntity((Entity) entity);
UpdateEntityTree(entity);
return entity;
}
/// <inheritdoc />
public override IEntity SpawnEntityNoMapInit(string? protoName, EntityCoordinates coordinates)
{
return SpawnEntity(protoName, coordinates);
}
protected override EntityUid GenerateEntityUid()
{
return new(_nextClientEntityUid++);
}
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityState? curState,
EntityState? nextState)
{
var compStateWork = new Dictionary<uint, (ComponentState? curState, ComponentState? nextState)>();
var entityUid = entity.Uid;
if (curState?.ComponentChanges != null)
if (message.SourceTick <= _gameStateManager.CurServerTick)
{
foreach (var compChange in curState.ComponentChanges)
{
if (compChange.Deleted)
{
if (compMan.TryGetComponent(entityUid, compChange.NetID, out var comp))
{
compMan.RemoveComponent(entityUid, comp);
}
}
else
{
if (compMan.HasComponent(entityUid, compChange.NetID))
continue;
var newComp = (Component) _compFactory.GetComponent(compChange.ComponentName!);
newComp.Owner = entity;
compMan.AddComponent(entity, newComp, true);
}
}
DispatchMsgEntity(message);
return;
}
if (curState?.ComponentStates != null)
{
foreach (var compState in curState.ComponentStates)
{
compStateWork[compState.NetID] = (compState, null);
}
}
// MsgEntity is sent with ReliableOrdered so Lidgren guarantees ordering of incoming messages.
// We still need to store a sequence input number to ensure ordering remains consistent in
// the priority queue.
_queue.Add((++_incomingMsgSequence, message));
}
if (nextState?.ComponentStates != null)
private void DispatchMsgEntity(MsgEntity message)
{
switch (message.Type)
{
foreach (var compState in nextState.ComponentStates)
{
if (compStateWork.TryGetValue(compState.NetID, out var state))
{
compStateWork[compState.NetID] = (state.curState, compState);
}
else
{
compStateWork[compState.NetID] = (null, compState);
}
}
}
case EntityMessageType.ComponentMessage:
ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message));
return;
foreach (var (netId, (cur, next)) in compStateWork)
{
if (compMan.TryGetComponent(entityUid, netId, out var component))
{
try
{
component.HandleComponentState(cur, next);
}
catch (Exception e)
{
var wrapper = new ComponentStateApplyException(
$"Failed to apply comp state: entity={component.Owner}, comp={component.Name}", e);
#if EXCEPTION_TOLERANCE
_runtimeLog.LogException(wrapper, "Component state apply");
#else
throw wrapper;
#endif
}
}
else
{
// The component can be null here due to interp.
// Because the NEXT state will have a new component, but this one doesn't yet.
// That's fine though.
if (cur == null)
{
continue;
}
var eUid = entityUid;
var eRegisteredNetUidName = _compFactory.GetRegistration(netId).Name;
DebugTools.Assert(
$"Component does not exist for state: entUid={eUid}, expectedNetId={netId}, expectedName={eRegisteredNetUidName}");
}
case EntityMessageType.SystemMessage:
ReceivedSystemMessage?.Invoke(this, message.SystemMessage);
return;
}
}
private sealed class MessageTickComparer : IComparer<(uint seq, MsgEntity msg)>
{
public int Compare((uint seq, MsgEntity msg) x, (uint seq, MsgEntity msg) y)
{
var cmp = y.msg.SourceTick.CompareTo(x.msg.SourceTick);
if (cmp != 0)
{
return cmp;
}
return y.seq.CompareTo(x.seq);
}
}
#endregion
}
}

View File

@@ -1,128 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Client.GameStates;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
/// <summary>
/// The client implementation of the Entity Network Manager.
/// </summary>
public class ClientEntityNetworkManager : IEntityNetworkManager
{
[Dependency] private readonly IClientNetManager _networkManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
/// <inheritdoc />
public event EventHandler<NetworkComponentMessage>? ReceivedComponentMessage;
/// <inheritdoc />
public event EventHandler<object>? ReceivedSystemMessage;
private readonly PriorityQueue<(uint seq, MsgEntity msg)> _queue = new(new MessageTickComparer());
private uint _incomingMsgSequence = 0;
/// <inheritdoc />
public void SetupNetworking()
{
_networkManager.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, HandleEntityNetworkMessage);
}
public void Update()
{
while (_queue.Count != 0 && _queue.Peek().msg.SourceTick <= _gameStateManager.CurServerTick)
{
var (_, msg) = _queue.Take();
// Logger.DebugS("net.ent", "Dispatching: {0}: {1}", seq, msg);
DispatchMsgEntity(msg);
}
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message)
{
SendSystemNetworkMessage(message, default(uint));
}
public void SendSystemNetworkMessage(EntitySystemMessage message, uint sequence)
{
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.SystemMessage;
msg.SystemMessage = message;
msg.SourceTick = _gameTiming.CurTick;
msg.Sequence = sequence;
_networkManager.ClientSendMessage(msg);
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message, INetChannel channel)
{
throw new NotSupportedException();
}
/// <inheritdoc />
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message)
{
if (!component.NetID.HasValue)
throw new ArgumentException($"Component {component.Name} does not have a NetID.", nameof(component));
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.ComponentMessage;
msg.EntityUid = entity.Uid;
msg.NetId = component.NetID.Value;
msg.ComponentMessage = message;
msg.SourceTick = _gameTiming.CurTick;
_networkManager.ClientSendMessage(msg);
}
private void HandleEntityNetworkMessage(MsgEntity message)
{
if (message.SourceTick <= _gameStateManager.CurServerTick)
{
DispatchMsgEntity(message);
return;
}
// MsgEntity is sent with ReliableOrdered so Lidgren guarantees ordering of incoming messages.
// We still need to store a sequence input number to ensure ordering remains consistent in
// the priority queue.
_queue.Add((++_incomingMsgSequence, message));
}
private void DispatchMsgEntity(MsgEntity message)
{
switch (message.Type)
{
case EntityMessageType.ComponentMessage:
ReceivedComponentMessage?.Invoke(this, new NetworkComponentMessage(message));
return;
case EntityMessageType.SystemMessage:
ReceivedSystemMessage?.Invoke(this, message.SystemMessage);
return;
}
}
private sealed class MessageTickComparer : IComparer<(uint seq, MsgEntity msg)>
{
public int Compare((uint seq, MsgEntity msg) x, (uint seq, MsgEntity msg) y)
{
var cmp = y.msg.SourceTick.CompareTo(x.msg.SourceTick);
if (cmp != 0)
{
return cmp;
}
return y.seq.CompareTo(x.seq);
}
}
}
}

View File

@@ -4,8 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
@@ -15,13 +14,11 @@ namespace Robust.Client.GameObjects
{
[ViewVariables]
private Dictionary<object, object> data = new();
[ViewVariables]
[DataField("visuals")]
internal List<AppearanceVisualizer> Visualizers = new();
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
private static bool _didRegisterSerializer;
[ViewVariables]
private bool _appearanceDirty;
@@ -97,8 +94,7 @@ namespace Robust.Client.GameObjects
return;
}
EntitySystem.Get<AppearanceSystem>()
.EnqueueAppearanceUpdate(this);
EntitySystem.Get<AppearanceSystem>().EnqueueUpdate(this);
_appearanceDirty = true;
}
@@ -107,18 +103,6 @@ namespace Robust.Client.GameObjects
_appearanceDirty = false;
}
public override void ExposeData(ObjectSerializer serializer)
{
if (!_didRegisterSerializer)
{
YamlObjectSerializer.RegisterTypeSerializer(typeof(AppearanceVisualizer),
new VisualizerTypeSerializer(_reflectionManager));
_didRegisterSerializer = true;
}
serializer.DataFieldCached(ref Visualizers, "visuals", new List<AppearanceVisualizer>());
}
public override void Initialize()
{
base.Initialize();
@@ -130,108 +114,15 @@ namespace Robust.Client.GameObjects
MarkDirty();
}
class VisualizerTypeSerializer : YamlObjectSerializer.TypeSerializer
{
private readonly IReflectionManager _reflectionManager;
public VisualizerTypeSerializer(IReflectionManager reflectionManager)
{
_reflectionManager = reflectionManager;
}
public override object NodeToType(Type type, YamlNode node, YamlObjectSerializer serializer)
{
var mapping = (YamlMappingNode) node;
var nodeType = mapping.GetNode("type");
switch (nodeType.AsString())
{
case SpriteLayerToggle.NAME:
var keyString = mapping.GetNode("key").AsString();
object key;
if (_reflectionManager.TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
var layer = mapping.GetNode("layer").AsInt();
return new SpriteLayerToggle(key, layer);
default:
var visType = _reflectionManager.LooseGetType(nodeType.AsString());
if (!typeof(AppearanceVisualizer).IsAssignableFrom(visType))
{
throw new InvalidOperationException();
}
var vis = (AppearanceVisualizer) Activator.CreateInstance(visType)!;
vis.LoadData(mapping);
return vis;
}
}
public override YamlNode TypeToNode(object obj, YamlObjectSerializer serializer)
{
switch (obj)
{
case SpriteLayerToggle spriteLayerToggle:
YamlScalarNode key;
if (spriteLayerToggle.Key is Enum)
{
var name = spriteLayerToggle.Key.GetType().FullName;
key = new YamlScalarNode($"{name}.{spriteLayerToggle.Key}");
}
else
{
key = new YamlScalarNode(spriteLayerToggle.Key.ToString());
}
return new YamlMappingNode
{
{new YamlScalarNode("type"), new YamlScalarNode(SpriteLayerToggle.NAME)},
{new YamlScalarNode("key"), key},
{new YamlScalarNode("layer"), new YamlScalarNode(spriteLayerToggle.SpriteLayer.ToString())},
};
default:
// TODO: A proper way to do serialization here.
// I can't use the ExposeData system here since that's specific to entity serializers.
return new YamlMappingNode();
}
}
}
internal class SpriteLayerToggle : AppearanceVisualizer
{
public const string NAME = "sprite_layer_toggle";
public readonly object Key;
public readonly int SpriteLayer;
public SpriteLayerToggle(object key, int spriteLayer)
{
Key = key;
SpriteLayer = spriteLayer;
}
}
}
/// <summary>
/// Handles the visualization of data inside of an appearance component.
/// Implementations of this class are NOT bound to a specific entity, they are flyweighted across multiple.
/// </summary>
[ImplicitDataDefinitionForInheritors]
public abstract class AppearanceVisualizer
{
/// <summary>
/// Load data from the prototype declaring this visualizer, to configure settings and such.
/// </summary>
public virtual void LoadData(YamlMappingNode node)
{
}
/// <summary>
/// Initializes an entity to be managed by this appearance controller.
/// DO NOT assume this is your only entity. Visualizers are shared.

View File

@@ -1,87 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
public sealed partial class ContainerManagerComponent
{
[DebuggerDisplay("ClientContainer {Owner.Uid}/{ID}")]
private sealed class ClientContainer : IContainer
{
public List<IEntity> Entities { get; } = new List<IEntity>();
public ClientContainer(string id, ContainerManagerComponent manager)
{
ID = id;
Manager = manager;
}
[ViewVariables] public IContainerManager Manager { get; }
[ViewVariables] public string ID { get; }
[ViewVariables] public IEntity Owner => Manager.Owner;
[ViewVariables] public bool Deleted { get; private set; }
[ViewVariables] public IReadOnlyList<IEntity> ContainedEntities => Entities;
[ViewVariables]
public bool ShowContents { get; set; }
[ViewVariables]
public bool OccludesLight { get; set; }
public bool CanInsert(IEntity toinsert)
{
return false;
}
public bool Insert(IEntity toinsert)
{
return false;
}
public bool CanRemove(IEntity toremove)
{
return false;
}
public bool Remove(IEntity toremove)
{
return false;
}
public void ForceRemove(IEntity toRemove)
{
throw new NotSupportedException("Cannot directly modify containers on the client");
}
public bool Contains(IEntity contained)
{
return Entities.Contains(contained);
}
public void DoInsert(IEntity entity)
{
Entities.Add(entity);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EntInsertedIntoContainerMessage(entity, this));
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(entity));
}
public void DoRemove(IEntity entity)
{
Entities.Remove(entity);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EntRemovedFromContainerMessage(entity, this));
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(entity));
}
public void Shutdown()
{
Deleted = true;
}
}
public override void InternalContainerShutdown(IContainer container)
{
}
}
}

View File

@@ -1,168 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
public sealed partial class ContainerManagerComponent : SharedContainerManagerComponent
{
[ViewVariables]
private readonly Dictionary<string, ClientContainer> _containers = new();
public override T MakeContainer<T>(string id)
{
throw new NotSupportedException("Cannot modify containers on the client.");
}
public override bool Remove(IEntity entity)
{
// TODO: This will probably need relaxing if we want to predict things like inventories.
throw new NotSupportedException("Cannot modify containers on the client.");
}
protected override IEnumerable<IContainer> GetAllContainersImpl()
{
return _containers.Values.Where(c => !c.Deleted);
}
public override IContainer GetContainer(string id)
{
return _containers[id];
}
public override bool HasContainer(string id)
{
return _containers.ContainsKey(id);
}
public override bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container)
{
var ret = _containers.TryGetValue(id, out var cont);
container = cont!;
return ret;
}
/// <inheritdoc />
public override bool TryGetContainer(IEntity entity, [NotNullWhen(true)] out IContainer? container)
{
foreach (var contain in _containers.Values)
{
if (!contain.Deleted && contain.Contains(entity))
{
container = contain;
return true;
}
}
container = default;
return false;
}
public override bool ContainsEntity(IEntity entity)
{
foreach (var container in _containers.Values)
{
if (!container.Deleted && container.Contains(entity))
{
return true;
}
}
return false;
}
public override void ForceRemove(IEntity entity)
{
throw new NotSupportedException("Cannot modify containers on the client.");
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
if(!(curState is ContainerManagerComponentState cast))
return;
// Delete now-gone containers.
List<string>? toDelete = null;
foreach (var (id, container) in _containers)
{
if (!cast.Containers.ContainsKey(id))
{
container.Shutdown();
toDelete ??= new List<string>();
toDelete.Add(id);
}
}
if (toDelete != null)
{
foreach (var dead in toDelete)
{
_containers.Remove(dead);
}
}
// Add new containers and update existing contents.
foreach (var (id, data) in cast.Containers)
{
if (!_containers.TryGetValue(id, out var container))
{
container = new ClientContainer(id, this);
_containers.Add(id, container);
}
// sync show flag
container.ShowContents = data.ShowContents;
container.OccludesLight = data.OccludesLight;
// Remove gone entities.
List<IEntity>? toRemove = null;
foreach (var entity in container.Entities)
{
if (!data.ContainedEntities.Contains(entity.Uid))
{
toRemove ??= new List<IEntity>();
toRemove.Add(entity);
}
}
if (toRemove != null)
{
foreach (var goner in toRemove)
{
container.DoRemove(goner);
}
}
// Add new entities.
foreach (var uid in data.ContainedEntities)
{
var entity = Owner.EntityManager.GetEntity(uid);
if (!container.Entities.Contains(entity))
{
container.DoInsert(entity);
}
}
}
}
protected override void Shutdown()
{
base.Shutdown();
// On shutdown we won't get to process remove events in the containers so this has to be manually done.
foreach (var container in _containers.Values)
{
foreach (var containerEntity in container.Entities)
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
new UpdateContainerOcclusionMessage(containerEntity));
}
}
}
}
}

View File

@@ -1,9 +1,11 @@
using Robust.Client.Graphics;
using Robust.Client.Graphics;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
@@ -20,8 +22,10 @@ namespace Robust.Client.GameObjects
// Horrible hack to get around ordering issues.
private bool _setCurrentOnInitialize;
private bool _setDrawFovOnInitialize;
private Vector2 _setZoomOnInitialize = Vector2.One/2f;
[DataField("drawFov")]
private bool _setDrawFovOnInitialize = true;
[DataField("zoom")]
private Vector2 _setZoomOnInitialize = Vector2.One;
private Vector2 _offset = Vector2.Zero;
public IEye? Eye => _eye;
@@ -148,6 +152,7 @@ namespace Robust.Client.GameObjects
Zoom = state.Zoom;
Offset = state.Offset;
Rotation = state.Rotation;
VisibilityMask = state.VisibilityMask;
}
public override void OnRemove()
@@ -157,15 +162,6 @@ namespace Robust.Client.GameObjects
Current = false;
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataFieldCached(ref _setZoomOnInitialize, "zoom", Vector2.One/2f);
serializer.DataFieldCached(ref _setDrawFovOnInitialize, "drawFov", true);
}
/// <summary>
/// Updates the Eye of this entity with the transform position. This has to be called every frame to
/// keep the view following the entity.

View File

@@ -1,105 +1,51 @@
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
public class IconComponent : Component
public class IconComponent : Component, ISerializationHooks
{
public override string Name => "Icon";
public IDirectionalTextureProvider? Icon { get; private set; }
[Dependency] private readonly IResourceCache _resourceCache = default!;
[DataField("sprite")]
private ResourcePath? rsi;
[DataField("state")]
private string? stateID;
void ISerializationHooks.AfterDeserialization()
{
if (rsi != null && stateID != null)
{
Icon = new SpriteSpecifier.Rsi(rsi, stateID).Frame0();
}
}
public const string LogCategory = "go.comp.icon";
const string SerializationCache = "icon";
public override void ExposeData(ObjectSerializer serializer)
private static IRsiStateLike TextureForConfig(IconComponent compData, IResourceCache resourceCache)
{
base.ExposeData(serializer);
// TODO: Does this need writing?
if (serializer.Reading)
{
Icon = TextureForConfig(serializer, _resourceCache);
}
}
private static IRsiStateLike TextureForConfig(ObjectSerializer serializer, IResourceCache resourceCache)
{
DebugTools.Assert(serializer.Reading);
if (serializer.TryGetCacheData<IRsiStateLike>(SerializationCache, out var dirTex))
{
return dirTex;
}
var tex = serializer.ReadDataField<string?>("texture", null);
if (!string.IsNullOrWhiteSpace(tex))
{
dirTex = resourceCache.GetResource<TextureResource>(SpriteComponent.TextureRoot / tex).Texture;
serializer.SetCacheData(SerializationCache, dirTex);
return dirTex;
}
RSI rsi;
var rsiPath = serializer.ReadDataField<string?>("sprite", null);
if (string.IsNullOrWhiteSpace(rsiPath))
{
dirTex = resourceCache.GetFallback<TextureResource>().Texture;
serializer.SetCacheData(SerializationCache, dirTex);
return dirTex;
}
var path = SpriteComponent.TextureRoot / rsiPath;
try
{
rsi = resourceCache.GetResource<RSIResource>(path).RSI;
}
catch
{
dirTex = resourceCache.GetFallback<TextureResource>().Texture;
serializer.SetCacheData(SerializationCache, dirTex);
return dirTex;
}
var stateId = serializer.ReadDataField<string?>("state", null);
if (string.IsNullOrWhiteSpace(stateId))
{
Logger.ErrorS(LogCategory, "No state specified.");
dirTex = resourceCache.GetFallback<TextureResource>().Texture;
serializer.SetCacheData(SerializationCache, dirTex);
return dirTex;
}
if (rsi.TryGetState(stateId, out var state))
{
serializer.SetCacheData(SerializationCache, state);
return state;
}
else
{
Logger.ErrorS(LogCategory, "State '{0}' does not exist on RSI.", stateId);
return resourceCache.GetFallback<TextureResource>().Texture;
}
return compData.Icon?.Default ?? resourceCache.GetFallback<TextureResource>().Texture;
}
public static IRsiStateLike? GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
{
if (!prototype.Components.TryGetValue("Icon", out var mapping))
if (!prototype.Components.TryGetValue("Icon", out var compData))
{
return null;
}
return TextureForConfig(YamlObjectSerializer.NewReader(mapping), resourceCache);
return TextureForConfig((IconComponent)compData, resourceCache);
}
}
}

View File

@@ -1,6 +1,8 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
@@ -17,14 +19,7 @@ namespace Robust.Client.GameObjects
/// The context that will be made active for a client that attaches to this entity.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public string ContextName { get; set; } = default!;
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataReadWriteFunction("context", InputContextContainer.DefaultContextName, value => ContextName = value, () => ContextName);
}
[DataField("context")]
public string ContextName { get; set; } = InputContextContainer.DefaultContextName;
}
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
@@ -8,8 +10,8 @@ namespace Robust.Client.GameObjects
{
internal sealed class ClientOccluderComponent : OccluderComponent
{
internal SnapGridComponent? SnapGrid { get; private set; }
[Dependency] private readonly IMapManager _mapManager = default!;
[ViewVariables] private (GridId, Vector2i) _lastPosition;
[ViewVariables] internal OccluderDir Occluding { get; private set; }
[ViewVariables] internal uint UpdateGeneration { get; set; }
@@ -29,39 +31,36 @@ namespace Robust.Client.GameObjects
{
base.Startup();
if (Owner.TryGetComponent(out SnapGridComponent? snap))
if (Owner.Transform.Anchored)
{
SnapGrid = snap;
SnapGrid.OnPositionChanged += SnapGridOnPositionChanged;
SnapGridOnPositionChanged();
}
}
private void SnapGridOnPositionChanged()
public void SnapGridOnPositionChanged()
{
SendDirty();
_lastPosition = (Owner.Transform.GridID, SnapGrid!.Position);
if(!Owner.Transform.Anchored)
return;
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
_lastPosition = (Owner.Transform.GridID, grid.TileIndicesFor(Owner.Transform.Coordinates));
}
protected override void Shutdown()
{
base.Shutdown();
if (SnapGrid != null)
{
SnapGrid.OnPositionChanged -= SnapGridOnPositionChanged;
}
SendDirty();
}
private void SendDirty()
{
if (SnapGrid != null)
if (Owner.Transform.Anchored)
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
new OccluderDirtyEvent(Owner, _lastPosition, SnapGrid.Offset));
new OccluderDirtyEvent(Owner, _lastPosition));
}
}
@@ -69,16 +68,18 @@ namespace Robust.Client.GameObjects
{
Occluding = OccluderDir.None;
if (Deleted || SnapGrid == null)
if (Deleted || !Owner.Transform.Anchored)
{
return;
}
void CheckDir(Direction dir, OccluderDir oclDir)
{
foreach (var neighbor in SnapGrid.GetInDir(dir))
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
var position = Owner.Transform.Coordinates;
foreach (var neighbor in grid.GetInDir(position, dir))
{
if (neighbor.TryGetComponent(out ClientOccluderComponent? comp) && comp.Enabled)
if (Owner.EntityManager.ComponentManager.TryGetComponent(neighbor, out ClientOccluderComponent? comp) && comp.Enabled)
{
Occluding |= oclDir;
break;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Animations;
@@ -7,13 +7,14 @@ using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(IPointLightComponent))]
public class PointLightComponent : Component, IPointLightComponent
public class PointLightComponent : Component, IPointLightComponent, ISerializationHooks
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
@@ -134,16 +135,25 @@ namespace Robust.Client.GameObjects
}
}
private float _radius = 5;
[DataField("radius")]
private float _radius = 5f;
[DataField("nestedvisible")]
private bool _visibleNested = true;
private bool _lightOnParent;
[DataField("color")]
private Color _color = Color.White;
private Vector2 _offset;
[DataField("offset")]
private Vector2 _offset = Vector2.Zero;
[DataField("enabled")]
private bool _enabled = true;
[DataField("autoRot")]
private bool _maskAutoRotate;
private Angle _rotation;
private float _energy;
private float _softness;
[DataField("energy")]
private float _energy = 1f;
[DataField("softness")]
private float _softness = 1f;
[DataField("mask")]
private string? _maskPath;
/// <summary>
@@ -169,6 +179,14 @@ namespace Robust.Client.GameObjects
Mask = null;
}
void ISerializationHooks.AfterDeserialization()
{
if (_maskPath != null)
{
Mask = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(_maskPath);
}
}
public override void Initialize()
{
base.Initialize();
@@ -180,7 +198,7 @@ namespace Robust.Client.GameObjects
{
base.HandleMessage(message, component);
if ((message is ParentChangedMessage msg))
if (message is ParentChangedMessage msg)
{
HandleTransformParentChanged(msg);
}
@@ -204,19 +222,6 @@ namespace Robust.Client.GameObjects
}
}
public override void ExposeData(ObjectSerializer serializer)
{
serializer.DataFieldCached(ref _offset, "offset", Vector2.Zero);
serializer.DataFieldCached(ref _radius, "radius", 5f);
serializer.DataFieldCached(ref _color, "color", Color.White);
serializer.DataFieldCached(ref _enabled, "enabled", true);
serializer.DataFieldCached(ref _energy, "energy", 1f);
serializer.DataFieldCached(ref _softness, "softness", 1f);
serializer.DataFieldCached(ref _maskAutoRotate, "autoRot", false);
serializer.DataFieldCached(ref _visibleNested, "nestedvisible", true);
serializer.DataFieldCached(ref _maskPath, "mask", null);
}
public override void OnRemove()
{
base.OnRemove();

View File

@@ -17,6 +17,9 @@ using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -25,17 +28,22 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
namespace Robust.Client.GameObjects
{
public sealed class SpriteComponent : SharedSpriteComponent, ISpriteComponent,
IComponentDebug
IComponentDebug, ISerializationHooks
{
[Dependency] private readonly IResourceCache resourceCache = default!;
[Dependency] private readonly IPrototypeManager prototypes = default!;
[DataField("visible")]
private bool _visible = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool Visible
public override bool Visible
{
get => _visible;
set => _visible = value;
}
[DataField("drawdepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
private int drawDepth = DrawDepthTag.Default;
/// <summary>
@@ -48,6 +56,7 @@ namespace Robust.Client.GameObjects
set => drawDepth = value;
}
[DataField("scale")]
private Vector2 scale = Vector2.One;
/// <summary>
@@ -61,7 +70,8 @@ namespace Robust.Client.GameObjects
set => scale = value;
}
private Angle rotation;
[DataField("rotation")]
private Angle rotation = Angle.Zero;
[Animatable]
[ViewVariables(VVAccess.ReadWrite)]
@@ -71,6 +81,7 @@ namespace Robust.Client.GameObjects
set => rotation = value;
}
[DataField("offset")]
private Vector2 offset = Vector2.Zero;
/// <summary>
@@ -84,6 +95,7 @@ namespace Robust.Client.GameObjects
set => offset = value;
}
[DataField("color")]
private Color color = Color.White;
[Animatable]
@@ -108,18 +120,152 @@ namespace Robust.Client.GameObjects
set => _directional = value;
}
[DataField("directional")]
private bool _directional = true;
[DataField("layerDatums")]
private List<PrototypeLayerData> LayerDatums
{
get
{
var layerDatums = new List<PrototypeLayerData>();
foreach (var layer in Layers)
{
layerDatums.Add(layer.ToPrototypeData());
}
return layerDatums;
}
set
{
if(value == null) return;
Layers.Clear();
foreach (var layerDatum in value)
{
var anyTextureAttempted = false;
var layer = new Layer(this);
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
{
var path = TextureRoot / layerDatum.RsiPath;
try
{
layer.RSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(path).RSI;
}
catch
{
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.State))
{
anyTextureAttempted = true;
var theRsi = layer.RSI ?? BaseRSI;
if (theRsi == null)
{
Logger.ErrorS(LogCategory,
"Layer has no RSI to load states from. Cannot use 'state' property. ({0})",
layerDatum.State);
}
else
{
var stateid = new RSI.StateId(layerDatum.State);
layer.State = stateid;
if (theRsi.TryGetState(stateid, out var state))
{
// Always use south because this layer will be cached in the serializer.
layer.AnimationTimeLeft = state.GetDelay(0);
}
else
{
Logger.ErrorS(LogCategory,
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
stateid);
}
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
{
anyTextureAttempted = true;
if (layer.State.IsValid)
{
Logger.ErrorS(LogCategory,
"Cannot specify 'texture' on a layer if it has an RSI state specified."
);
}
else
{
layer.Texture =
IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
{
if (IoCManager.Resolve<IPrototypeManager>().TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
{
layer.Shader = prototype.Instance();
}
else
{
Logger.ErrorS(LogCategory,
"Shader prototype '{0}' does not exist.",
layerDatum.Shader);
}
}
layer.Color = layerDatum.Color;
layer.Rotation = layerDatum.Rotation;
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
layer.Visible = anyTextureAttempted && layerDatum.Visible;
layer.Scale = layerDatum.Scale;
Layers.Add(layer);
if (layerDatum.MapKeys != null)
{
var index = Layers.Count - 1;
foreach (var keyString in layerDatum.MapKeys)
{
object key;
if (IoCManager.Resolve<IReflectionManager>().TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
if (LayerMap.ContainsKey(key))
{
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
continue;
}
LayerMap.Add(key, index);
}
}
}
_layerMapShared = true;
UpdateIsInert();
}
}
private RSI? _baseRsi;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("rsi", priority: 2)]
public RSI? BaseRSI
{
get => _baseRsi;
set
{
_baseRsi = value;
if (Layers == null || value == null)
if (value == null)
{
return;
}
@@ -147,6 +293,12 @@ namespace Robust.Client.GameObjects
}
}
[DataField("sprite", readOnly: true)] private string? rsi;
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new ();
[DataField("state", readOnly: true)] private string? state;
[DataField("texture", readOnly: true)] private string? texture;
[ViewVariables(VVAccess.ReadWrite)]
public bool ContainerOccluded { get; set; }
@@ -158,11 +310,7 @@ namespace Robust.Client.GameObjects
[ViewVariables] private Dictionary<object, int> LayerMap = new();
[ViewVariables] private bool _layerMapShared;
[ViewVariables] private List<Layer> Layers = default!;
[Dependency] private readonly IResourceCache resourceCache = default!;
[Dependency] private readonly IPrototypeManager prototypes = default!;
[Dependency] private readonly IReflectionManager reflectionManager = default!;
[ViewVariables] private List<Layer> Layers = new();
[ViewVariables(VVAccess.ReadWrite)] public uint RenderOrder { get; set; }
@@ -170,10 +318,9 @@ namespace Robust.Client.GameObjects
private static ShaderInstance? _defaultShader;
[ViewVariables]
private ShaderInstance? DefaultShader => _defaultShader ??
(_defaultShader = prototypes
.Index<ShaderPrototype>("shaded")
.Instance());
private ShaderInstance? DefaultShader => _defaultShader ??= prototypes
.Index<ShaderPrototype>("shaded")
.Instance();
public const string LogCategory = "go.comp.sprite";
const string LayerSerializationCache = "spritelayer";
@@ -181,6 +328,46 @@ namespace Robust.Client.GameObjects
[ViewVariables(VVAccess.ReadWrite)] public bool IsInert { get; private set; }
void ISerializationHooks.AfterDeserialization()
{
{
if (!string.IsNullOrWhiteSpace(rsi))
{
var rsiPath = TextureRoot / rsi;
try
{
BaseRSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(rsiPath).RSI;
}
catch (Exception e)
{
Logger.ErrorS(SpriteComponent.LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
}
}
}
if (layerDatums.Count == 0)
{
if (state != null || texture != null)
{
layerDatums.Insert(0, new PrototypeLayerData
{
TexturePath = string.IsNullOrWhiteSpace(texture) ? null : texture,
State = string.IsNullOrWhiteSpace(state) ? null : state,
Color = Color.White,
Scale = Vector2.One,
Visible = true,
});
state = null;
texture = null;
}
}
if (layerDatums.Count != 0)
{
LayerDatums = layerDatums;
}
}
/// <summary>
/// Update this sprite component to visibly match the current state of other at the time
/// this is called. Does not keep them perpetually in sync.
@@ -661,8 +848,8 @@ namespace Robust.Client.GameObjects
}
else
{
Logger.ErrorS(LogCategory, "State '{0}' does not exist in RSI. Trace:\n{1}", stateId,
Environment.StackTrace);
Logger.ErrorS(LogCategory, "State '{0}' does not exist in RSI {1}. Trace:\n{2}", stateId,
actualRsi.Path, Environment.StackTrace);
theLayer.Texture = null;
}
}
@@ -1005,9 +1192,14 @@ namespace Robust.Client.GameObjects
RenderInternal(drawingHandle, worldRotation, Vector2.Zero, overrideDirection);
}
private bool _screenLock = false;
private Direction _overrideDirection = Direction.South;
private bool _enableOverrideDirection = false;
[DataField("noRot")]
private bool _screenLock = true;
[DataField("overrideDir")]
private Direction _overrideDirection = Direction.East;
[DataField("enableOverrideDir")]
private bool _enableOverrideDirection;
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
@@ -1153,205 +1345,6 @@ namespace Robust.Client.GameObjects
return texture;
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataFieldCached(ref scale, "scale", Vector2.One);
serializer.DataFieldCached(ref rotation, "rotation", Angle.Zero);
serializer.DataFieldCached(ref offset, "offset", Vector2.Zero);
serializer.DataFieldCached(ref drawDepth, "drawdepth", DrawDepthTag.Default,
WithFormat.Constants<DrawDepthTag>());
serializer.DataFieldCached(ref color, "color", Color.White);
serializer.DataFieldCached(ref _visible, "visible", true);
serializer.DataFieldCached(ref _directional, "directional", true); //TODO: Kill ME
serializer.DataFieldCached(ref _screenLock, "noRot", true);
serializer.DataFieldCached(ref _enableOverrideDirection, "enableOverrideDir", false);
serializer.DataFieldCached(ref _overrideDirection, "overrideDir", Direction.East);
// TODO: Writing?
if (!serializer.Reading)
{
return;
}
{
var rsi = serializer.ReadDataField<string?>("sprite", null);
if (!string.IsNullOrWhiteSpace(rsi))
{
var rsiPath = TextureRoot / rsi;
try
{
BaseRSI = resourceCache.GetResource<RSIResource>(rsiPath).RSI;
}
catch (Exception e)
{
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
}
}
}
List<Layer> CloneLayers(List<Layer> source)
{
var clone = new List<Layer>(source.Count);
foreach (var layer in source)
{
clone.Add(new Layer(layer, this));
}
return clone;
}
if (serializer.TryGetCacheData<List<Layer>>(LayerSerializationCache, out var layers))
{
LayerMap = serializer.GetCacheData<Dictionary<object, int>>(LayerMapSerializationCache);
_layerMapShared = true;
Layers = CloneLayers(layers);
UpdateIsInert();
return;
}
layers = new List<Layer>();
var layerMap = new Dictionary<object, int>();
var layerData =
serializer.ReadDataField("layers", new List<PrototypeLayerData>());
if(layerData.Count == 0){
var baseState = serializer.ReadDataField<string?>("state", null);
var texturePath = serializer.ReadDataField<string?>("texture", null);
if (baseState != null || texturePath != null)
{
layerData.Insert(0, new PrototypeLayerData
{
TexturePath = string.IsNullOrWhiteSpace(texturePath) ? null : texturePath,
State = string.IsNullOrWhiteSpace(baseState) ? null : baseState,
Color = Color.White,
Scale = Vector2.One,
Visible = true,
});
}
}
foreach (var layerDatum in layerData)
{
var anyTextureAttempted = false;
var layer = new Layer(this);
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
{
var path = TextureRoot / layerDatum.RsiPath;
try
{
layer.RSI = resourceCache.GetResource<RSIResource>(path).RSI;
}
catch
{
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.State))
{
anyTextureAttempted = true;
var theRsi = layer.RSI ?? BaseRSI;
if (theRsi == null)
{
Logger.ErrorS(LogCategory,
"Layer has no RSI to load states from."
+ "cannot use 'state' property. Prototype: '{0}'", Owner.Prototype?.ID);
}
else
{
var stateid = new RSI.StateId(layerDatum.State);
layer.State = stateid;
if (theRsi.TryGetState(stateid, out var state))
{
// Always use south because this layer will be cached in the serializer.
layer.AnimationTimeLeft = state.GetDelay(0);
}
else
{
Logger.ErrorS(LogCategory,
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
stateid);
}
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
{
anyTextureAttempted = true;
if (layer.State.IsValid)
{
Logger.ErrorS(LogCategory,
"Cannot specify 'texture' on a layer if it has an RSI state specified."
);
}
else
{
layer.Texture =
resourceCache.GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
{
if (prototypes.TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
{
layer.Shader = prototype.Instance();
}
else
{
Logger.ErrorS(LogCategory,
"Shader prototype '{0}' does not exist. Prototype: '{1}'",
layerDatum.Shader, Owner.Prototype?.ID);
}
}
layer.Color = layerDatum.Color;
layer.Rotation = layerDatum.Rotation;
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
layer.Visible = anyTextureAttempted && layerDatum.Visible;
layer.Scale = layerDatum.Scale;
layers.Add(layer);
if (layerDatum.MapKeys != null)
{
var index = layers.Count - 1;
foreach (var keyString in layerDatum.MapKeys)
{
object key;
if (reflectionManager.TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
if (layerMap.ContainsKey(key))
{
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
continue;
}
layerMap.Add(key, index);
}
}
}
Layers = layers;
LayerMap = layerMap;
_layerMapShared = true;
serializer.SetCacheData(LayerSerializationCache, CloneLayers(Layers));
serializer.SetCacheData(LayerMapSerializationCache, layerMap);
UpdateIsInert();
}
public override void OnRemove()
{
base.OnRemove();
@@ -1663,7 +1656,7 @@ namespace Robust.Client.GameObjects
Flip = 3,
}
private class Layer : ISpriteLayer
public class Layer : ISpriteLayer
{
[ViewVariables] private readonly SpriteComponent _parent;
@@ -1730,6 +1723,22 @@ namespace Robust.Client.GameObjects
RSI.StateId ISpriteLayer.RsiState { get => State; set => SetState(value); }
Texture? ISpriteLayer.Texture { get => Texture; set => SetTexture(value); }
public PrototypeLayerData ToPrototypeData()
{
return new PrototypeLayerData
{
Color = Color,
Rotation = Rotation,
Scale = Scale,
//todo Shader = Shader,
State = State.Name,
Visible = Visible,
RsiPath = RSI?.Path?.ToString(),
//todo TexturePath = Textur
//todo MapKeys
};
}
bool ISpriteLayer.Visible
{
get => Visible;
@@ -2000,7 +2009,6 @@ namespace Robust.Client.GameObjects
}
return state;
}
}
@@ -2051,7 +2059,6 @@ namespace Robust.Client.GameObjects
if (!anyTexture)
yield return resourceCache.GetFallback<TextureResource>().Texture;
}
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
@@ -2078,6 +2085,7 @@ namespace Robust.Client.GameObjects
public IEntityManager EntityManager { get; } = null!;
public string Name { get; set; } = string.Empty;
public EntityUid Uid { get; } = EntityUid.Invalid;
EntityLifeStage IEntity.LifeStage { get => _lifeStage; set => _lifeStage = value; }
public bool Initialized { get; } = false;
public bool Initializing { get; } = false;
public bool Deleted { get; } = true;
@@ -2094,10 +2102,12 @@ namespace Robust.Client.GameObjects
public IMetaDataComponent MetaData { get; } = null!;
private Dictionary<Type, IComponent> _components = new();
private EntityLifeStage _lifeStage;
public T AddComponent<T>() where T : Component, new()
{
var typeFactory = IoCManager.Resolve<IDynamicTypeFactoryInternal>();
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var comp = (T) typeFactory.CreateInstanceUnchecked(typeof(T));
_components[typeof(T)] = comp;
comp.Owner = this;
@@ -2107,9 +2117,9 @@ namespace Robust.Client.GameObjects
_components[typeof(ISpriteComponent)] = comp;
}
if (Prototype != null && Prototype.Components.TryGetValue(comp.Name, out var node))
if (Prototype != null && Prototype.TryGetComponent<T>(comp.Name, out var node))
{
comp.ExposeData(YamlObjectSerializer.NewReader(node));
comp = serializationManager.Copy(node, comp)!;
}
return comp;
@@ -2140,11 +2150,6 @@ namespace Robust.Client.GameObjects
return null!;
}
public IComponent GetComponent(uint netID)
{
return null!;
}
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component) where T : class
{
component = null;
@@ -2171,21 +2176,6 @@ namespace Robust.Client.GameObjects
return null;
}
public bool TryGetComponent(uint netId, [NotNullWhen(true)] out IComponent? component)
{
component = null;
return false;
}
public IComponent? GetComponentOrNull(uint netId)
{
return null;
}
public void Shutdown()
{
}
public void Delete()
{
}

View File

@@ -6,41 +6,31 @@ using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Client.GameObjects
{
public class ClientUserInterfaceComponent : SharedUserInterfaceComponent
public class ClientUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
{
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
private readonly Dictionary<object, BoundUserInterface> _openInterfaces =
new();
private Dictionary<object, PrototypeData> _interfaceData = default!;
#pragma warning disable 649
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
#pragma warning restore 649
private readonly Dictionary<object, PrototypeData> _interfaces = new();
public override void ExposeData(ObjectSerializer serializer)
[DataField("interfaces", readOnly: true)]
private List<PrototypeData> _interfaceData = new();
void ISerializationHooks.AfterDeserialization()
{
base.ExposeData(serializer);
_interfaces.Clear();
const string cache = "ui_cache";
if (serializer.TryGetCacheData<Dictionary<object, PrototypeData>>(cache, out var interfaceData))
foreach (var data in _interfaceData)
{
_interfaceData = interfaceData;
return;
_interfaces[data.UiKey] = data;
}
var data = serializer.ReadDataFieldCached("interfaces", new List<PrototypeData>());
interfaceData = new Dictionary<object, PrototypeData>();
foreach (var prototypeData in data)
{
interfaceData[prototypeData.UiKey] = prototypeData;
}
serializer.SetCacheData(cache, interfaceData);
_interfaceData = interfaceData;
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel,
@@ -81,7 +71,7 @@ namespace Robust.Client.GameObjects
private void OpenInterface(BoundInterfaceMessageWrapMessage wrapped)
{
var data = _interfaceData[wrapped.UiKey];
var data = _interfaces[wrapped.UiKey];
// TODO: This type should be cached, but I'm too lazy.
var type = _reflectionManager.LooseGetType(data.ClientType);
var boundInterface = (BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[]{this, wrapped.UiKey});

View File

@@ -9,13 +9,13 @@ namespace Robust.Client.GameObjects
public static class EntityManagerExt
{
public static void RaisePredictiveEvent<T>(this IEntityManager entityManager, T msg)
where T : EntitySystemMessage
where T : EntityEventArgs
{
var localPlayer = IoCManager.Resolve<IPlayerManager>().LocalPlayer;
DebugTools.AssertNotNull(localPlayer);
var sequence = IoCManager.Resolve<IClientGameStateManager>().SystemMessageDispatched(msg);
entityManager.EntityNetManager.SendSystemNetworkMessage(msg, sequence);
entityManager.EntityNetManager?.SendSystemNetworkMessage(msg, sequence);
var eventArgs = new EntitySessionEventArgs(localPlayer!.Session);

View File

@@ -1,51 +1,33 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
internal sealed class AppearanceSystem : EntitySystem
{
private readonly Queue<AppearanceComponent> _updatesQueued = new();
private readonly Queue<AppearanceComponent> _queuedUpdates = new();
public void EnqueueUpdate(AppearanceComponent component)
{
_queuedUpdates.Enqueue(component);
}
public override void FrameUpdate(float frameTime)
{
while (_updatesQueued.TryDequeue(out var appearance))
while (_queuedUpdates.TryDequeue(out var appearance))
{
UpdateComponent(appearance);
if (appearance.Deleted)
return;
foreach (var visualizer in appearance.Visualizers)
{
visualizer.OnChangeData(appearance);
}
appearance.UnmarkDirty();
}
}
private static void UpdateComponent(AppearanceComponent component)
{
if (component.Deleted)
return;
foreach (var visualizer in component.Visualizers)
{
switch (visualizer)
{
case AppearanceComponent.SpriteLayerToggle spriteLayerToggle:
UpdateSpriteLayerToggle(component, spriteLayerToggle);
break;
default:
visualizer.OnChangeData(component);
break;
}
}
}
private static void UpdateSpriteLayerToggle(AppearanceComponent component, AppearanceComponent.SpriteLayerToggle toggle)
{
component.TryGetData(toggle.Key, out bool visible);
var sprite = component.Owner.GetComponent<SpriteComponent>();
sprite.LayerSetVisible(toggle.SpriteLayer, visible);
}
public void EnqueueAppearanceUpdate(AppearanceComponent component)
{
_updatesQueued.Enqueue(component);
}
}
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Client.Audio;
@@ -9,15 +8,15 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
public class AudioSystem : EntitySystem
public class AudioSystem : EntitySystem, IAudioSystem
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
@@ -29,8 +28,6 @@ namespace Robust.Client.GameObjects
private readonly List<PlayingStream> _playingClydeStreams = new();
public int OcclusionCollisionMask;
/// <inheritdoc />
public override void Initialize()
{
@@ -40,9 +37,21 @@ namespace Robust.Client.GameObjects
SubscribeNetworkEvent<PlayAudioPositionalMessage>(PlayAudioPositionalHandler);
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
}
public override void Shutdown()
{
base.Shutdown();
UnsubscribeNetworkEvent<PlayAudioEntityMessage>();
UnsubscribeNetworkEvent<PlayAudioGlobalMessage>();
UnsubscribeNetworkEvent<PlayAudioPositionalMessage>();
UnsubscribeNetworkEvent<StopAudioMessageClient>();
UnsubscribeLocalEvent<SoundSystem.QueryAudioSystem>();
}
private void StopAudioMessageHandler(StopAudioMessageClient ev)
{
var stream = _playingClydeStreams.Find(p => p.NetIdentifier == ev.Identifier);
@@ -166,6 +175,12 @@ namespace Robust.Client.GameObjects
Logger.Warning("Interrupting positional audio, can't set position.");
stream.Source.StopPlaying();
}
if (stream.TrackingEntity != null)
{
stream.Source.SetVelocity(stream.TrackingEntity.GlobalLinearVelocity());
}
}
}
}
@@ -182,7 +197,6 @@ namespace Robust.Client.GameObjects
{
stream.Source.Dispose();
stream.Done = true;
stream.DoPlaybackDone();
}
/// <summary>
@@ -190,7 +204,7 @@ namespace Robust.Client.GameObjects
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream? Play(string filename, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(string filename, AudioParams? audioParams = null)
{
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
{
@@ -206,7 +220,7 @@ namespace Robust.Client.GameObjects
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream Play(AudioStream stream, AudioParams? audioParams = null)
private IPlayingAudioStream Play(AudioStream stream, AudioParams? audioParams = null)
{
var source = _clyde.CreateAudioSource(stream);
ApplyAudioParams(audioParams, source);
@@ -228,7 +242,7 @@ namespace Robust.Client.GameObjects
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream? Play(string filename, IEntity entity, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(string filename, IEntity entity, AudioParams? audioParams = null)
{
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
{
@@ -245,7 +259,7 @@ namespace Robust.Client.GameObjects
/// <param name="stream">The audio stream to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream? Play(AudioStream stream, IEntity entity, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(AudioStream stream, IEntity entity, AudioParams? audioParams = null)
{
var source = _clyde.CreateAudioSource(stream);
if (!source.SetPosition(entity.Transform.WorldPosition))
@@ -274,7 +288,7 @@ namespace Robust.Client.GameObjects
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream? Play(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
{
@@ -291,7 +305,7 @@ namespace Robust.Client.GameObjects
/// <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>
public IPlayingAudioStream? Play(AudioStream stream, EntityCoordinates coordinates,
private IPlayingAudioStream? Play(AudioStream stream, EntityCoordinates coordinates,
AudioParams? audioParams = null)
{
var source = _clyde.CreateAudioSource(stream);
@@ -341,92 +355,30 @@ namespace Robust.Client.GameObjects
{
Source.StopPlaying();
}
public event Action? PlaybackDone;
public void DoPlaybackDone()
{
PlaybackDone?.Invoke();
}
}
}
public interface IPlayingAudioStream
{
void Stop();
event Action PlaybackDone;
}
public static class AudioSystemExtensions
{
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this IEntity entity,
string filename,
AudioParams? audioParams,
AudioSystem? audioSystem = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(filename, entity, audioParams);
}
/// <summary>
/// Play an audio stream following an entity.
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this IEntity entity,
AudioStream stream,
AudioParams? audioParams = null,
AudioSystem? audioSystem = null)
/// <inheritdoc />
public int DefaultSoundRange => 25;
/// <inheritdoc />
public int OcclusionCollisionMask { get; set; }
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, AudioParams? audioParams = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(stream, entity, audioParams);
return Play(filename, audioParams);
}
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this EntityCoordinates coordinates,
string filename,
AudioParams? audioParams = null,
AudioSystem? audioSystem = null)
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(filename, coordinates, audioParams);
return Play(filename, entity, audioParams);
}
/// <summary>
/// Play an audio stream at a static position.
/// </summary>
/// <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>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this EntityCoordinates coordinates,
AudioStream stream,
AudioParams? audioParams = null,
AudioSystem? audioSystem = null)
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(stream, coordinates, audioParams);
return Play(filename, coordinates, audioParams);
}
}
}

View File

@@ -4,7 +4,7 @@ using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
public class ContainerSystem : EntitySystem
public class ClientContainerSystem : ContainerSystem
{
private readonly HashSet<IEntity> _updateQueue = new();
@@ -91,14 +91,4 @@ namespace Robust.Client.GameObjects
}
}
}
internal readonly struct UpdateContainerOcclusionMessage
{
public UpdateContainerOcclusionMessage(IEntity entity)
{
Entity = entity;
}
public IEntity Entity { get; }
}
}

View File

@@ -6,6 +6,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
@@ -20,10 +21,11 @@ namespace Robust.Client.GameObjects
{
[Dependency] private readonly IMapManager _mapManager = default!;
private readonly Queue<IEntity> _dirtyEntities = new();
private readonly Queue<EntityUid> _dirtyEntities = new();
private uint _updateGeneration;
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
@@ -32,6 +34,18 @@ namespace Robust.Client.GameObjects
UpdatesAfter.Add(typeof(PhysicsSystem));
SubscribeLocalEvent<OccluderDirtyEvent>(HandleDirtyEvent);
SubscribeLocalEvent<ClientOccluderComponent, SnapGridPositionChangedEvent>(HandleSnapGridMove);
}
/// <inheritdoc />
public override void Shutdown()
{
UnsubscribeLocalEvent<OccluderDirtyEvent>();
UnsubscribeLocalEvent<ClientOccluderComponent, SnapGridPositionChangedEvent>(HandleSnapGridMove);
base.Shutdown();
}
public override void FrameUpdate(float frameTime)
@@ -47,8 +61,8 @@ namespace Robust.Client.GameObjects
while (_dirtyEntities.TryDequeue(out var entity))
{
if (!entity.Deleted
&& entity.TryGetComponent(out ClientOccluderComponent? occluder)
if (EntityManager.EntityExists(entity)
&& ComponentManager.TryGetComponent(entity, out ClientOccluderComponent? occluder)
&& occluder.UpdateGeneration != _updateGeneration)
{
occluder.Update();
@@ -58,6 +72,11 @@ namespace Robust.Client.GameObjects
}
}
private static void HandleSnapGridMove(EntityUid uid, ClientOccluderComponent component, SnapGridPositionChangedEvent args)
{
component.SnapGridOnPositionChanged();
}
private void HandleDirtyEvent(OccluderDirtyEvent ev)
{
var sender = ev.Sender;
@@ -65,13 +84,14 @@ namespace Robust.Client.GameObjects
sender.TryGetComponent(out ClientOccluderComponent? iconSmooth)
&& iconSmooth.Running)
{
var snapGrid = sender.GetComponent<SnapGridComponent>();
var grid1 = _mapManager.GetGrid(sender.Transform.GridID);
var coords = sender.Transform.Coordinates;
_dirtyEntities.Enqueue(sender);
AddValidEntities(snapGrid.GetInDir(Direction.North));
AddValidEntities(snapGrid.GetInDir(Direction.South));
AddValidEntities(snapGrid.GetInDir(Direction.East));
AddValidEntities(snapGrid.GetInDir(Direction.West));
_dirtyEntities.Enqueue(sender.Uid);
AddValidEntities(grid1.GetInDir(coords, Direction.North));
AddValidEntities(grid1.GetInDir(coords, Direction.South));
AddValidEntities(grid1.GetInDir(coords, Direction.East));
AddValidEntities(grid1.GetInDir(coords, Direction.West));
}
// Entity is no longer valid, update around the last position it was at.
@@ -79,44 +99,37 @@ namespace Robust.Client.GameObjects
{
var pos = ev.LastPosition.Value.pos;
AddValidEntities(grid.GetSnapGridCell(pos + new Vector2i(1, 0), ev.Offset));
AddValidEntities(grid.GetSnapGridCell(pos + new Vector2i(-1, 0), ev.Offset));
AddValidEntities(grid.GetSnapGridCell(pos + new Vector2i(0, 1), ev.Offset));
AddValidEntities(grid.GetSnapGridCell(pos + new Vector2i(0, -1), ev.Offset));
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(1, 0)));
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(-1, 0)));
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(0, 1)));
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(0, -1)));
}
}
private void AddValidEntities(IEnumerable<IEntity> candidates)
private void AddValidEntities(IEnumerable<EntityUid> candidates)
{
foreach (var entity in candidates)
{
if (entity.HasComponent<ClientOccluderComponent>())
if (ComponentManager.HasComponent<ClientOccluderComponent>(entity))
{
_dirtyEntities.Enqueue(entity);
}
}
}
private void AddValidEntities(IEnumerable<IComponent> candidates)
{
AddValidEntities(candidates.Select(c => c.Owner));
}
}
/// <summary>
/// Event raised by a <see cref="ClientOccluderComponent"/> when it needs to be recalculated.
/// </summary>
internal sealed class OccluderDirtyEvent : EntitySystemMessage
internal sealed class OccluderDirtyEvent : EntityEventArgs
{
public OccluderDirtyEvent(IEntity sender, (GridId grid, Vector2i pos)? lastPosition, SnapGridOffset offset)
public OccluderDirtyEvent(IEntity sender, (GridId grid, Vector2i pos)? lastPosition)
{
LastPosition = lastPosition;
Offset = offset;
Sender = sender;
}
public (GridId grid, Vector2i pos)? LastPosition { get; }
public SnapGridOffset Offset { get; }
public IEntity Sender { get; }
}
}

View File

@@ -11,6 +11,7 @@ using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Enums;
namespace Robust.Client.GameObjects
{
@@ -42,7 +43,7 @@ namespace Robust.Client.GameObjects
{
base.Shutdown();
overlayManager.RemoveOverlay("EffectSystem");
overlayManager.RemoveOverlay(typeof(EffectOverlay));
}
public void CreateEffect(EffectSystemMessage message)
@@ -329,7 +330,6 @@ namespace Robust.Client.GameObjects
{
private readonly IPlayerManager _playerManager;
public override bool AlwaysDirty => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _unshadedShader;
@@ -337,8 +337,7 @@ namespace Robust.Client.GameObjects
private readonly IMapManager _mapManager;
private readonly IEntityManager _entityManager;
public EffectOverlay(EffectSystem owner, IPrototypeManager protoMan, IMapManager mapMan, IPlayerManager playerMan, IEntityManager entityManager) : base(
"EffectSystem")
public EffectOverlay(EffectSystem owner, IPrototypeManager protoMan, IMapManager mapMan, IPlayerManager playerMan, IEntityManager entityManager)
{
_owner = owner;
_unshadedShader = protoMan.Index<ShaderPrototype>("unshaded").Instance();
@@ -347,11 +346,11 @@ namespace Robust.Client.GameObjects
_entityManager = entityManager;
}
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
protected internal override void Draw(in OverlayDrawArgs args)
{
var map = _owner.eyeManager.CurrentMap;
var worldHandle = (DrawingHandleWorld) handle;
var worldHandle = args.WorldHandle;
ShaderInstance? currentShader = null;
var player = _playerManager.LocalPlayer?.ControlledEntity;

View File

@@ -97,7 +97,7 @@ namespace Robust.Client.GameObjects
private void DispatchInputCommand(FullInputCmdMessage message)
{
_stateManager.InputCommandDispatched(message);
EntityNetworkManager.SendSystemNetworkMessage(message, message.InputSequence);
EntityManager.EntityNetManager?.SendSystemNetworkMessage(message, message.InputSequence);
}
public override void Initialize()
@@ -157,7 +157,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Entity system message that is raised when the player changes attached entities.
/// </summary>
public class PlayerAttachSysMessage : EntitySystemMessage
public class PlayerAttachSysMessage : EntityEventArgs
{
/// <summary>
/// New entity the player is attached to.

View File

@@ -19,19 +19,19 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
private readonly Dictionary<MapId, MapTrees> _mapTrees = new();
private readonly Dictionary<MapId, Dictionary<GridId, MapTrees>> _gridTrees = new();
private readonly List<SpriteComponent> _spriteQueue = new();
private readonly List<PointLightComponent> _lightQueue = new();
internal DynamicTree<SpriteComponent> GetSpriteTreeForMap(MapId map)
internal DynamicTree<SpriteComponent> GetSpriteTreeForMap(MapId map, GridId grid)
{
return _mapTrees[map].SpriteTree;
return _gridTrees[map][grid].SpriteTree;
}
internal DynamicTree<PointLightComponent> GetLightTreeForMap(MapId map)
internal DynamicTree<PointLightComponent> GetLightTreeForMap(MapId map, GridId grid)
{
return _mapTrees[map].LightTree;
return _gridTrees[map][grid].LightTree;
}
public override void Initialize()
@@ -44,6 +44,8 @@ namespace Robust.Client.GameObjects
_mapManager.MapCreated += MapManagerOnMapCreated;
_mapManager.MapDestroyed += MapManagerOnMapDestroyed;
_mapManager.OnGridCreated += MapManagerOnGridCreated;
_mapManager.OnGridRemoved += MapManagerOnGridRemoved;
SubscribeLocalEvent<EntMapIdChangedMessage>(EntMapIdChanged);
SubscribeLocalEvent<MoveEvent>(EntMoved);
@@ -53,18 +55,40 @@ namespace Robust.Client.GameObjects
SubscribeLocalEvent<RenderTreeRemoveLightMessage>(RemoveLight);
}
public override void Shutdown()
{
base.Shutdown();
_mapManager.MapCreated -= MapManagerOnMapCreated;
_mapManager.MapDestroyed -= MapManagerOnMapDestroyed;
_mapManager.OnGridCreated -= MapManagerOnGridCreated;
_mapManager.OnGridRemoved -= MapManagerOnGridRemoved;
UnsubscribeLocalEvent<EntMapIdChangedMessage>();
UnsubscribeLocalEvent<MoveEvent>();
UnsubscribeLocalEvent<EntParentChangedMessage>();
UnsubscribeLocalEvent<PointLightRadiusChangedMessage>();
UnsubscribeLocalEvent<RenderTreeRemoveSpriteMessage>();
UnsubscribeLocalEvent<RenderTreeRemoveLightMessage>();
}
// For these next 2 methods (the Remove* ones):
// If the Transform is removed BEFORE the Sprite/Light,
// then the MapIdChanged code will handle and remove it (because MapId gets set to nullspace).
// Otherwise these will still have their past MapId and that's all we need..
private void RemoveLight(RenderTreeRemoveLightMessage ev)
{
_mapTrees[ev.Map].LightTree.Remove(ev.Light);
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Map, MapTrees.LightAabbFunc(ev.Light), true))
{
_gridTrees[ev.Map][gridId].LightTree.Remove(ev.Light);
}
}
private void RemoveSprite(RenderTreeRemoveSpriteMessage ev)
{
_mapTrees[ev.Map].SpriteTree.Remove(ev.Sprite);
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Map, MapTrees.SpriteAabbFunc(ev.Sprite), true))
{
_gridTrees[ev.Map][gridId].SpriteTree.Remove(ev.Sprite);
}
}
private void PointLightRadiusChanged(PointLightRadiusChangedMessage ev)
@@ -119,27 +143,56 @@ namespace Robust.Client.GameObjects
{
// Nullspace is a valid map ID for stuff to have but we also aren't gonna bother indexing it.
// So that's why there's a GetValueOrDefault.
var oldMapTrees = _mapTrees.GetValueOrDefault(ev.OldMapId);
var newMapTrees = _mapTrees.GetValueOrDefault(ev.Entity.Transform.MapID);
var oldMapTrees = _gridTrees.GetValueOrDefault(ev.OldMapId);
var newMapTrees = _gridTrees.GetValueOrDefault(ev.Entity.Transform.MapID);
// TODO: MMMM probably a better way to do this.
if (ev.Entity.TryGetComponent(out SpriteComponent? sprite))
{
oldMapTrees?.SpriteTree.Remove(sprite);
if (oldMapTrees != null)
{
foreach (var (_, gridTree) in oldMapTrees)
{
gridTree.SpriteTree.Remove(sprite);
}
}
newMapTrees?.SpriteTree.AddOrUpdate(sprite);
var bounds = MapTrees.SpriteAabbFunc(sprite);
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Entity.Transform.MapID, bounds, true))
{
var gridBounds = gridId == GridId.Invalid
? bounds : bounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
newMapTrees?[gridId].SpriteTree.AddOrUpdate(sprite, gridBounds);
}
}
if (ev.Entity.TryGetComponent(out PointLightComponent? light))
{
oldMapTrees?.LightTree.Remove(light);
if (oldMapTrees != null)
{
foreach (var (_, gridTree) in oldMapTrees)
{
gridTree.LightTree.Remove(light);
}
}
newMapTrees?.LightTree.AddOrUpdate(light);
var bounds = MapTrees.LightAabbFunc(light);
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Entity.Transform.MapID, bounds, true))
{
var gridBounds = gridId == GridId.Invalid
? bounds : bounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
newMapTrees?[gridId].LightTree.AddOrUpdate(light, gridBounds);
}
}
}
private void MapManagerOnMapDestroyed(object? sender, MapEventArgs e)
{
_mapTrees.Remove(e.Map);
_gridTrees.Remove(e.Map);
}
private void MapManagerOnMapCreated(object? sender, MapEventArgs e)
@@ -149,36 +202,59 @@ namespace Robust.Client.GameObjects
return;
}
_mapTrees.Add(e.Map, new MapTrees());
_gridTrees.Add(e.Map, new Dictionary<GridId, MapTrees>
{
{GridId.Invalid, new MapTrees()}
});
}
private void MapManagerOnGridCreated(MapId mapId, GridId gridId)
{
_gridTrees[mapId].Add(gridId, new MapTrees());
}
private void MapManagerOnGridRemoved(MapId mapId, GridId gridId)
{
_gridTrees[mapId].Remove(gridId);
}
public override void FrameUpdate(float frameTime)
{
foreach (var queuedUpdateSprite in _spriteQueue)
{
var transform = queuedUpdateSprite.Owner.Transform;
var map = transform.MapID;
var map = queuedUpdateSprite.Owner.Transform.MapID;
if (map == MapId.Nullspace)
{
continue;
}
var updateMapTree = _mapTrees[map].SpriteTree;
updateMapTree.AddOrUpdate(queuedUpdateSprite);
var mapTree = _gridTrees[map];
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map,
MapTrees.SpriteAabbFunc(queuedUpdateSprite), true))
{
mapTree[gridId].SpriteTree.AddOrUpdate(queuedUpdateSprite);
}
queuedUpdateSprite.TreeUpdateQueued = false;
}
foreach (var queuedUpdateLight in _lightQueue)
{
var transform = queuedUpdateLight.Owner.Transform;
var map = transform.MapID;
var map = queuedUpdateLight.Owner.Transform.MapID;
if (map == MapId.Nullspace)
{
continue;
}
var updateMapTree = _mapTrees[map].LightTree;
updateMapTree.AddOrUpdate(queuedUpdateLight);
var mapTree = _gridTrees[map];
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map,
MapTrees.LightAabbFunc(queuedUpdateLight), true))
{
mapTree[gridId].LightTree.AddOrUpdate(queuedUpdateLight);
}
queuedUpdateLight.TreeUpdateQueued = false;
}
@@ -197,14 +273,14 @@ namespace Robust.Client.GameObjects
LightTree = new DynamicTree<PointLightComponent>(LightAabbFunc);
}
private static Box2 SpriteAabbFunc(in SpriteComponent value)
internal static Box2 SpriteAabbFunc(in SpriteComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
return new Box2(worldPos, worldPos);
}
private static Box2 LightAabbFunc(in PointLightComponent value)
internal static Box2 LightAabbFunc(in PointLightComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;

View File

@@ -3,6 +3,7 @@ using Robust.Client.Graphics;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects
{
@@ -13,6 +14,7 @@ namespace Robust.Client.GameObjects
public class SpriteSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
/// <inheritdoc />
public override void FrameUpdate(float frameTime)
@@ -29,18 +31,32 @@ namespace Robust.Client.GameObjects
return;
}
var mapTree = renderTreeSystem.GetSpriteTreeForMap(currentMap);
mapTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
foreach (var gridId in _mapManager.FindGridIdsIntersecting(currentMap, pvsBounds, true))
{
if (value.IsInert)
Box2 gridBounds;
if (gridId == GridId.Invalid)
{
return true;
gridBounds = pvsBounds;
}
else
{
gridBounds = pvsBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
}
value.FrameUpdate(state);
return true;
}, pvsBounds, approx: true);
var mapTree = renderTreeSystem.GetSpriteTreeForMap(currentMap, gridId);
mapTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
{
if (value.IsInert)
{
return true;
}
value.FrameUpdate(state);
return true;
}, gridBounds, approx: true);
}
}
}
}

View File

@@ -3,10 +3,8 @@ using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
public interface IClientEntityManager : IEntityManager
public interface IClientEntityManager : IEntityManager, IEntityNetworkManager
{
/// <returns>The list of new entities created.</returns>
List<EntityUid> ApplyEntityStates(EntityState[]? curEntStates, IEnumerable<EntityUid>? deletions,
EntityState[]? nextEntStates);
}
}

View File

@@ -0,0 +1,15 @@
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
internal interface IClientEntityManagerInternal : IClientEntityManager
{
// These methods are used by the Game State Manager.
IEntity CreateEntity(string? prototypeName, EntityUid? uid = null);
void InitializeEntity(IEntity entity);
void StartEntity(IEntity entity);
}
}

View File

@@ -1,14 +1,18 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Client.Map;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Network.Messages;
using Robust.Client.Player;
using Robust.Client.Timing;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Log;
@@ -27,20 +31,25 @@ namespace Robust.Client.GameStates
private uint _nextInputCmdSeq = 1;
private readonly Queue<FullInputCmdMessage> _pendingInputs = new();
private readonly Queue<(uint sequence, GameTick sourceTick, EntitySystemMessage msg, object sessionMsg)>
private readonly Queue<(uint sequence, GameTick sourceTick, EntityEventArgs msg, object sessionMsg)>
_pendingSystemMessages
= new();
[Dependency] private readonly IClientEntityManager _entities = default!;
[Dependency] private readonly IComponentFactory _compFactory = default!;
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IPlayerManager _players = default!;
[Dependency] private readonly IClientNetManager _network = default!;
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IClientMapManager _mapManager = default!;
[Dependency] private readonly IClientGameTiming _timing = default!;
[Dependency] private readonly INetConfigurationManager _config = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly IComponentManager _componentManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
#if EXCEPTION_TOLERANCE
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
#endif
/// <inheritdoc />
public int MinBufferSize => _processor.MinBufferSize;
@@ -126,7 +135,7 @@ namespace Robust.Client.GameStates
_nextInputCmdSeq++;
}
public uint SystemMessageDispatched<T>(T message) where T : EntitySystemMessage
public uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs
{
if (!Predicting)
{
@@ -242,65 +251,69 @@ namespace Robust.Client.GameStates
if (!Predicting) return;
using var _ = _timing.StartPastPredictionArea();
if (_pendingInputs.Count > 0)
using(var _ = _timing.StartPastPredictionArea())
{
Logger.DebugS(CVars.NetPredict.Name, "CL> Predicted:");
if (_pendingInputs.Count > 0)
{
Logger.DebugS(CVars.NetPredict.Name, "CL> Predicted:");
}
var pendingInputEnumerator = _pendingInputs.GetEnumerator();
var pendingMessagesEnumerator = _pendingSystemMessages.GetEnumerator();
var hasPendingInput = pendingInputEnumerator.MoveNext();
var hasPendingMessage = pendingMessagesEnumerator.MoveNext();
var ping = _network.ServerChannel!.Ping / 1000f + PredictLagBias; // seconds.
var targetTick = _timing.CurTick.Value + _processor.TargetBufferSize +
(int) Math.Ceiling(_timing.TickRate * ping) + PredictTickBias;
// Logger.DebugS("net.predict", $"Predicting from {_lastProcessedTick} to {targetTick}");
for (var t = _lastProcessedTick.Value + 1; t <= targetTick; t++)
{
var tick = new GameTick(t);
_timing.CurTick = tick;
while (hasPendingInput && pendingInputEnumerator.Current.Tick <= tick)
{
var inputCmd = pendingInputEnumerator.Current;
_inputManager.NetworkBindMap.TryGetKeyFunction(inputCmd.InputFunctionId, out var boundFunc);
Logger.DebugS(CVars.NetPredict.Name,
$" seq={inputCmd.InputSequence}, sub={inputCmd.SubTick}, dTick={tick}, func={boundFunc.FunctionName}, " +
$"state={inputCmd.State}");
input.PredictInputCommand(inputCmd);
hasPendingInput = pendingInputEnumerator.MoveNext();
}
while (hasPendingMessage && pendingMessagesEnumerator.Current.sourceTick <= tick)
{
var msg = pendingMessagesEnumerator.Current.msg;
_entities.EventBus.RaiseEvent(EventSource.Local, msg);
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.sessionMsg);
hasPendingMessage = pendingMessagesEnumerator.MoveNext();
}
if (t != targetTick)
{
// Don't run EntitySystemManager.TickUpdate if this is the target tick,
// because the rest of the main loop will call into it with the target tick later,
// and it won't be a past prediction.
_entitySystemManager.TickUpdate((float) _timing.TickPeriod.TotalSeconds);
((IBroadcastEventBusInternal) _entities.EventBus).ProcessEventQueue();
}
}
}
var pendingInputEnumerator = _pendingInputs.GetEnumerator();
var pendingMessagesEnumerator = _pendingSystemMessages.GetEnumerator();
var hasPendingInput = pendingInputEnumerator.MoveNext();
var hasPendingMessage = pendingMessagesEnumerator.MoveNext();
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds);
var ping = _network.ServerChannel!.Ping / 1000f + PredictLagBias; // seconds.
var targetTick = _timing.CurTick.Value + _processor.TargetBufferSize +
(int) Math.Ceiling(_timing.TickRate * ping) + PredictTickBias;
// Logger.DebugS("net.predict", $"Predicting from {_lastProcessedTick} to {targetTick}");
for (var t = _lastProcessedTick.Value + 1; t <= targetTick; t++)
{
var tick = new GameTick(t);
_timing.CurTick = tick;
while (hasPendingInput && pendingInputEnumerator.Current.Tick <= tick)
{
var inputCmd = pendingInputEnumerator.Current;
_inputManager.NetworkBindMap.TryGetKeyFunction(inputCmd.InputFunctionId, out var boundFunc);
Logger.DebugS(CVars.NetPredict.Name,
$" seq={inputCmd.InputSequence}, sub={inputCmd.SubTick}, dTick={tick}, func={boundFunc.FunctionName}, " +
$"state={inputCmd.State}");
input.PredictInputCommand(inputCmd);
hasPendingInput = pendingInputEnumerator.MoveNext();
}
while (hasPendingMessage && pendingMessagesEnumerator.Current.sourceTick <= tick)
{
var msg = pendingMessagesEnumerator.Current.msg;
_entities.EventBus.RaiseEvent(EventSource.Local, msg);
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.sessionMsg);
hasPendingMessage = pendingMessagesEnumerator.MoveNext();
}
if (t != targetTick)
{
// Don't run EntitySystemManager.Update if this is the target tick,
// because the rest of the main loop will call into it with the target tick later,
// and it won't be a past prediction.
_entitySystemManager.Update((float) _timing.TickPeriod.TotalSeconds);
((IEntityEventBus) _entities.EventBus).ProcessEventQueue();
}
}
_lookup.Update();
}
private void ResetPredictedEntities(GameTick curTick)
@@ -381,7 +394,7 @@ namespace Robust.Client.GameStates
{
_config.TickProcessMessages();
_mapManager.ApplyGameStatePre(curState.MapData);
var createdEntities = _entities.ApplyEntityStates(curState.EntityStates, curState.EntityDeletions,
var createdEntities = ApplyEntityStates(curState.EntityStates, curState.EntityDeletions,
nextState?.EntityStates);
_players.ApplyPlayerStates(curState.PlayerStates);
_mapManager.ApplyGameStatePost(curState.MapData);
@@ -389,6 +402,218 @@ namespace Robust.Client.GameStates
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState));
return createdEntities;
}
private List<EntityUid> ApplyEntityStates(EntityState[]? curEntStates, IEnumerable<EntityUid>? deletions,
EntityState[]? nextEntStates)
{
var toApply = new Dictionary<IEntity, (EntityState?, EntityState?)>();
var toInitialize = new List<Entity>();
var created = new List<EntityUid>();
deletions ??= new EntityUid[0];
if (curEntStates != null && curEntStates.Length != 0)
{
foreach (var es in curEntStates)
{
//Known entities
if (_entities.TryGetEntity(es.Uid, out var entity))
{
toApply.Add(entity, (es, null));
}
else //Unknown entities
{
var metaState = (MetaDataComponentState?) es.ComponentStates
?.FirstOrDefault(c => c.NetID == NetIDs.META_DATA);
if (metaState == null)
{
throw new InvalidOperationException($"Server sent new entity state for {es.Uid} without metadata component!");
}
var newEntity = (Entity)_entities.CreateEntity(metaState.PrototypeId, es.Uid);
toApply.Add(newEntity, (es, null));
toInitialize.Add(newEntity);
created.Add(newEntity.Uid);
}
}
}
if (nextEntStates != null && nextEntStates.Length != 0)
{
foreach (var es in nextEntStates)
{
if (_entities.TryGetEntity(es.Uid, out var entity))
{
if (toApply.TryGetValue(entity, out var state))
{
toApply[entity] = (state.Item1, es);
}
else
{
toApply[entity] = (null, es);
}
}
}
}
// Make sure this is done after all entities have been instantiated.
foreach (var kvStates in toApply)
{
var ent = kvStates.Key;
var entity = (Entity) ent;
HandleEntityState(entity.EntityManager.ComponentManager, entity, kvStates.Value.Item1,
kvStates.Value.Item2);
}
foreach (var id in deletions)
{
_entities.DeleteEntity(id);
}
#if EXCEPTION_TOLERANCE
HashSet<Entity> brokenEnts = new HashSet<Entity>();
#endif
foreach (var entity in toInitialize)
{
#if EXCEPTION_TOLERANCE
try
{
#endif
_entities.InitializeEntity(entity);
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
Logger.ErrorS("state", $"Server entity threw in Init: uid={entity.Uid}, proto={entity.Prototype}\n{e}");
brokenEnts.Add(entity);
}
#endif
}
foreach (var entity in toInitialize)
{
#if EXCEPTION_TOLERANCE
if (brokenEnts.Contains(entity))
continue;
try
{
#endif
_entities.StartEntity(entity);
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
Logger.ErrorS("state", $"Server entity threw in Start: uid={entity.Uid}, proto={entity.Prototype}\n{e}");
brokenEnts.Add(entity);
}
#endif
}
foreach (var entity in toInitialize)
{
#if EXCEPTION_TOLERANCE
if (brokenEnts.Contains(entity))
continue;
#endif
}
#if EXCEPTION_TOLERANCE
foreach (var entity in brokenEnts)
{
entity.Delete();
}
#endif
return created;
}
private void HandleEntityState(IComponentManager compMan, IEntity entity, EntityState? curState,
EntityState? nextState)
{
var compStateWork = new Dictionary<uint, (ComponentState? curState, ComponentState? nextState)>();
var entityUid = entity.Uid;
if (curState?.ComponentChanges != null)
{
foreach (var compChange in curState.ComponentChanges)
{
if (compChange.Deleted)
{
if (compMan.TryGetComponent(entityUid, compChange.NetID, out var comp))
{
compMan.RemoveComponent(entityUid, comp);
}
}
else
{
if (compMan.HasComponent(entityUid, compChange.NetID))
continue;
var newComp = (Component) _compFactory.GetComponent(compChange.ComponentName!);
newComp.Owner = entity;
compMan.AddComponent(entity, newComp, true);
}
}
}
if (curState?.ComponentStates != null)
{
foreach (var compState in curState.ComponentStates)
{
compStateWork[compState.NetID] = (compState, null);
}
}
if (nextState?.ComponentStates != null)
{
foreach (var compState in nextState.ComponentStates)
{
if (compStateWork.TryGetValue(compState.NetID, out var state))
{
compStateWork[compState.NetID] = (state.curState, compState);
}
else
{
compStateWork[compState.NetID] = (null, compState);
}
}
}
foreach (var (netId, (cur, next)) in compStateWork)
{
if (compMan.TryGetComponent(entityUid, netId, out var component))
{
try
{
component.HandleComponentState(cur, next);
}
catch (Exception e)
{
var wrapper = new ComponentStateApplyException(
$"Failed to apply comp state: entity={component.Owner}, comp={component.Name}", e);
#if EXCEPTION_TOLERANCE
_runtimeLog.LogException(wrapper, "Component state apply");
#else
throw wrapper;
#endif
}
}
else
{
// The component can be null here due to interp.
// Because the NEXT state will have a new component, but this one doesn't yet.
// That's fine though.
if (cur == null)
{
continue;
}
var eUid = entityUid;
var eRegisteredNetUidName = _compFactory.GetRegistration(netId).Name;
DebugTools.Assert(
$"Component does not exist for state: entUid={eUid}, expectedNetId={netId}, expectedName={eRegisteredNetUidName}");
}
}
}
}
public class GameStateAppliedArgs : EventArgs

View File

@@ -70,6 +70,6 @@ namespace Robust.Client.GameStates
/// <param name="message">Message being dispatched.</param>
void InputCommandDispatched(FullInputCmdMessage message);
uint SystemMessageDispatched<T>(T message) where T : EntitySystemMessage;
uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs;
}
}

View File

@@ -1,13 +1,16 @@
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.GameStates
{
@@ -27,13 +30,13 @@ namespace Robust.Client.GameStates
private const int TrafficHistorySize = 64; // Size of the traffic history bar in game ticks.
/// <inheritdoc />
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
public override OverlaySpace Space => OverlaySpace.ScreenSpace | OverlaySpace.WorldSpace;
private readonly Font _font;
private readonly int _lineHeight;
private readonly List<NetEntity> _netEnts = new();
public NetEntityOverlay() : base(nameof(NetEntityOverlay))
public NetEntityOverlay()
{
IoCManager.InjectDependencies(this);
var cache = IoCManager.Resolve<IResourceCache>();
@@ -42,12 +45,12 @@ namespace Robust.Client.GameStates
_gameStateManager.GameStateApplied += HandleGameStateApplied;
}
private void HandleGameStateApplied(GameStateAppliedArgs args)
{
if(_gameTiming.InPrediction) // we only care about real server states.
return;
// Shift traffic history down one
for (var i = 0; i < _netEnts.Count; i++)
{
@@ -74,7 +77,7 @@ namespace Robust.Client.GameStates
if (netEnt.Id != entityState.Uid)
continue;
//TODO: calculate size of state and record it here.
netEnt.Traffic[^1] = 1;
netEnt.LastUpdate = gameState.ToSequence;
@@ -94,15 +97,15 @@ namespace Robust.Client.GameStates
}
bool pvsEnabled = _configurationManager.GetCVar<bool>("net.pvs");
float pvsSize = _configurationManager.GetCVar<float>("net.maxupdaterange");
float pvsRange = _configurationManager.GetCVar<float>("net.maxupdaterange");
var pvsCenter = _eyeManager.CurrentEye.Position;
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsSize*2, pvsSize*2));
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsRange*2, pvsRange*2));
int timeout = _gameTiming.TickRate * 3;
for (int i = 0; i < _netEnts.Count; i++)
{
var netEnt = _netEnts[i];
if(_entityManager.EntityExists(netEnt.Id))
{
//TODO: Whoever is working on PVS remake, change the InPVS detection.
@@ -123,22 +126,58 @@ namespace Robust.Client.GameStates
_netEnts[i] = netEnt; // copy struct back
}
}
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
protected internal override void Draw(in OverlayDrawArgs args)
{
if (!_netManager.IsConnected)
return;
switch (args.Space)
{
case OverlaySpace.ScreenSpace:
DrawScreen(args);
break;
case OverlaySpace.WorldSpace:
DrawWorld(args);
break;
}
}
private void DrawWorld(in OverlayDrawArgs args)
{
bool pvsEnabled = _configurationManager.GetCVar<bool>("net.pvs");
if(!pvsEnabled)
return;
float pvsSize = _configurationManager.GetCVar<float>("net.maxupdaterange");
var pvsCenter = _eyeManager.CurrentEye.Position;
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsSize, pvsSize));
var worldHandle = args.WorldHandle;
worldHandle.DrawRect(pvsBox, Color.Red, false);
}
private void DrawScreen(in OverlayDrawArgs args)
{
// remember, 0,0 is top left of ui with +X right and +Y down
var screenHandle = (DrawingHandleScreen)handle;
var screenHandle = args.ScreenHandle;
for (int i = 0; i < _netEnts.Count; i++)
{
var netEnt = _netEnts[i];
if (!_entityManager.TryGetEntity(netEnt.Id, out var ent))
{
_netEnts.RemoveSwap(i);
i--;
continue;
}
var xPos = 100;
var yPos = 10 + _lineHeight * i;
var name = $"({netEnt.Id}) {_entityManager.GetEntity(netEnt.Id).Prototype?.ID}";
var name = $"({netEnt.Id}) {ent.Prototype?.ID}";
var color = CalcTextColor(ref netEnt);
DrawString(screenHandle, _font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
DrawTrafficBox(screenHandle, ref netEnt, xPos, yPos);
@@ -179,20 +218,19 @@ namespace Robust.Client.GameStates
return Color.Green; // Entity in PVS, but not updated recently.
}
protected override void Dispose(bool disposing)
protected override void DisposeBehavior()
{
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
base.Dispose(disposing);
base.DisposeBehavior();
}
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str, Color textColor)
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var chr in str)
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, chr, baseLine, 1, textColor);
var advance = font.DrawChar(handle, rune, baseLine, 1, textColor);
baseLine += new Vector2(advance, 0);
}
}
@@ -225,7 +263,7 @@ namespace Robust.Client.GameStates
{
if (args.Length != 1)
{
shell.WriteError("Invalid argument amount. Expected 2 arguments.");
shell.WriteError("Invalid argument amount. Expected 1 arguments.");
return;
}
@@ -238,14 +276,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if(bValue && !overlayMan.HasOverlay(nameof(NetEntityOverlay)))
if(bValue && !overlayMan.HasOverlay(typeof(NetEntityOverlay)))
{
overlayMan.AddOverlay(new NetEntityOverlay());
shell.WriteLine("Enabled network entity report overlay.");
}
else if(!bValue && overlayMan.HasOverlay(nameof(NetEntityOverlay)))
else if(!bValue && overlayMan.HasOverlay(typeof(NetEntityOverlay)))
{
overlayMan.RemoveOverlay(nameof(NetEntityOverlay));
overlayMan.RemoveOverlay(typeof(NetEntityOverlay));
shell.WriteLine("Disabled network entity report overlay.");
}
}

View File

@@ -1,7 +1,11 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Text;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Network;
@@ -34,7 +38,11 @@ namespace Robust.Client.GameStates
private readonly List<(GameTick Tick, int Payload, int lag, int interp)> _history = new(HistorySize+10);
public NetGraphOverlay() : base(nameof(NetGraphOverlay))
private int _totalHistoryPayload; // sum of all data point sizes in bytes
public EntityUid WatchEntId { get; set; }
public NetGraphOverlay()
{
IoCManager.InjectDependencies(this);
var cache = IoCManager.Resolve<IResourceCache>();
@@ -58,7 +66,73 @@ namespace Robust.Client.GameStates
// calc interp info
var interpBuff = _gameStateManager.CurrentBufferSize - _gameStateManager.MinBufferSize;
_totalHistoryPayload += sz;
_history.Add((toSeq, sz, lag, interpBuff));
// not watching an ent
if(!WatchEntId.IsValid() || WatchEntId.IsClientSide())
return;
string? entStateString = null;
string? entDelString = null;
var conShell = IoCManager.Resolve<IConsoleHost>().LocalShell;
var entStates = args.AppliedState.EntityStates;
if (entStates is not null)
{
var sb = new StringBuilder();
foreach (var entState in entStates)
{
if (entState.Uid == WatchEntId)
{
if(entState.ComponentChanges is not null)
{
sb.Append($"\n Changes:");
foreach (var compChange in entState.ComponentChanges)
{
var del = compChange.Deleted ? 'D' : 'C';
sb.Append($"\n [{del}]{compChange.NetID}:{compChange.ComponentName}");
}
}
if (entState.ComponentStates is not null)
{
sb.Append($"\n States:");
foreach (var compState in entState.ComponentStates)
{
sb.Append($"\n {compState.NetID}:{compState.GetType().Name}");
}
}
}
}
entStateString = sb.ToString();
}
var entDeletes = args.AppliedState.EntityDeletions;
if (entDeletes is not null)
{
var sb = new StringBuilder();
foreach (var entDelete in entDeletes)
{
if (entDelete == WatchEntId)
{
entDelString = "\n Deleted";
}
}
}
if (!string.IsNullOrWhiteSpace(entStateString) || !string.IsNullOrWhiteSpace(entDelString))
{
var fullString = $"watchEnt: from={args.AppliedState.FromSequence}, to={args.AppliedState.ToSequence}, eid={WatchEntId}";
if (!string.IsNullOrWhiteSpace(entStateString))
fullString += entStateString;
if (!string.IsNullOrWhiteSpace(entDelString))
fullString += entDelString;
conShell.WriteLine(fullString + "\n");
}
}
/// <inheritdoc />
@@ -67,19 +141,27 @@ namespace Robust.Client.GameStates
base.FrameUpdate(args);
var over = _history.Count - HistorySize;
if (over > 0)
if (over <= 0)
return;
for (int i = 0; i < over; i++)
{
_history.RemoveRange(0, over);
var point = _history[i];
_totalHistoryPayload -= point.Payload;
}
_history.RemoveRange(0, over);
}
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
protected internal override void Draw(in OverlayDrawArgs args)
{
// remember, 0,0 is top left of ui with +X right and +Y down
var leftMargin = 300;
var width = HistorySize;
var height = 500;
var drawSizeThreshold = Math.Min(_totalHistoryPayload / HistorySize, 300);
var handle = args.ScreenHandle;
// bottom payload line
handle.DrawLine(new Vector2(leftMargin, height), new Vector2(leftMargin + width, height), Color.DarkGray.WithAlpha(0.8f));
@@ -99,6 +181,12 @@ namespace Robust.Client.GameStates
var yoff = height - state.Payload / BytesPerPixel;
handle.DrawLine(new Vector2(xOff, height), new Vector2(xOff, yoff), Color.LightGreen.WithAlpha(0.8f));
// Draw size if above average
if (drawSizeThreshold * 1.5 < state.Payload)
{
DrawString(handle, _font, new Vector2(xOff, yoff - _font.GetLineHeight(1)), state.Payload.ToString());
}
// second tick marks
if (state.Tick.Value % _gameTiming.TickRate == 0)
{
@@ -123,6 +211,10 @@ namespace Robust.Client.GameStates
handle.DrawLine(new Vector2(xOff, height + LowerGraphOffset), new Vector2(xOff, height + LowerGraphOffset + state.interp * 6), interpColor.WithAlpha(0.8f));
}
// average payload line
var avgyoff = height - drawSizeThreshold / BytesPerPixel;
handle.DrawLine(new Vector2(leftMargin, avgyoff), new Vector2(leftMargin + width, avgyoff), Color.DarkGray.WithAlpha(0.8f));
// top payload warning line
var warnYoff = height - _warningPayloadSize / BytesPerPixel;
handle.DrawLine(new Vector2(leftMargin, warnYoff), new Vector2(leftMargin + width, warnYoff), Color.DarkGray.WithAlpha(0.8f));
@@ -132,30 +224,30 @@ namespace Robust.Client.GameStates
handle.DrawLine(new Vector2(leftMargin, midYoff), new Vector2(leftMargin + width, midYoff), Color.DarkGray.WithAlpha(0.8f));
// payload text
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin + width, warnYoff), "56K");
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin + width, midYoff), "33.6K");
DrawString(handle, _font, new Vector2(leftMargin + width, warnYoff), "56K");
DrawString(handle, _font, new Vector2(leftMargin + width, midYoff), "33.6K");
// interp text info
if(lastLagY != -1)
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin + width, lastLagY), $"{lastLagMs.ToString()}ms");
DrawString(handle, _font, new Vector2(leftMargin + width, lastLagY), $"{lastLagMs.ToString()}ms");
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
DrawString(handle, _font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
}
protected override void Dispose(bool disposing)
protected override void DisposeBehavior()
{
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
base.Dispose(disposing);
base.DisposeBehavior();
}
private void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var chr in str)
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, chr, baseLine, 1, Color.White);
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
baseLine += new Vector2(advance, 0);
}
}
@@ -183,17 +275,48 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if(bValue && !overlayMan.HasOverlay(nameof(NetGraphOverlay)))
if(bValue && !overlayMan.HasOverlay(typeof(NetGraphOverlay)))
{
overlayMan.AddOverlay(new NetGraphOverlay());
shell.WriteLine("Enabled network overlay.");
}
else if(overlayMan.HasOverlay(nameof(NetGraphOverlay)))
else if(overlayMan.HasOverlay(typeof(NetGraphOverlay)))
{
overlayMan.RemoveOverlay(nameof(NetGraphOverlay));
overlayMan.RemoveOverlay(typeof(NetGraphOverlay));
shell.WriteLine("Disabled network overlay.");
}
}
}
private class NetWatchEntCommand : IConsoleCommand
{
public string Command => "net_watchent";
public string Help => "net_watchent <0|EntityUid>";
public string Description => "Dumps all network updates for an EntityId to the console.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteError("Invalid argument amount. Expected 1 argument.");
return;
}
if (!EntityUid.TryParse(args[0], out var eValue))
{
shell.WriteError("Invalid argument: Needs to be 0 or an entityId.");
return;
}
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if (overlayMan.HasOverlay(typeof(NetGraphOverlay)))
{
var netOverlay = overlayMan.GetOverlay<NetGraphOverlay>();
netOverlay.WatchEntId = eValue;
}
}
}
}
}

View File

@@ -1,3 +1,4 @@
using Robust.Shared.Enums;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
@@ -5,6 +6,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using System;
using Robust.Shared.Timing;
namespace Robust.Client.GameStates
@@ -18,14 +20,15 @@ namespace Robust.Client.GameStates
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _shader;
public NetInterpOverlay() : base(nameof(NetInterpOverlay))
public NetInterpOverlay()
{
IoCManager.InjectDependencies(this);
_shader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
}
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
protected internal override void Draw(in OverlayDrawArgs args)
{
var handle = args.DrawingHandle;
handle.UseShader(_shader);
var worldHandle = (DrawingHandleWorld) handle;
var viewport = _eyeManager.GetWorldViewport();
@@ -85,14 +88,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if (bValue && !overlayMan.HasOverlay(nameof(NetInterpOverlay)))
if (bValue && !overlayMan.HasOverlay<NetInterpOverlay>())
{
overlayMan.AddOverlay(new NetInterpOverlay());
shell.WriteLine("Enabled network interp overlay.");
}
else if (overlayMan.HasOverlay(nameof(NetInterpOverlay)))
else if (overlayMan.HasOverlay<NetInterpOverlay>())
{
overlayMan.RemoveOverlay(nameof(NetInterpOverlay));
overlayMan.RemoveOverlay<NetInterpOverlay>();
shell.WriteLine("Disabled network interp overlay.");
}
}

View File

@@ -1,4 +1,4 @@
using JetBrains.Annotations;
using JetBrains.Annotations;
using Robust.Shared.Maths;
using Robust.Shared.Utility;

View File

@@ -50,9 +50,9 @@ namespace Robust.Client.Graphics
}
/// <inheritdoc />
public void GetViewMatrix(out Matrix3 viewMatrix)
public void GetViewMatrix(out Matrix3 viewMatrix, Vector2 renderScale)
{
var scaleMat = Matrix3.CreateScale(_scale.X, _scale.Y);
var scaleMat = Matrix3.CreateScale(_scale.X * renderScale.X, _scale.Y * renderScale.Y);
var rotMat = Matrix3.CreateRotation(_rotation);
var transMat = Matrix3.CreateTranslation(-_coords.Position);
@@ -60,9 +60,9 @@ namespace Robust.Client.Graphics
}
/// <inheritdoc />
public void GetViewMatrixInv(out Matrix3 viewMatrixInv)
public void GetViewMatrixInv(out Matrix3 viewMatrixInv, Vector2 renderScale)
{
GetViewMatrix(out var viewMatrix);
GetViewMatrix(out var viewMatrix, renderScale);
viewMatrixInv = Matrix3.Invert(viewMatrix);
}
}

View File

@@ -1,4 +1,6 @@
using Robust.Shared.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -19,6 +21,7 @@ namespace Robust.Client.Graphics
[Dependency] private readonly IClyde _displayManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
// We default to this when we get set to a null eye.
private readonly FixedEye _defaultEye = new();
@@ -32,11 +35,18 @@ namespace Robust.Client.Graphics
set => _currentEye = value;
}
public IViewportControl MainViewport { get; set; } = default!;
public void ClearCurrentEye()
{
_currentEye = _defaultEye;
}
void IEyeManager.Initialize()
{
MainViewport = _uiManager.MainViewport;
}
/// <inheritdoc />
public MapId CurrentMap => CurrentEye.Position.MapId;
@@ -49,7 +59,7 @@ namespace Robust.Client.Graphics
var topRight = ScreenToMap(new Vector2(vpSize.X, 0));
var bottomRight = ScreenToMap(vpSize);
var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y));
var left = MathHelper.Min(topLeft.X, topRight.X, bottomRight.X, bottomLeft.X);
var bottom = MathHelper.Min(topLeft.Y, topRight.Y, bottomRight.Y, bottomLeft.Y);
var right = MathHelper.Max(topLeft.X, topRight.X, bottomRight.X, bottomLeft.X);
@@ -61,16 +71,7 @@ namespace Robust.Client.Graphics
/// <inheritdoc />
public Vector2 WorldToScreen(Vector2 point)
{
var newPoint = point;
CurrentEye.GetViewMatrix(out var viewMatrix);
newPoint = viewMatrix * newPoint;
// (inlined version of UiProjMatrix)
newPoint *= new Vector2(1, -1) * PixelsPerMeter;
newPoint += _displayManager.ScreenSize / 2f;
return newPoint;
return MainViewport.WorldToScreen(point);
}
/// <inheritdoc />
@@ -115,17 +116,7 @@ namespace Robust.Client.Graphics
/// <inheritdoc />
public MapCoordinates ScreenToMap(Vector2 point)
{
var newPoint = point;
// (inlined version of UiProjMatrix^-1)
newPoint -= _displayManager.ScreenSize / 2f;
newPoint *= new Vector2(1, -1) / PixelsPerMeter;
// view matrix
CurrentEye.GetViewMatrixInv(out var viewMatrixInv);
newPoint = viewMatrixInv * newPoint;
return new MapCoordinates(newPoint, CurrentMap);
return MainViewport.ScreenToMap(point);
}
}
}

View File

@@ -41,13 +41,15 @@ namespace Robust.Client.Graphics
/// world space to camera space.
/// </summary>
/// <param name="viewMatrix">View matrix for this camera.</param>
void GetViewMatrix(out Matrix3 viewMatrix);
/// <param name="renderScale"></param>
void GetViewMatrix(out Matrix3 viewMatrix, Vector2 renderScale);
/// <summary>
/// Returns the inverted view matrix for this eye, used to convert a point from
/// camera space to world space.
/// </summary>
/// <param name="viewMatrixInv">Inverted view matrix for this camera.</param>
void GetViewMatrixInv(out Matrix3 viewMatrixInv);
/// <param name="renderScale"></param>
void GetViewMatrixInv(out Matrix3 viewMatrixInv, Vector2 renderScale);
}
}

View File

@@ -1,4 +1,5 @@
using Robust.Shared.Map;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
@@ -17,6 +18,8 @@ namespace Robust.Client.Graphics
/// </remarks>
IEye CurrentEye { get; set; }
IViewportControl MainViewport { get; set; }
/// <summary>
/// The ID of the map on which the current eye is "placed".
/// </summary>
@@ -72,5 +75,6 @@ namespace Robust.Client.Graphics
MapCoordinates ScreenToMap(Vector2 point);
void ClearCurrentEye();
void Initialize();
}
}

View File

@@ -53,7 +53,7 @@ namespace Robust.Client.Graphics.Clyde
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
_configurationManager.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
ConfigurationManager.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
}
private void _audioCreateContext()
@@ -81,7 +81,7 @@ namespace Robust.Client.Graphics.Clyde
private void _audioOpenDevice()
{
var preferredDevice = _configurationManager.GetCVar(CVars.AudioDevice);
var preferredDevice = ConfigurationManager.GetCVar(CVars.AudioDevice);
// Open device.
if (!string.IsNullOrEmpty(preferredDevice))
@@ -482,7 +482,7 @@ namespace Robust.Client.Graphics.Clyde
var (x, y) = position;
if (!ValidatePosition(x, y))
if (!AreFinite(x, y))
{
return false;
}
@@ -503,7 +503,7 @@ namespace Robust.Client.Graphics.Clyde
return true;
}
private static bool ValidatePosition(float x, float y)
private static bool AreFinite(float x, float y)
{
if (float.IsFinite(x) && float.IsFinite(y))
{
@@ -513,6 +513,22 @@ namespace Robust.Client.Graphics.Clyde
return false;
}
public void SetVelocity(Vector2 velocity)
{
_checkDisposed();
var (x, y) = velocity;
if (!AreFinite(x, y))
{
return;
}
AL.Source(SourceHandle, ALSource3f.Velocity, x, y, 0);
_checkAlError();
}
public void SetPitch(float pitch)
{
_checkDisposed();
@@ -667,7 +683,6 @@ namespace Robust.Client.Graphics.Clyde
_checkAlError();
}
private void SetOcclusionEfx(float gain, float cutoff)
{
if (FilterHandle == 0)
@@ -694,7 +709,7 @@ namespace Robust.Client.Graphics.Clyde
var (x, y) = position;
if (!ValidatePosition(x, y))
if (!AreFinite(x, y))
{
return false;
}
@@ -706,7 +721,7 @@ namespace Robust.Client.Graphics.Clyde
return true;
}
private static bool ValidatePosition(float x, float y)
private static bool AreFinite(float x, float y)
{
if (float.IsFinite(x) && float.IsFinite(y))
{
@@ -716,6 +731,22 @@ namespace Robust.Client.Graphics.Clyde
return false;
}
public void SetVelocity(Vector2 velocity)
{
_checkDisposed();
var (x, y) = velocity;
if (!AreFinite(x, y))
{
return;
}
AL.Source(SourceHandle!.Value, ALSource3f.Velocity, x, y, 0);
_checkAlError();
}
public void SetPitch(float pitch)
{
_checkDisposed();

View File

@@ -12,7 +12,8 @@ namespace Robust.Client.Graphics.Clyde
static Clyde()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
RuntimeInformation.ProcessArchitecture == Architecture.X64)
RuntimeInformation.ProcessArchitecture == Architecture.X64 &&
Environment.GetEnvironmentVariable("ROBUST_INTEGRATED_GPU") != "1")
{
try
{

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Log;
@@ -115,7 +115,7 @@ namespace Robust.Client.Graphics.Clyde
var prev = cap;
var cVarName = $"display.ogl_block_{capName}";
var block = _configurationManager.GetCVar<bool>(cVarName);
var block = ConfigurationManager.GetCVar<bool>(cVarName);
if (block)
{
@@ -146,7 +146,7 @@ namespace Robust.Client.Graphics.Clyde
foreach (var cvar in cvars)
{
_configurationManager.RegisterCVar($"display.ogl_block_{cvar}", false);
ConfigurationManager.RegisterCVar($"display.ogl_block_{cvar}", false);
}
}

View File

@@ -208,12 +208,12 @@ namespace Robust.Client.Graphics.Clyde
_setChunkDirty(grid, chunk);
}
private void _updateOnGridCreated(GridId gridId)
private void _updateOnGridCreated(MapId mapId, GridId gridId)
{
_mapChunkData.Add(gridId, new Dictionary<Vector2i, MapChunkData>());
}
private void _updateOnGridRemoved(GridId gridId)
private void _updateOnGridRemoved(MapId mapId, GridId gridId)
{
var data = _mapChunkData[gridId];
foreach (var chunkDatum in data.Values)

View File

@@ -7,6 +7,9 @@ using Robust.Client.ResourceManagement;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Enums;
namespace Robust.Client.Graphics.Clyde
{
@@ -65,67 +68,136 @@ namespace Robust.Client.Graphics.Clyde
return;
}
RenderOverlays(OverlaySpace.ScreenSpaceBelowWorld);
_mainViewport.Eye = _eyeManager.CurrentEye;
RenderViewport(_mainViewport);
foreach (var weak in _viewports.Values)
{
var handle = _renderHandle.DrawingHandleScreen;
var tex = _mainViewport.RenderTarget.Texture;
handle.DrawTexture(tex, (0, 0));
FlushRenderQueue();
if (weak.TryGetTarget(out var viewport) && viewport.AutomaticRender)
RenderViewport(viewport);
}
TakeScreenshot(ScreenshotType.BeforeUI);
RenderOverlays(OverlaySpace.ScreenSpace);
using (DebugGroup("UI"))
{
_userInterfaceManager.Render(_renderHandle);
FlushRenderQueue();
}
TakeScreenshot(ScreenshotType.AfterUI);
TakeScreenshot(ScreenshotType.Final);
// And finally, swap those buffers!
SwapBuffers();
}
private void RenderOverlays(OverlaySpace space)
private void RenderOverlays(Viewport vp, OverlaySpace space, in Box2 worldBox)
{
using (DebugGroup($"Overlays: {space}"))
{
var list = new List<Overlay>();
foreach (var overlay in _overlayManager.AllOverlays)
{
if ((overlay.Space & space) != 0)
{
list.Add(overlay);
}
}
list.Sort(OverlayComparer.Instance);
var list = GetOverlaysForSpace(space);
foreach (var overlay in list)
{
overlay.ClydeRender(_renderHandle, space);
}
if (overlay.RequestScreenTexture)
{
FlushRenderQueue();
UpdateOverlayScreenTexture(space, vp.RenderTarget);
}
FlushRenderQueue();
if (overlay.OverwriteTargetFrameBuffer())
{
ClearFramebuffer(default);
}
overlay.ClydeRender(_renderHandle, space, null, vp, new UIBox2i((0, 0), vp.Size), worldBox);
}
}
}
private void DrawEntitiesAndWorldOverlay(Viewport viewport, Box2 worldBounds)
private void RenderOverlaysDirect(
Viewport vp,
IViewportControl vpControl,
DrawingHandleBase handle,
OverlaySpace space,
in UIBox2i bounds)
{
var list = GetOverlaysForSpace(space);
var worldBounds = CalcWorldBounds(vp);
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, worldBounds);
foreach (var overlay in list)
{
overlay.Draw(args);
}
}
private List<Overlay> GetOverlaysForSpace(OverlaySpace space)
{
var list = new List<Overlay>();
foreach (var overlay in _overlayManager.AllOverlays)
{
if ((overlay.Space & space) != 0)
{
list.Add(overlay);
}
}
list.Sort(OverlayComparer.Instance);
return list;
}
private ClydeTexture? ScreenBufferTexture;
private GLHandle screenBufferHandle;
private Vector2 lastFrameSize;
/// <summary>
/// Sends SCREEN_TEXTURE to all overlays in the given OverlaySpace that request it.
/// </summary>
private bool UpdateOverlayScreenTexture(OverlaySpace space, RenderTexture texture)
{
//This currently does NOT consider viewports and just grabs the current screen framebuffer. This will need to be improved upon in the future.
List<Overlay> oTargets = new List<Overlay>();
foreach (var overlay in _overlayManager.AllOverlays)
{
if (overlay.RequestScreenTexture && overlay.Space == space)
{
oTargets.Add(overlay);
}
}
if (oTargets.Count > 0 && ScreenBufferTexture != null)
{
if (lastFrameSize != _framebufferSize)
{
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
GL.TexImage2D(TextureTarget.Texture2D, 0,
_hasGLSrgb ? PixelInternalFormat.Srgb8Alpha8 : PixelInternalFormat.Rgba8, _framebufferSize.X,
_framebufferSize.Y, 0,
PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
}
lastFrameSize = _framebufferSize;
CopyRenderTextureToTexture(texture, ScreenBufferTexture);
foreach (Overlay overlay in oTargets)
{
overlay.ScreenTexture = ScreenBufferTexture;
}
oTargets.Clear();
return true;
}
return false;
}
private void DrawEntities(Viewport viewport, Box2 worldBounds)
{
if (_eyeManager.CurrentMap == MapId.Nullspace || !_mapManager.HasMapEntity(_eyeManager.CurrentMap))
{
return;
}
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldBounds);
var screenSize = viewport.Size;
// So we could calculate the correct size of the entities based on the contents of their sprite...
@@ -175,7 +247,13 @@ namespace Robust.Client.Graphics.Clyde
flushed = true;
}
overlay.ClydeRender(_renderHandle, OverlaySpace.WorldSpace);
overlay.ClydeRender(
_renderHandle,
OverlaySpace.WorldSpace,
null,
viewport,
new UIBox2i((0, 0), viewport.Size),
worldBounds);
overlayIndex = j;
continue;
}
@@ -194,13 +272,13 @@ namespace Robust.Client.Graphics.Clyde
var spriteRT = spriteBB.TopRight;
// finally we can calculate screen bounding in pixels
var screenLB = _eyeManager.WorldToScreen(spriteLB);
var screenRT = _eyeManager.WorldToScreen(spriteRT);
var screenLB = viewport.WorldToLocal(spriteLB);
var screenRT = viewport.WorldToLocal(spriteRT);
// we need to scale RT a for effects like emission or highlight
// scale can be passed with PostShader as variable in future
var postShadeScale = 1.25f;
var screenSpriteSize = (Vector2i)((screenRT - screenLB) * postShadeScale).Rounded();
var screenSpriteSize = (Vector2i) ((screenRT - screenLB) * postShadeScale).Rounded();
screenSpriteSize.Y = -screenSpriteSize.Y;
// I'm not 100% sure why it works, but without it post-shader
@@ -225,8 +303,8 @@ namespace Robust.Client.Graphics.Clyde
// which is necessary for light application,
// but it's ACTUALLY drawing into the center of the render target.
var spritePos = spriteBB.Center;
var screenPos = _eyeManager.WorldToScreen(spritePos);
var (roundedX, roundedY) = roundedPos = (Vector2i)screenPos;
var screenPos = viewport.WorldToLocal(spritePos);
var (roundedX, roundedY) = roundedPos = (Vector2i) screenPos;
var flippedPos = new Vector2i(roundedX, screenSize.Y - roundedY);
flippedPos -= entityPostRenderTarget.Size / 2;
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));
@@ -267,14 +345,6 @@ namespace Robust.Client.Graphics.Clyde
_drawingSpriteList.Clear();
FlushRenderQueue();
// Cleanup remainders
foreach (var overlay in worldOverlays)
{
overlay.ClydeRender(_renderHandle, OverlaySpace.WorldSpace);
}
FlushRenderQueue();
}
[MethodImpl(MethodImplOptions.NoInlining)]
@@ -283,29 +353,44 @@ namespace Robust.Client.Graphics.Clyde
{
var spriteSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
var tree = spriteSystem.GetSpriteTreeForMap(map);
tree.QueryAabb(ref list, ((
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
in SpriteComponent value) =>
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
{
if (value.ContainerOccluded || !value.Visible)
Box2 gridBounds;
if (gridId == GridId.Invalid)
{
return true;
gridBounds = worldBounds;
}
else
{
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
}
var entity = value.Owner;
var transform = entity.Transform;
var tree = spriteSystem.GetSpriteTreeForMap(map, gridId);
ref var entry = ref state.AllocAdd();
entry.sprite = value;
entry.worldRot = transform.WorldRotation;
entry.matrix = transform.WorldMatrix;
var worldPos = entry.matrix.Transform(transform.LocalPosition);
entry.yWorldPos = worldPos.Y;
return true;
tree.QueryAabb(ref list, ((
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
in SpriteComponent value) =>
{
// TODO: Probably value in storing this as its own DynamicTree
if (value.ContainerOccluded || !value.Visible)
{
return true;
}
}), worldBounds, approx: true);
var entity = value.Owner;
var transform = entity.Transform;
ref var entry = ref state.AllocAdd();
entry.sprite = value;
entry.worldRot = transform.WorldRotation;
entry.matrix = transform.WorldMatrix;
var worldPos = entry.matrix.Transform(transform.LocalPosition);
entry.yWorldPos = worldPos.Y;
return true;
}), gridBounds, approx: true);
}
}
private void DrawSplash(IRenderHandle handle)
@@ -317,11 +402,13 @@ namespace Robust.Client.Graphics.Clyde
private void RenderViewport(Viewport viewport)
{
if (viewport.Eye == null)
if (viewport.Eye == null || viewport.Eye.Position.MapId == MapId.Nullspace)
{
return;
}
using var _ = DebugGroup($"Viewport: {viewport.Name}");
// TODO: for the love of god all this state pushing/popping needs to be cleaned up.
var oldTransform = _currentMatrixModel;
@@ -346,12 +433,11 @@ namespace Robust.Client.Graphics.Clyde
SetViewportImmediate(Box2i.FromDimensions(Vector2i.Zero, rt.Size));
_updateUniformConstants(viewport.Size);
CalcWorldMatrices(rt.Size, eye, out var proj, out var view);
CalcWorldMatrices(rt.Size, viewport.RenderScale, eye, out var proj, out var view);
SetProjViewFull(proj, view);
// Calculate world-space AABB for camera, to cull off-screen things.
var worldBounds = Box2.CenteredAround(eye.Position.Position,
_framebufferSize / (float) EyeManager.PixelsPerMeter * eye.Zoom);
var worldBounds = CalcWorldBounds(viewport);
if (_eyeManager.CurrentMap != MapId.Nullspace)
{
@@ -360,6 +446,9 @@ namespace Robust.Client.Graphics.Clyde
DrawLightsAndFov(viewport, worldBounds, eye);
}
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowWorld, worldBounds);
FlushRenderQueue();
using (DebugGroup("Grids"))
{
_drawGrids(worldBounds);
@@ -368,9 +457,11 @@ namespace Robust.Client.Graphics.Clyde
// We will also render worldspace overlays here so we can do them under / above entities as necessary
using (DebugGroup("Entities"))
{
DrawEntitiesAndWorldOverlay(viewport, worldBounds);
DrawEntities(viewport, worldBounds);
}
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowFOV, worldBounds);
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawFov)
{
ApplyFovToBuffer(viewport, eye);
@@ -401,6 +492,9 @@ namespace Robust.Client.Graphics.Clyde
viewport.WallBleedIntermediateRenderTarget2.Texture,
UIBox2.FromDimensions(Vector2.Zero, ScreenSize), new Color(1, 1, 1, 0.5f));
}
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldBounds);
FlushRenderQueue();
}
PopRenderStateFull(state);
@@ -411,6 +505,17 @@ namespace Robust.Client.Graphics.Clyde
_currentViewport = oldVp;
}
private static Box2 CalcWorldBounds(Viewport viewport)
{
var eye = viewport.Eye;
if (eye == null)
return default;
// TODO: This seems completely unfit by lacking things like rotation handling.
return Box2.CenteredAround(eye.Position.Position,
viewport.Size / viewport.RenderScale / EyeManager.PixelsPerMeter * eye.Zoom);
}
private sealed class OverlayComparer : IComparer<Overlay>
{
public static readonly OverlayComparer Instance = new();

View File

@@ -5,6 +5,7 @@ using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp.PixelFormats;
using ES20 = OpenToolkit.Graphics.ES20;
namespace Robust.Client.Graphics.Clyde
@@ -31,6 +32,27 @@ namespace Robust.Client.Graphics.Clyde
CheckGlError();
GL.BindTexture(TextureTarget.Texture2D, glHandle.Handle);
CheckGlError();
GL.ActiveTexture(TextureUnit.Texture0);
}
private void CopyRenderTextureToTexture(RenderTexture source, ClydeTexture target) {
LoadedRenderTarget sourceLoaded = RtToLoaded(source);
bool pause = sourceLoaded != _currentBoundRenderTarget;
FullStoredRendererState? store = null;
if (pause) {
store = PushRenderStateFull();
BindRenderTargetFull(source);
CheckGlError();
}
GL.BindTexture(TextureTarget.Texture2D, _loadedTextures[target.TextureId].OpenGLObject.Handle);
CheckGlError();
GL.CopyTexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 0, 0, _framebufferSize.X, _framebufferSize.Y);
CheckGlError();
if (pause && store != null) {
PopRenderStateFull((FullStoredRendererState)store);
}
}
private static long EstPixelSize(PixelInternalFormat format)

View File

@@ -3,13 +3,14 @@ using System.Collections.Generic;
using System.Buffers;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement.ResourceTypes;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using static Robust.Client.GameObjects.ClientOccluderComponent;
using OGLTextureWrapMode = OpenToolkit.Graphics.OpenGL.TextureWrapMode;
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
namespace Robust.Client.Graphics.Clyde
{
@@ -195,7 +196,6 @@ namespace Robust.Client.Graphics.Clyde
}
}
_lightSoftShaderHandle = LoadShaderHandle("/Shaders/Internal/light-soft.swsl");
_lightHardShaderHandle = LoadShaderHandle("/Shaders/Internal/light-hard.swsl");
_fovShaderHandle = LoadShaderHandle("/Shaders/Internal/fov.swsl");
@@ -361,16 +361,23 @@ namespace Robust.Client.Graphics.Clyde
FinalizeDepthDraw();
}
BindRenderTargetImmediate(RtToLoaded(viewport.LightRenderTarget));
CheckGlError();
GLClearColor(_lightManager.AmbientLightColor);
GL.Clear(ClearBufferMask.ColorBufferBit);
CheckGlError();
GL.Enable(EnableCap.StencilTest);
_isStencilling = true;
var (lightW, lightH) = GetLightMapSize(viewport.Size);
GL.Viewport(0, 0, lightW, lightH);
CheckGlError();
BindRenderTargetImmediate(RtToLoaded(viewport.LightRenderTarget));
CheckGlError();
GLClearColor(_lightManager.AmbientLightColor);
GL.ClearStencil(0xFF);
GL.StencilMask(0xFF);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.StencilBufferBit);
CheckGlError();
ApplyLightingFovToBuffer(viewport, eye);
var lightShader = _loadedShaders[_enableSoftShadows ? _lightSoftShaderHandle : _lightHardShaderHandle].Program;
lightShader.Use();
@@ -382,6 +389,11 @@ namespace Robust.Client.Graphics.Clyde
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.One);
CheckGlError();
GL.StencilFunc(StencilFunction.Equal, 0xFF, 0xFF);
CheckGlError();
GL.StencilOp(TKStencilOp.Keep, TKStencilOp.Keep, TKStencilOp.Keep);
CheckGlError();
var lastRange = float.NaN;
var lastPower = float.NaN;
var lastColor = new Color(float.NaN, float.NaN, float.NaN, float.NaN);
@@ -463,11 +475,11 @@ namespace Robust.Client.Graphics.Clyde
}
ResetBlendFunc();
GL.Disable(EnableCap.StencilTest);
_isStencilling = false;
CheckGlError();
ApplyLightingFovToBuffer(viewport, eye);
BlurOntoWalls(viewport, eye);
MergeWallLayer(viewport);
@@ -485,40 +497,55 @@ namespace Robust.Client.Graphics.Clyde
GetLightsToRender(MapId map, in Box2 worldBounds)
{
var renderingTreeSystem = _entitySystemManager.GetEntitySystem<RenderingTreeSystem>();
var lightTree = renderingTreeSystem.GetLightTreeForMap(map);
var state = (this, worldBounds, count: 0);
lightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true))
{
var transform = light.Owner.Transform;
Box2 gridBounds;
if (state.count >= LightsToRenderListSize)
if (gridId == GridId.Invalid)
{
// There are too many lights to fit in the static memory.
return false;
gridBounds = worldBounds;
}
else
{
gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
}
if (!light.Enabled || light.ContainerOccluded)
var lightTree = renderingTreeSystem.GetLightTreeForMap(map, gridId);
lightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) =>
{
var transform = light.Owner.Transform;
if (state.count >= LightsToRenderListSize)
{
// There are too many lights to fit in the static memory.
return false;
}
// TODO: Don't insert into trees for these, same as sprites.
if (!light.Enabled || light.ContainerOccluded)
{
return true;
}
var lightPos = transform.WorldMatrix.Transform(light.Offset);
var circle = new Circle(lightPos, light.Radius);
// If the light doesn't touch anywhere the camera can see, it doesn't matter.
if (!circle.Intersects(state.worldBounds))
{
return true;
}
float distanceSquared = (state.worldBounds.Center - lightPos).LengthSquared;
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
return true;
}
var lightPos = transform.WorldMatrix.Transform(light.Offset);
var circle = new Circle(lightPos, light.Radius);
// If the light doesn't touch anywhere the camera can see, it doesn't matter.
if (!circle.Intersects(state.worldBounds))
{
return true;
}
float distanceSquared = (state.worldBounds.Center - lightPos).LengthSquared;
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
return true;
}, worldBounds);
}, gridBounds);
}
if (state.count > _maxLightsPerScene)
{
@@ -577,7 +604,7 @@ namespace Robust.Client.Graphics.Clyde
// Have to scale the blurring radius based on viewport size and camera zoom.
const float refCameraHeight = 14;
var cameraSize = eye.Zoom.Y * viewport.Size.Y / EyeManager.PixelsPerMeter;
var cameraSize = eye.Zoom.Y * viewport.Size.Y * (1 / viewport.RenderScale.Y) / EyeManager.PixelsPerMeter;
// 7e-3f is just a magic factor that makes it look ok.
var factor = 7e-3f * (refCameraHeight / cameraSize);
@@ -645,6 +672,12 @@ namespace Robust.Client.Graphics.Clyde
private void ApplyFovToBuffer(Viewport viewport, IEye eye)
{
GL.Clear(ClearBufferMask.StencilBufferBit);
GL.Enable(EnableCap.StencilTest);
GL.StencilOp(OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Replace);
GL.StencilFunc(StencilFunction.Always, 1, 0xFF);
GL.StencilMask(0xFF);
// Applies FOV to the final framebuffer.
var fovShader = _loadedShaders[_fovShaderHandle].Program;
@@ -658,6 +691,10 @@ namespace Robust.Client.Graphics.Clyde
fovShader.SetUniformMaybe("center", eye.Position.Position);
DrawBlit(viewport, fovShader);
GL.StencilMask(0x00);
GL.Disable(EnableCap.StencilTest);
_isStencilling = false;
}
private void ApplyLightingFovToBuffer(Viewport viewport, IEye eye)
@@ -690,6 +727,13 @@ namespace Robust.Client.Graphics.Clyde
fovShader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
fovShader.SetUniformMaybe("center", eye.Position.Position);
GL.StencilMask(0xFF);
CheckGlError();
GL.StencilFunc(StencilFunction.Always, 0, 0);
CheckGlError();
GL.StencilOp(TKStencilOp.Keep, TKStencilOp.Keep, TKStencilOp.Replace);
CheckGlError();
DrawBlit(viewport, fovShader);
if (_hasGLSamplerObjects)
@@ -939,7 +983,8 @@ namespace Robust.Client.Graphics.Clyde
viewport.WallMaskRenderTarget = CreateRenderTarget(viewport.Size, RenderTargetColorFormat.R8,
name: $"{viewport.Name}-{nameof(viewport.WallMaskRenderTarget)}");
viewport.LightRenderTarget = CreateRenderTarget(lightMapSize, lightMapColorFormat,
viewport.LightRenderTarget = CreateRenderTarget(lightMapSize,
new RenderTargetFormatParameters(lightMapColorFormat, hasDepthStencil: true),
lightMapSampleParameters,
$"{viewport.Name}-{nameof(viewport.LightRenderTarget)}");

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@@ -6,6 +6,7 @@ using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.Log;
using SixLabors.ImageSharp.PixelFormats;
// ReSharper disable once IdentifierTypo
using RTCF = Robust.Client.Graphics.RenderTargetColorFormat;
@@ -191,7 +192,8 @@ namespace Robust.Client.Graphics.Clyde
FramebufferHandle = fbo,
Size = size,
TextureHandle = textureObject.TextureId,
MemoryPressure = pressure
MemoryPressure = pressure,
ColorFormat = format.ColorFormat
};
//GC.AddMemoryPressure(pressure);
@@ -278,6 +280,8 @@ namespace Robust.Client.Graphics.Clyde
public Vector2i Size;
public bool IsSrgb;
public RTCF ColorFormat;
// Remaining properties only apply if the render target is NOT a window.
// Handle to the framebuffer object.
public GLHandle FramebufferHandle;
@@ -302,6 +306,12 @@ namespace Robust.Client.Graphics.Clyde
}
public abstract Vector2i Size { get; }
public void CopyPixelsToMemory<T>(CopyPixelsDelegate<T> callback, UIBox2i? subRegion = null) where T : unmanaged, IPixel<T>
{
Clyde.CopyRenderTargetPixels(Handle, subRegion, callback);
}
public ClydeHandle Handle { get; }
protected virtual void Dispose(bool disposing)

View File

@@ -1,17 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.GameObjects;
using Robust.Client.Utility;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using Color = Robust.Shared.Maths.Color;
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
@@ -120,9 +116,9 @@ namespace Robust.Client.Graphics.Clyde
view = Matrix3.Identity;
}
private static void CalcWorldMatrices(in Vector2i screenSize, IEye eye, out Matrix3 proj, out Matrix3 view)
private static void CalcWorldMatrices(in Vector2i screenSize, in Vector2 renderScale, IEye eye, out Matrix3 proj, out Matrix3 view)
{
eye.GetViewMatrix(out view);
eye.GetViewMatrix(out view, renderScale);
CalcWorldProjMatrix(screenSize, out proj);
}
@@ -280,9 +276,19 @@ namespace Robust.Client.Graphics.Clyde
}
/// <summary>
/// Flush the render handle, processing and re-pooling all the command lists.
/// Flushes the render handle, processing and re-pooling all the command lists.
/// </summary>
private void FlushRenderQueue()
{
FlushBatchQueue();
// Reset renderer state.
_currentMatrixModel = Matrix3.Identity;
_queuedShader = _defaultShader.Handle;
SetScissorFull(null);
}
private void FlushBatchQueue()
{
// Finish any batches that may have been WiP.
BreakBatch();
@@ -308,11 +314,6 @@ namespace Robust.Client.Graphics.Clyde
ProcessRenderCommands();
_queuedRenderCommands.Clear();
// Reset renderer state.
_currentMatrixModel = Matrix3.Identity;
_queuedShader = _defaultShader.Handle;
SetScissorFull(null);
}
private void SetScissorFull(UIBox2i? state)
@@ -371,6 +372,7 @@ namespace Robust.Client.Graphics.Clyde
program.Use();
int textureUnitVal = 0;
// Assign shader parameters to uniform since they may be dirty.
foreach (var (name, value) in instance.Parameters)
{
@@ -413,6 +415,15 @@ namespace Robust.Client.Graphics.Clyde
case Matrix4 matrix4:
program.SetUniform(name, matrix4);
break;
case ClydeTexture clydeTexture:
//It's important to start at Texture6 here since DrawCommandBatch uses Texture0 and Texture1 immediately after calling this
//function! If passing in textures as uniforms ever stops working it might be since someone made it use all the way up to Texture6 too.
//Might change this in the future?
TextureUnit cTarget = TextureUnit.Texture6+textureUnitVal;
SetTexture(cTarget, ((ClydeTexture)clydeTexture).TextureId);
program.SetUniformTexture(name, cTarget);
textureUnitVal++;
break;
default:
throw new InvalidOperationException($"Unable to handle shader parameter {name}: {value}");
}
@@ -490,6 +501,7 @@ namespace Robust.Client.Graphics.Clyde
private void DrawTexture(ClydeHandle texture, Vector2 bl, Vector2 br, Vector2 tl, Vector2 tr, in Color modulate,
in Box2 texCoords)
{
EnsureBatchSpaceAvailable(4, GetQuadBatchIndexCount());
EnsureBatchState(texture, in modulate, true, GetQuadBatchPrimitiveType(), _queuedShader);
bl = _currentMatrixModel.Transform(bl);
@@ -515,6 +527,8 @@ namespace Robust.Client.Graphics.Clyde
FinishBatch();
_batchMetaData = null;
EnsureBatchSpaceAvailable(vertices.Length, indices.Length);
vertices.CopyTo(BatchVertexData.AsSpan(BatchVertexIndex));
// We are weaving this into the batch buffers for performance (and simplicity).
@@ -556,6 +570,8 @@ namespace Robust.Client.Graphics.Clyde
FinishBatch();
_batchMetaData = null;
EnsureBatchSpaceAvailable(vertices.Length, 0);
vertices.CopyTo(BatchVertexData.AsSpan(BatchVertexIndex));
ref var command = ref AllocRenderCommand(RenderCommandType.DrawBatch);
@@ -591,6 +607,7 @@ namespace Robust.Client.Graphics.Clyde
private void DrawLine(Vector2 a, Vector2 b, Color color)
{
EnsureBatchSpaceAvailable(2, 0);
EnsureBatchState(_stockTextureWhite.TextureId, color, false, BatchPrimitiveType.LineList, _queuedShader);
a = _currentMatrixModel.Transform(a);
@@ -605,6 +622,14 @@ namespace Robust.Client.Graphics.Clyde
_debugStats.LastClydeDrawCalls += 1;
}
private void EnsureBatchSpaceAvailable(int vtx, int idx)
{
if (BatchVertexIndex + vtx >= BatchVertexData.Length || BatchIndexIndex + idx > BatchIndexData.Length)
{
FlushBatchQueue();
}
}
private void DrawSetScissor(UIBox2i? scissorBox)
{
BreakBatch();
@@ -763,116 +788,6 @@ namespace Robust.Client.Graphics.Clyde
_batchMetaData = null;
}
private unsafe void TakeScreenshot(ScreenshotType type)
{
if (_queuedScreenshots.Count == 0 || _queuedScreenshots.All(p => p.type != type))
{
return;
}
var delegates = _queuedScreenshots.Where(p => p.type == type).ToList();
_queuedScreenshots.RemoveAll(p => p.type == type);
GL.PixelStore(PixelStoreParameter.PackAlignment, 1);
CheckGlError();
var bufferLength = ScreenSize.X * ScreenSize.Y;
if (!(_hasGLFenceSync && HasGLAnyMapBuffer && _hasGLPixelBufferObjects))
{
Logger.DebugS("clyde.ogl", "Necessary features for async screenshots not available, falling back to blocking path.");
// We need these 3 features to be able to do asynchronous screenshots, if we don't have them,
// we'll have to fall back to a crappy synchronous stalling method of glReadPixels().
var buffer = new Rgba32[bufferLength];
fixed (Rgba32* ptr = buffer)
{
var bufSize = sizeof(Rgba32) * bufferLength;
GL.ReadnPixels(0, 0, ScreenSize.X, ScreenSize.Y, PixelFormat.Rgba, PixelType.UnsignedByte, bufSize,
(IntPtr) ptr);
CheckGlError();
}
var (w, h) = ScreenSize;
var image = new Image<Rgb24>(w, h);
var imageSpan = image.GetPixelSpan();
FlipCopyScreenshot(buffer, imageSpan, w, h);
RunCallback(image);
return;
}
GL.GenBuffers(1, out uint pbo);
CheckGlError();
GL.BindBuffer(BufferTarget.PixelPackBuffer, pbo);
CheckGlError();
GL.BufferData(BufferTarget.PixelPackBuffer, bufferLength * sizeof(Rgba32), IntPtr.Zero,
BufferUsageHint.StreamRead);
CheckGlError();
GL.ReadPixels(0, 0, ScreenSize.X, ScreenSize.Y, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
CheckGlError();
var fence = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
CheckGlError();
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
CheckGlError();
_transferringScreenshots.Add((pbo, fence, ScreenSize, RunCallback));
void RunCallback(Image<Rgb24> image) => delegates.ForEach(p => p.callback(image));
}
private unsafe void CheckTransferringScreenshots()
{
if (_transferringScreenshots.Count == 0)
{
return;
}
foreach (var screenshot in _transferringScreenshots.ToList())
{
var (pbo, fence, (width, height), callback) = screenshot;
int status;
GL.GetSync(fence, SyncParameterName.SyncStatus, sizeof(int), null, &status);
CheckGlError();
if (status == (int) All.Signaled)
{
var bufLen = width * height;
var bufSize = sizeof(Rgba32) * bufLen;
GL.BindBuffer(BufferTarget.PixelPackBuffer, pbo);
CheckGlError();
var ptr = MapFullBuffer(BufferTarget.PixelPackBuffer, bufSize, BufferAccess.ReadOnly,
BufferAccessMask.MapReadBit);
var packSpan = new ReadOnlySpan<Rgba32>((void*) ptr, width * height);
var image = new Image<Rgb24>(width, height);
var imageSpan = image.GetPixelSpan();
FlipCopyScreenshot(packSpan, imageSpan, width, height);
UnmapBuffer(BufferTarget.PixelPackBuffer);
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
CheckGlError();
GL.DeleteBuffer(pbo);
CheckGlError();
GL.DeleteSync(fence);
CheckGlError();
_transferringScreenshots.Remove(screenshot);
// TODO: Don't do unnecessary copy here.
callback(image);
}
}
}
private FullStoredRendererState PushRenderStateFull()
{
return new FullStoredRendererState(_currentMatrixProj, _currentMatrixView, _currentRenderTarget);

View File

@@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.Utility;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using PF = OpenToolkit.Graphics.OpenGL4.PixelFormat;
using PT = OpenToolkit.Graphics.OpenGL4.PixelType;
namespace Robust.Client.Graphics.Clyde
{
// Contains primary screenshot and pixel-copying logic.
internal sealed partial class Clyde
{
// Full-framebuffer screenshots undergo the following sequence of events:
// 1. Screenshots are queued by content or whatever.
// 2. When the rendering code reaches the screenshot type,
// we instruct the GPU driver to copy the framebuffer and asynchronously transfer it to host memory.
// 3. Transfer finished asynchronously, we invoke the callback.
//
// On RAW GLES2, we cannot do this asynchronously due to lacking GL features,
// and the game will stutter as a result. This is sadly unavoidable.
//
// For CopyPixels on render targets, the copy and transfer is started immediately when the function is called.
private readonly List<QueuedScreenshot> _queuedScreenshots = new();
private readonly List<TransferringPixelCopy> _transferringPixelCopies = new();
public void Screenshot(ScreenshotType type, CopyPixelsDelegate<Rgb24> callback, UIBox2i? subRegion = null)
{
_queuedScreenshots.Add(new QueuedScreenshot(type, callback, subRegion));
}
private void TakeScreenshot(ScreenshotType type)
{
if (_queuedScreenshots.Count == 0)
{
return;
}
GL.PixelStore(PixelStoreParameter.PackAlignment, 1);
CheckGlError();
for (var i = 0; i < _queuedScreenshots.Count; i++)
{
var (qType, callback, subRegion) = _queuedScreenshots[i];
if (qType != type)
continue;
DoCopyPixels(ScreenSize, subRegion, callback);
_queuedScreenshots.RemoveSwap(i--);
}
}
private void CopyRenderTargetPixels<T>(
ClydeHandle renderTarget,
UIBox2i? subRegion,
CopyPixelsDelegate<T> callback)
where T : unmanaged, IPixel<T>
{
var loaded = _renderTargets[renderTarget];
var original = GL.GetInteger(GetPName.ReadFramebufferBinding);
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, loaded.FramebufferHandle.Handle);
DoCopyPixels(loaded.Size, subRegion, callback);
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, original);
}
private unsafe void DoCopyPixels<T>(
Vector2i fbSize,
UIBox2i? subRegion,
CopyPixelsDelegate<T> callback)
where T : unmanaged, IPixel<T>
{
var (pf, pt) = default(T) switch
{
Rgba32 => (PF.Rgba, PT.UnsignedByte),
Rgb24 => (PF.Rgb, PT.UnsignedByte),
_ => throw new ArgumentException("Unsupported pixel type.")
};
var size = ClampSubRegion(fbSize, subRegion);
var bufferLength = size.X * size.Y;
if (!(_hasGLFenceSync && HasGLAnyMapBuffer && _hasGLPixelBufferObjects))
{
Logger.DebugS("clyde.ogl",
"Necessary features for async screenshots not available, falling back to blocking path.");
// We need these 3 features to be able to do asynchronous screenshots, if we don't have them,
// we'll have to fall back to a crappy synchronous stalling method of glReadnPixels().
var buffer = new T[bufferLength];
fixed (T* ptr = buffer)
{
var bufSize = sizeof(T) * bufferLength;
GL.ReadnPixels(
0, 0,
size.X, size.Y,
pf, pt,
bufSize,
(nint) ptr);
CheckGlError();
}
var image = new Image<T>(size.X, size.Y);
var imageSpan = image.GetPixelSpan();
FlipCopy(buffer, imageSpan, size.X, size.Y);
callback(image);
return;
}
GL.GenBuffers(1, out uint pbo);
CheckGlError();
GL.BindBuffer(BufferTarget.PixelPackBuffer, pbo);
CheckGlError();
GL.BufferData(
BufferTarget.PixelPackBuffer,
bufferLength * sizeof(Rgba32), IntPtr.Zero,
BufferUsageHint.StreamRead);
CheckGlError();
GL.ReadPixels(0, 0, size.X, size.Y, pf, pt, IntPtr.Zero);
CheckGlError();
var fence = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
CheckGlError();
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
CheckGlError();
var transferring = new TransferringPixelCopy(pbo, fence, size, FinishPixelTransfer<T>, callback);
_transferringPixelCopies.Add(transferring);
}
private unsafe void CheckTransferringScreenshots()
{
if (_transferringPixelCopies.Count == 0)
{
return;
}
for (var i = 0; i < _transferringPixelCopies.Count; i++)
{
var transferring = _transferringPixelCopies[i];
// Check if transfer done (sync signalled)
int status;
GL.GetSync(transferring.Sync, SyncParameterName.SyncStatus, sizeof(int), null, &status);
CheckGlError();
if (status != (int) All.Signaled)
continue;
transferring.TransferContinue(transferring);
_transferringPixelCopies.RemoveSwap(i--);
}
}
private unsafe void FinishPixelTransfer<T>(TransferringPixelCopy transferring) where T : unmanaged, IPixel<T>
{
var (pbo, fence, (width, height), _, callback) = transferring;
var bufLen = width * height;
var bufSize = sizeof(T) * bufLen;
GL.BindBuffer(BufferTarget.PixelPackBuffer, pbo);
CheckGlError();
var ptr = MapFullBuffer(
BufferTarget.PixelPackBuffer,
bufSize,
BufferAccess.ReadOnly,
BufferAccessMask.MapReadBit);
var packSpan = new ReadOnlySpan<T>(ptr, width * height);
var image = new Image<T>(width, height);
var imageSpan = image.GetPixelSpan();
FlipCopy(packSpan, imageSpan, width, height);
UnmapBuffer(BufferTarget.PixelPackBuffer);
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
CheckGlError();
GL.DeleteBuffer(pbo);
CheckGlError();
GL.DeleteSync(fence);
CheckGlError();
var castCallback = (CopyPixelsDelegate<T>) callback;
castCallback(image);
}
private sealed record QueuedScreenshot(
ScreenshotType Type,
CopyPixelsDelegate<Rgb24> Callback,
UIBox2i? SubRegion);
private sealed record TransferringPixelCopy(
uint Pbo,
nint Sync,
Vector2i Size,
// Funny callback dance to handle the generics.
Action<TransferringPixelCopy> TransferContinue,
Delegate Callback);
}
}

View File

@@ -1,10 +1,10 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Text;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.ResourceManagement.ResourceTypes;
using Robust.Client.ResourceManagement;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using StencilOp = Robust.Client.Graphics.StencilOp;
@@ -428,7 +428,8 @@ namespace Robust.Client.Graphics.Clyde
private protected override void SetParameterImpl(string name, Texture value)
{
throw new NotImplementedException();
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetStencilOpImpl(StencilOp op)

View File

@@ -26,7 +26,7 @@ namespace Robust.Client.Graphics.Clyde
private readonly ConcurrentQueue<ClydeHandle> _textureDisposeQueue = new();
public Texture LoadTextureFromPNGStream(Stream stream, string? name = null,
public OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParams = null)
{
DebugTools.Assert(_mainThread == Thread.CurrentThread);
@@ -37,7 +37,7 @@ namespace Robust.Client.Graphics.Clyde
return LoadTextureFromImage(image, name, loadParams);
}
public Texture LoadTextureFromImage<T>(Image<T> image, string? name = null,
public OwnedTexture LoadTextureFromImage<T>(Image<T> image, string? name = null,
TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel<T>
{
DebugTools.Assert(_mainThread == Thread.CurrentThread);
@@ -56,19 +56,19 @@ namespace Robust.Client.Graphics.Clyde
{
// Disable sRGB so stuff doesn't get interpreter wrong.
actualParams.Srgb = false;
var img = ApplyA8Swizzle((Image<A8>) (object) image);
using var img = ApplyA8Swizzle((Image<A8>) (object) image);
return LoadTextureFromImage(img, name, loadParams);
}
if (pixelType == typeof(L8) && !actualParams.Srgb)
{
var img = ApplyL8Swizzle((Image<L8>) (object) image);
using var img = ApplyL8Swizzle((Image<L8>) (object) image);
return LoadTextureFromImage(img, name, loadParams);
}
}
// Flip image because OpenGL reads images upside down.
var copy = FlipClone(image);
using var copy = FlipClone(image);
var texture = CreateBaseTextureInternal<T>(image.Width, image.Height, actualParams, name);
@@ -324,11 +324,13 @@ namespace Robust.Client.Graphics.Clyde
if (typeof(T) == typeof(A8))
{
SetSubImage(texture, dstTl, ApplyA8Swizzle((Image<A8>) (object) srcImage), srcBox);
return;
}
if (typeof(T) == typeof(L8))
{
SetSubImage(texture, dstTl, ApplyL8Swizzle((Image<L8>) (object) srcImage), srcBox);
return;
}
}
@@ -450,24 +452,6 @@ namespace Robust.Client.Graphics.Clyde
}
}
private static void FlipCopyScreenshot(ReadOnlySpan<Rgba32> srcSpan, Span<Rgb24> dstSpan, int w, int h)
{
var dr = h - 1;
for (var r = 0; r < h; r++, dr--)
{
var si = r * w;
var di = dr * w;
var srcRow = srcSpan[si..(si + w)];
var dstRow = dstSpan[di..(di + w)];
for (var x = 0; x < w; x++)
{
var src = srcRow[x];
dstRow[x] = new Rgb24(src.R, src.G, src.B);
}
}
}
private static Image<Rgba32> ApplyA8Swizzle(Image<A8> source)
{
var newImage = new Image<Rgba32>(source.Width, source.Height);

View File

@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics.Clyde
@@ -10,7 +13,7 @@ namespace Robust.Client.Graphics.Clyde
private readonly Dictionary<ClydeHandle, WeakReference<Viewport>> _viewports =
new();
private Viewport CreateViewport(Vector2i size, string? name = null)
private Viewport CreateViewport(Vector2i size, TextureSampleParameters? sampleParameters = default, string? name = null)
{
var handle = AllocRid();
var viewport = new Viewport(handle, name, this)
@@ -18,6 +21,7 @@ namespace Robust.Client.Graphics.Clyde
Size = size,
RenderTarget = CreateRenderTarget(size,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb, true),
sampleParameters: sampleParameters,
name: $"{name}-MainRenderTarget")
};
@@ -28,9 +32,9 @@ namespace Robust.Client.Graphics.Clyde
return viewport;
}
IClydeViewport IClyde.CreateViewport(Vector2i size, string? name)
IClydeViewport IClyde.CreateViewport(Vector2i size, TextureSampleParameters? sampleParameters, string? name)
{
return CreateViewport(size, name);
return CreateViewport(size, sampleParameters, name);
}
private static Vector2 ScreenToMap(Vector2 point, Viewport vp)
@@ -45,7 +49,7 @@ namespace Robust.Client.Graphics.Clyde
point *= new Vector2(1, -1) / EyeManager.PixelsPerMeter;
// view matrix
vp.Eye.GetViewMatrixInv(out var viewMatrixInv);
vp.Eye.GetViewMatrixInv(out var viewMatrixInv, vp.RenderScale);
point = viewMatrixInv * point;
return point;
@@ -106,12 +110,66 @@ namespace Robust.Client.Graphics.Clyde
}
public Vector2i Size { get; set; }
public Vector2 RenderScale { get; set; } = Vector2.One;
public bool AutomaticRender { get; set; }
void IClydeViewport.Render()
{
_clyde.RenderViewport(this);
}
public MapCoordinates LocalToWorld(Vector2 point)
{
if (Eye == null)
return default;
var newPoint = point;
// (inlined version of UiProjMatrix^-1)
newPoint -= Size / 2f;
newPoint *= new Vector2(1, -1) / EyeManager.PixelsPerMeter;
// view matrix
Eye.GetViewMatrixInv(out var viewMatrixInv, RenderScale);
newPoint = viewMatrixInv * newPoint;
return new MapCoordinates(newPoint, Eye.Position.MapId);
}
public Vector2 WorldToLocal(Vector2 point)
{
if (Eye == null)
return default;
var eye = (IEye) Eye;
var newPoint = point;
eye.GetViewMatrix(out var viewMatrix, RenderScale);
newPoint = viewMatrix * newPoint;
// (inlined version of UiProjMatrix)
newPoint *= new Vector2(1, -1) * EyeManager.PixelsPerMeter;
newPoint += Size / 2f;
return newPoint;
}
public void RenderScreenOverlaysBelow(
DrawingHandleScreen handle,
IViewportControl control,
in UIBox2i viewportBounds)
{
_clyde.RenderOverlaysDirect(this, control, handle, OverlaySpace.ScreenSpaceBelowWorld, viewportBounds);
}
public void RenderScreenOverlaysAbove(
DrawingHandleScreen handle,
IViewportControl control,
in UIBox2i viewportBounds)
{
_clyde.RenderOverlaysDirect(this, control, handle, OverlaySpace.ScreenSpace, viewportBounds);
}
public void Dispose()
{
RenderTarget.Dispose();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
using System.Threading;
using OpenToolkit;
using OpenToolkit.Graphics.OpenGL4;
using OpenToolkit.GraphicsLibraryFramework;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Client.Utility;
@@ -14,6 +15,7 @@ using Robust.Shared;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using static Robust.Client.Utility.LiterallyJustMessageBox;
@@ -46,6 +48,7 @@ namespace Robust.Client.Graphics.Clyde
{
// Keep delegates around to prevent GC issues.
private GLFWCallbacks.ErrorCallback _errorCallback = default!;
private GLFWCallbacks.MonitorCallback _monitorCallback = default!;
private GLFWCallbacks.CharCallback _charCallback = default!;
private GLFWCallbacks.CursorPosCallback _cursorPosCallback = default!;
private GLFWCallbacks.KeyCallback _keyCallback = default!;
@@ -73,6 +76,18 @@ namespace Robust.Client.Graphics.Clyde
private Vector2 _lastMousePos;
// Can't use ClydeHandle because it's 64 bit.
private int _nextWindowId = 1;
private readonly Dictionary<int, MonitorReg> _monitors = new();
public event Action<TextEventArgs>? TextEntered;
public event Action<MouseMoveEventArgs>? MouseMove;
public event Action<KeyEventArgs>? KeyUp;
public event Action<KeyEventArgs>? KeyDown;
public event Action<MouseWheelEventArgs>? MouseWheel;
public event Action<string>? CloseWindow;
public event Action? OnWindowScaleChanged;
// NOTE: in engine we pretend the framebuffer size is the screen size..
// For practical reasons like UI rendering.
public override Vector2i ScreenSize => _framebufferSize;
@@ -148,15 +163,60 @@ namespace Robust.Client.Graphics.Clyde
return false;
}
InitMonitors();
InitCursors();
return InitWindow();
}
private void InitMonitors()
{
var monitors = GLFW.GetMonitorsRaw(out var count);
for (var i = 0; i < count; i++)
{
SetupMonitor(monitors[i]);
}
}
private void SetupMonitor(Monitor* monitor)
{
var handle = _nextWindowId++;
DebugTools.Assert(GLFW.GetMonitorUserPointer(monitor) == null, "GLFW window already has user pointer??");
var name = GLFW.GetMonitorName(monitor);
var videoMode = GLFW.GetVideoMode(monitor);
var impl = new ClydeMonitorImpl(handle, name, (videoMode->Width, videoMode->Height), videoMode->RefreshRate);
GLFW.SetMonitorUserPointer(monitor, (void*) handle);
_monitors[handle] = new MonitorReg
{
Id = handle,
Impl = impl,
Monitor = monitor
};
}
private void DestroyMonitor(Monitor* monitor)
{
var ptr = GLFW.GetMonitorUserPointer(monitor);
if (ptr == null)
{
var name = GLFW.GetMonitorName(monitor);
Logger.WarningS("clyde.win", $"Monitor '{name}' had no user pointer set??");
return;
}
_monitors.Remove((int) ptr);
GLFW.SetMonitorUserPointer(monitor, null);
}
private bool InitWindow()
{
var width = _configurationManager.GetCVar(CVars.DisplayWidth);
var height = _configurationManager.GetCVar(CVars.DisplayHeight);
var width = ConfigurationManager.GetCVar(CVars.DisplayWidth);
var height = ConfigurationManager.GetCVar(CVars.DisplayHeight);
Monitor* monitor = null;
@@ -166,6 +226,8 @@ namespace Robust.Client.Graphics.Clyde
var mode = GLFW.GetVideoMode(monitor);
width = mode->Width;
height = mode->Height;
GLFW.WindowHint(WindowHintInt.RefreshRate, mode->RefreshRate);
}
#if DEBUG
@@ -174,13 +236,19 @@ namespace Robust.Client.Graphics.Clyde
GLFW.WindowHint(WindowHintString.X11ClassName, "SS14");
GLFW.WindowHint(WindowHintString.X11InstanceName, "SS14");
var renderer = (Renderer) _configurationManager.GetCVar<int>(CVars.DisplayRenderer);
var renderer = (Renderer) ConfigurationManager.GetCVar<int>(CVars.DisplayRenderer);
Span<Renderer> renderers = (renderer == Renderer.Default) ? stackalloc Renderer[] {
Renderer.OpenGL33,
Renderer.OpenGL31,
Renderer.OpenGLES2
} : stackalloc Renderer[] {renderer};
Span<Renderer> renderers = (renderer == Renderer.Default)
? stackalloc Renderer[]
{
Renderer.OpenGL33,
Renderer.OpenGL31,
Renderer.OpenGLES2
}
: stackalloc Renderer[] {renderer};
ErrorCode lastGlfwError = default;
string? lastGlfwErrorDesc = default;
foreach (Renderer r in renderers)
{
@@ -193,22 +261,22 @@ namespace Robust.Client.Graphics.Clyde
_isCore = renderer == Renderer.OpenGL33;
break;
}
// Window failed to init due to error.
// Try not to treat the error code seriously.
var code = GLFW.GetError(out string desc);
Logger.DebugS("clyde.win", $"{r} unsupported: [${code}] ${desc}");
lastGlfwError = GLFW.GetError(out lastGlfwErrorDesc);
Logger.DebugS("clyde.win", $"{r} unsupported: [{lastGlfwError}] ${lastGlfwErrorDesc}");
}
if (_glfwWindow == null)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var code = GLFW.GetError(out string desc);
var errorContent = "Failed to create the game window. " +
"This probably means your GPU is too old to play the game. " +
"That or update your graphic drivers\n" +
$"The exact error is: [{code}]\n {desc}";
"This probably means your GPU is too old to play the game. " +
"Try to update your graphics drivers, " +
"or enable compatibility mode in the launcher if that fails.\n" +
$"The exact error is: {lastGlfwError}\n{lastGlfwErrorDesc}";
MessageBoxW(null,
errorContent,
@@ -301,6 +369,7 @@ namespace Robust.Client.Graphics.Clyde
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
GLFW.WindowHint(WindowHintBool.SrgbCapable, false);
}
_glfwWindow = GLFW.CreateWindow(width, height, string.Empty, monitor, null);
}
}
@@ -393,11 +462,30 @@ namespace Robust.Client.Graphics.Clyde
Logger.ErrorS("clyde.win.glfw", "GLFW Error: [{0}] {1}", code, description);
}
private void OnGlfwMonitor(Monitor* monitor, ConnectedState state)
{
try
{
if (state == ConnectedState.Connected)
{
SetupMonitor(monitor);
}
else
{
DestroyMonitor(monitor);
}
}
catch (Exception e)
{
CatchCallbackException(e);
}
}
private void OnGlfwChar(Window* window, uint codepoint)
{
try
{
_gameController.TextEntered(new TextEventArgs(codepoint));
TextEntered?.Invoke(new TextEventArgs(codepoint));
}
catch (Exception e)
{
@@ -413,8 +501,7 @@ namespace Robust.Client.Graphics.Clyde
var delta = newPos - _lastMousePos;
_lastMousePos = newPos;
var ev = new MouseMoveEventArgs(delta, newPos);
_gameController.MouseMove(ev);
MouseMove?.Invoke(new MouseMoveEventArgs(delta, newPos));
}
catch (Exception e)
{
@@ -461,11 +548,11 @@ namespace Robust.Client.Graphics.Clyde
switch (action)
{
case InputAction.Release:
_gameController.KeyUp(ev);
KeyUp?.Invoke(ev);
break;
case InputAction.Press:
case InputAction.Repeat:
_gameController.KeyDown(ev);
KeyDown?.Invoke(ev);
break;
default:
throw new ArgumentOutOfRangeException(nameof(action), action, null);
@@ -477,7 +564,7 @@ namespace Robust.Client.Graphics.Clyde
try
{
var ev = new MouseWheelEventArgs(((float) offsetX, (float) offsetY), _lastMousePos);
_gameController.MouseWheel(ev);
MouseWheel?.Invoke(ev);
}
catch (Exception e)
{
@@ -489,7 +576,7 @@ namespace Robust.Client.Graphics.Clyde
{
try
{
_gameController.Shutdown("Window closed");
CloseWindow?.Invoke("Window closed");
}
catch (Exception e)
{
@@ -514,11 +601,6 @@ namespace Robust.Client.Graphics.Clyde
GL.Viewport(0, 0, fbW, fbH);
CheckGlError();
if (fbW != 0 && fbH != 0)
{
_mainViewport.Dispose();
CreateMainViewport();
}
OnWindowResized?.Invoke(new WindowResizedEventArgs(oldSize, _framebufferSize));
}
@@ -533,6 +615,7 @@ namespace Robust.Client.Graphics.Clyde
try
{
_windowScale = (xScale, yScale);
OnWindowScaleChanged?.Invoke();
}
catch (Exception e)
{
@@ -568,6 +651,7 @@ namespace Robust.Client.Graphics.Clyde
private void StoreCallbacks()
{
_errorCallback = OnGlfwError;
_monitorCallback = OnGlfwMonitor;
_charCallback = OnGlfwChar;
_cursorPosCallback = OnGlfwCursorPos;
_keyCallback = OnGlfwKey;
@@ -590,6 +674,19 @@ namespace Robust.Client.Graphics.Clyde
GLFW.SetWindowTitle(_glfwWindow, title);
}
public void SetWindowMonitor(IClydeMonitor monitor)
{
var monitorImpl = (ClydeMonitorImpl) monitor;
var reg = _monitors[monitorImpl.Id];
GLFW.SetWindowMonitor(
_glfwWindow,
reg.Monitor,
0, 0,
monitorImpl.Size.X, monitorImpl.Size.Y,
monitorImpl.RefreshRate);
}
public void RequestWindowAttention()
{
GLFW.RequestWindowAttention(_glfwWindow);
@@ -654,18 +751,40 @@ namespace Robust.Client.Graphics.Clyde
GLFW.GetWindowPos(_glfwWindow, out var x, out var y);
_prevWindowPos = (x, y);
var monitor = GLFW.GetPrimaryMonitor();
var monitor = MonitorForWindow(_glfwWindow);
var mode = GLFW.GetVideoMode(monitor);
GLFW.SetWindowMonitor(_glfwWindow, GLFW.GetPrimaryMonitor(), 0, 0, mode->Width, mode->Height,
GLFW.SetWindowMonitor(_glfwWindow, monitor, 0, 0, mode->Width, mode->Height,
mode->RefreshRate);
}
else
{
GLFW.SetWindowMonitor(_glfwWindow, null, _prevWindowPos.X, _prevWindowPos.Y, _prevWindowSize.X, _prevWindowSize.Y, 0);
GLFW.SetWindowMonitor(_glfwWindow, null, _prevWindowPos.X, _prevWindowPos.Y, _prevWindowSize.X,
_prevWindowSize.Y, 0);
}
}
// glfwGetWindowMonitor only works for fullscreen windows.
// Picks the monitor with the top-left corner of the window.
private Monitor* MonitorForWindow(Window* window)
{
GLFW.GetWindowPos(window, out var winPosX, out var winPosY);
var monitors = GLFW.GetMonitorsRaw(out var count);
for (var i = 0; i < count; i++)
{
var monitor = monitors[i];
GLFW.GetMonitorPos(monitor, out var monPosX, out var monPosY);
var videoMode = GLFW.GetVideoMode(monitor);
var box = Box2i.FromDimensions(monPosX, monPosY, videoMode->Width, videoMode->Height);
if (box.Contains(winPosX, winPosY))
return monitor;
}
// Fallback
return GLFW.GetPrimaryMonitor();
}
string IClipboardManager.GetText()
{
return GLFW.GetClipboardString(_glfwWindow);
@@ -676,6 +795,11 @@ namespace Robust.Client.Graphics.Clyde
GLFW.SetClipboardString(_glfwWindow, text);
}
public IEnumerable<IClydeMonitor> EnumerateMonitors()
{
return _monitors.Values.Select(c => c.Impl);
}
// We can't let exceptions unwind into GLFW, as that can cause the CLR to crash.
// And it probably messes up GLFW too.
// So all the callbacks are passed to this method.
@@ -689,5 +813,28 @@ namespace Robust.Client.Graphics.Clyde
_glfwExceptionList.Add(e);
}
private sealed class MonitorReg
{
public int Id;
public Monitor* Monitor;
public ClydeMonitorImpl Impl = default!;
}
private sealed class ClydeMonitorImpl : IClydeMonitor
{
public ClydeMonitorImpl(int id, string name, Vector2i size, int refreshRate)
{
Id = id;
Name = name;
Size = size;
RefreshRate = refreshRate;
}
public int Id { get; }
public string Name { get; }
public Vector2i Size { get; }
public int RefreshRate { get; }
}
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
@@ -12,10 +11,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute;
namespace Robust.Client.Graphics.Clyde
@@ -47,8 +43,6 @@ namespace Robust.Client.Graphics.Clyde
private GLBuffer QuadVBO = default!;
private GLHandle QuadVAO;
private Viewport _mainViewport = default!;
private bool _drawingSplash = true;
private GLShaderProgram? _currentProgram;
@@ -59,13 +53,6 @@ namespace Robust.Client.Graphics.Clyde
private bool _checkGLErrors;
private readonly List<(ScreenshotType type, Action<Image<Rgb24>> callback)> _queuedScreenshots
= new();
private readonly List<(uint pbo, IntPtr sync, Vector2i size, Action<Image<Rgb24>> callback)>
_transferringScreenshots
= new();
public Clyde()
{
// Init main window render target.
@@ -87,7 +74,7 @@ namespace Robust.Client.Graphics.Clyde
{
base.Initialize();
_configurationManager.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
ConfigurationManager.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
if (!InitWindowing())
{
@@ -124,9 +111,9 @@ namespace Robust.Client.Graphics.Clyde
protected override void ReadConfig()
{
base.ReadConfig();
_lightmapDivider = _configurationManager.GetCVar(CVars.DisplayLightMapDivider);
_maxLightsPerScene = _configurationManager.GetCVar(CVars.DisplayMaxLightsPerScene);
_enableSoftShadows = _configurationManager.GetCVar(CVars.DisplaySoftShadows);
_lightmapDivider = ConfigurationManager.GetCVar(CVars.DisplayLightMapDivider);
_maxLightsPerScene = ConfigurationManager.GetCVar(CVars.DisplayMaxLightsPerScene);
_enableSoftShadows = ConfigurationManager.GetCVar(CVars.DisplaySoftShadows);
}
protected override void ReloadConfig()
@@ -152,11 +139,6 @@ namespace Robust.Client.Graphics.Clyde
public override event Action<WindowFocusedEventArgs>? OnWindowFocused;
public void Screenshot(ScreenshotType type, Action<Image<Rgb24>> callback)
{
_queuedScreenshots.Add((type, callback));
}
private void InitOpenGL()
{
var vendor = GL.GetString(StringName.Vendor);
@@ -238,7 +220,7 @@ namespace Robust.Client.Graphics.Clyde
private (int major, int minor)? ParseGLOverrideVersion()
{
var overrideGLVersion = _configurationManager.GetCVar(CVars.DisplayOGLOverrideVersion);
var overrideGLVersion = ConfigurationManager.GetCVar(CVars.DisplayOGLOverrideVersion);
if (string.IsNullOrEmpty(overrideGLVersion))
{
return null;
@@ -314,18 +296,10 @@ namespace Robust.Client.Graphics.Clyde
ProjViewUBO = new GLUniformBuffer<ProjViewMatrices>(this, BindingIndexProjView, nameof(ProjViewUBO));
UniformConstantsUBO = new GLUniformBuffer<UniformConstants>(this, BindingIndexUniformConstants, nameof(UniformConstantsUBO));
CreateMainViewport();
}
private void CreateMainViewport()
{
var (w, h) = _framebufferSize;
// Ensure viewport size is always even to avoid artifacts.
if (w % 2 == 1) w += 1;
if (h % 2 == 1) h += 1;
_mainViewport = CreateViewport((w, h), nameof(_mainViewport));
screenBufferHandle = new GLHandle(GL.GenTexture());
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
ApplySampleParameters(TextureSampleParameters.Default);
ScreenBufferTexture = GenTexture(screenBufferHandle, _framebufferSize, true, null, TexturePixelType.Rgba32);
}
[Conditional("DEBUG")]

View File

@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using JetBrains.Annotations;
using Robust.Client.Audio;
using Robust.Client.Input;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using SixLabors.ImageSharp;
@@ -37,6 +40,13 @@ namespace Robust.Client.Graphics.Clyde
public IClydeDebugInfo DebugInfo { get; } = new DummyDebugInfo();
public IClydeDebugStats DebugStats { get; } = new DummyDebugStats();
public event Action<TextEventArgs>? TextEntered;
public event Action<MouseMoveEventArgs>? MouseMove;
public event Action<KeyEventArgs>? KeyUp;
public event Action<KeyEventArgs>? KeyDown;
public event Action<MouseWheelEventArgs>? MouseWheel;
public event Action<string>? CloseWindow;
public Texture GetStockTexture(ClydeStockTexture stockTexture)
{
return new DummyTexture((1, 1));
@@ -63,6 +73,11 @@ namespace Robust.Client.Graphics.Clyde
// Nada.
}
public void SetWindowMonitor(IClydeMonitor monitor)
{
// Nada.
}
public void RequestWindowAttention()
{
// Nada.
@@ -86,6 +101,12 @@ namespace Robust.Client.Graphics.Clyde
remove { }
}
public event Action OnWindowScaleChanged
{
add { }
remove { }
}
public void Render()
{
// Nada.
@@ -101,7 +122,7 @@ namespace Robust.Client.Graphics.Clyde
// Nada.
}
public Texture LoadTextureFromPNGStream(Stream stream, string? name = null,
public OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParams = null)
{
using (var image = Image.Load<Rgba32>(stream))
@@ -110,7 +131,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
public Texture LoadTextureFromImage<T>(Image<T> image, string? name = null,
public OwnedTexture LoadTextureFromImage<T>(Image<T> image, string? name = null,
TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel<T>
{
return new DummyTexture((image.Width, image.Height));
@@ -146,14 +167,23 @@ namespace Robust.Client.Graphics.Clyde
// Nada.
}
public void Screenshot(ScreenshotType type, Action<Image<Rgb24>> callback)
public void Screenshot(ScreenshotType type, CopyPixelsDelegate<Rgb24> callback, UIBox2i? subRegion = null)
{
callback(new Image<Rgb24>(ScreenSize.X, ScreenSize.Y));
// Immediately call callback with an empty buffer.
var (x, y) = ClampSubRegion(ScreenSize, subRegion);
callback(new Image<Rgb24>(x, y));
}
public IClydeViewport CreateViewport(Vector2i size, string? name = null)
public IClydeViewport CreateViewport(Vector2i size, TextureSampleParameters? sampleParameters,
string? name = null)
{
return new Viewport();
return new Viewport(size);
}
public IEnumerable<IClydeMonitor> EnumerateMonitors()
{
// TODO: Actually return something.
yield break;
}
public ClydeHandle LoadShader(ParsedShader shader, string? name = null)
@@ -267,6 +297,11 @@ namespace Robust.Client.Graphics.Clyde
{
// Nada.
}
public void SetVelocity(Vector2 velocity)
{
// Nada.
}
}
private sealed class DummyBufferedAudioSource : DummyAudioSource, IClydeBufferedAudioSource
@@ -402,12 +437,15 @@ namespace Robust.Client.Graphics.Clyde
}
public Vector2i Size { get; }
public Texture Texture { get; }
public void Delete()
public void CopyPixelsToMemory<T>(CopyPixelsDelegate<T> callback, UIBox2i? subRegion) where T : unmanaged, IPixel<T>
{
var (x, y) = ClampSubRegion(Size, subRegion);
callback(new Image<T>(x, y));
}
public Texture Texture { get; }
public void Dispose()
{
}
@@ -424,6 +462,12 @@ namespace Robust.Client.Graphics.Clyde
public Vector2i Size => _clyde.ScreenSize;
public void CopyPixelsToMemory<T>(CopyPixelsDelegate<T> callback, UIBox2i? subRegion) where T : unmanaged, IPixel<T>
{
var (x, y) = ClampSubRegion(Size, subRegion);
callback(new Image<T>(x, y));
}
public void Dispose()
{
}
@@ -449,6 +493,11 @@ namespace Robust.Client.Graphics.Clyde
private sealed class Viewport : IClydeViewport
{
public Viewport(Vector2i size)
{
Size = size;
}
public void Dispose()
{
}
@@ -458,9 +507,38 @@ namespace Robust.Client.Graphics.Clyde
public IEye? Eye { get; set; }
public Vector2i Size { get; }
public Vector2 RenderScale { get; set; }
public bool AutomaticRender { get; set; }
public void Render()
{
// Nada
}
public MapCoordinates LocalToWorld(Vector2 point)
{
return default;
}
public Vector2 WorldToLocal(Vector2 point)
{
return default;
}
public void RenderScreenOverlaysBelow(
DrawingHandleScreen handle,
IViewportControl control,
in UIBox2i viewportBounds)
{
// Nada
}
public void RenderScreenOverlaysAbove(
DrawingHandleScreen handle,
IViewportControl control,
in UIBox2i viewportBounds)
{
// Nada
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using OpenToolkit.Graphics.OpenGL4;

View File

@@ -4,7 +4,6 @@ varying highp vec2 Pos;
uniform sampler2D lightMap;
uniform highp vec4 modulate;
#line 1000
// [SHADER_HEADER_CODE]
void main()
@@ -13,7 +12,6 @@ void main()
lowp vec4 COLOR;
#line 10000
// [SHADER_CODE]
lowp vec3 lightSample = texture2D(lightMap, Pos).rgb;

View File

@@ -89,9 +89,10 @@ uniform highp vec2 TEXTURE_PIXEL_SIZE;
// -- srgb emulation --
#ifdef HAS_SRGB
highp vec4 zTexture(highp vec2 uv)
highp vec4 zTextureSpec(sampler2D tex, highp vec2 uv)
{
return texture2D(TEXTURE, uv);
return texture2D(tex, uv);
}
highp vec4 zAdjustResult(highp vec4 col)
@@ -101,9 +102,9 @@ highp vec4 zAdjustResult(highp vec4 col)
#else
uniform lowp vec2 SRGB_EMU_CONFIG;
highp vec4 zTexture(highp vec2 uv)
highp vec4 zTextureSpec(sampler2D tex, highp vec2 uv)
{
highp vec4 col = texture2D(TEXTURE, uv);
highp vec4 col = texture2D(tex, uv);
if (SRGB_EMU_CONFIG.x > 0.5)
{
return zFromSrgb(col);
@@ -121,5 +122,10 @@ highp vec4 zAdjustResult(highp vec4 col)
}
#endif
highp vec4 zTexture(highp vec2 uv)
{
return zTextureSpec(TEXTURE, uv);
}
// -- Utilities End --

View File

@@ -18,8 +18,7 @@ namespace Robust.Client.Graphics
/// </summary>
internal abstract class ClydeBase
{
[Dependency] protected readonly IConfigurationManager _configurationManager = default!;
[Dependency] protected readonly IGameControllerInternal _gameController = default!;
[Dependency] protected readonly IConfigurationManager ConfigurationManager = default!;
protected WindowMode WindowMode { get; private set; } = WindowMode.Windowed;
protected bool VSync { get; private set; } = true;
@@ -31,11 +30,11 @@ namespace Robust.Client.Graphics
public virtual bool Initialize()
{
_configurationManager.OnValueChanged(CVars.DisplayVSync, _vSyncChanged, true);
_configurationManager.OnValueChanged(CVars.DisplayWindowMode, _windowModeChanged, true);
_configurationManager.OnValueChanged(CVars.DisplayLightMapDivider, LightmapDividerChanged, true);
_configurationManager.OnValueChanged(CVars.DisplayMaxLightsPerScene, MaxLightsPerSceneChanged, true);
_configurationManager.OnValueChanged(CVars.DisplaySoftShadows, SoftShadowsChanged, true);
ConfigurationManager.OnValueChanged(CVars.DisplayVSync, _vSyncChanged, true);
ConfigurationManager.OnValueChanged(CVars.DisplayWindowMode, _windowModeChanged, true);
ConfigurationManager.OnValueChanged(CVars.DisplayLightMapDivider, LightmapDividerChanged, true);
ConfigurationManager.OnValueChanged(CVars.DisplayMaxLightsPerScene, MaxLightsPerSceneChanged, true);
ConfigurationManager.OnValueChanged(CVars.DisplaySoftShadows, SoftShadowsChanged, true);
return true;
}
@@ -51,8 +50,8 @@ namespace Robust.Client.Graphics
protected virtual void ReadConfig()
{
WindowMode = (WindowMode) _configurationManager.GetCVar(CVars.DisplayWindowMode);
VSync = _configurationManager.GetCVar(CVars.DisplayVSync);
WindowMode = (WindowMode) ConfigurationManager.GetCVar(CVars.DisplayWindowMode);
VSync = ConfigurationManager.GetCVar(CVars.DisplayVSync);
}
private void _vSyncChanged(bool newValue)
@@ -86,5 +85,12 @@ namespace Robust.Client.Graphics
protected virtual void SoftShadowsChanged(bool newValue)
{
}
protected static Vector2i ClampSubRegion(Vector2i size, UIBox2i? subRegionSpecified)
{
return subRegionSpecified == null
? size
: UIBox2i.FromDimensions(Vector2i.Zero, size).Intersection(subRegionSpecified.Value)?.Size ?? default;
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Text;
using Robust.Client.ResourceManagement;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -40,63 +41,48 @@ namespace Robust.Client.Graphics
return GetLineHeight(scale) - GetHeight(scale);
}
[Obsolete("Use GetAscent")] public int Ascent => GetAscent(1);
[Obsolete("Use GetHeight")] public int Height => GetHeight(1);
[Obsolete("Use GetDescent")] public int Descent => GetDescent(1);
[Obsolete("Use GetLineHeight")] public int LineHeight => GetLineHeight(1);
[Obsolete("Use GetLineSeparation")] public int LineSeparation => GetLineSeparation(1);
// Yes, I am aware that using char is bad.
// At the same time the font system is nowhere close to rendering Unicode so...
/// <summary>
/// Draw a character at a certain baseline position on screen.
/// </summary>
/// <param name="handle">The drawing handle to draw to.</param>
/// <param name="chr">
/// The Unicode code point to draw. Yes I'm aware about UTF-16 being crap,
/// do you think this system can draw anything except ASCII?
/// </param>
/// <param name="rune">The Unicode code point to draw.</param>
/// <param name="baseline">The baseline from which to draw the character.</param>
/// <param name="scale">DPI scale factor to render the font at.</param>
/// <param name="color">The color of the character to draw.</param>
/// <param name="fallback">If the character is not available, render "<22>" instead.</param>
/// <returns>How much to advance the cursor to draw the next character.</returns>
[Obsolete("Use DrawChar with scale support.")]
public float DrawChar(DrawingHandleScreen handle, char chr, Vector2 baseline, Color color)
{
return DrawChar(handle, chr, baseline, 1, color);
}
public abstract float DrawChar(
DrawingHandleScreen handle, Rune rune, Vector2 baseline, float scale,
Color color, bool fallback=true);
public abstract float DrawChar(DrawingHandleScreen handle, char chr, Vector2 baseline, float scale,
Color color);
[Obsolete("Use Rune variant instead")]
public float DrawChar(DrawingHandleScreen handle, char chr, Vector2 baseline, float scale, Color color)
{
return DrawChar(handle, (Rune) chr, baseline, scale, color);
}
/// <summary>
/// Gets metrics describing the dimensions and positioning of a single glyph in the font.
/// </summary>
/// <param name="chr">The character to fetch the glyph metrics for.</param>
/// <param name="rune">The unicode codepoint to fetch the glyph metrics for.</param>
/// <param name="scale">DPI scale factor to render the font at.</param>
/// <param name="fallback">
/// If the character is not available, return data for "<22>" instead.
/// This can still fail if the font does not define <20> itself.
/// </param>
/// <returns>
/// <c>null</c> if this font does not have a glyph for the specified character,
/// otherwise the metrics you asked for.
/// </returns>
/// <seealso cref="TryGetCharMetrics"/>
[Obsolete("Use GetCharMetrics with scale support.")]
public CharMetrics? GetCharMetrics(char chr)
{
return GetCharMetrics(chr, 1);
}
public abstract CharMetrics? GetCharMetrics(char chr, float scale);
public abstract CharMetrics? GetCharMetrics(Rune rune, float scale, bool fallback=true);
/// <summary>
/// Try-pattern version of <see cref="GetCharMetrics"/>.
/// </summary>
[Obsolete("Use TryGetCharMetrics with scale support.")]
public bool TryGetCharMetrics(char chr, out CharMetrics metrics)
public bool TryGetCharMetrics(Rune rune, float scale, out CharMetrics metrics, bool fallback=true)
{
return TryGetCharMetrics(chr, 1, out metrics);
}
public bool TryGetCharMetrics(char chr, float scale, out CharMetrics metrics)
{
var maybe = GetCharMetrics(chr, scale);
var maybe = GetCharMetrics(rune, scale);
if (maybe.HasValue)
{
metrics = maybe.Value;
@@ -128,15 +114,23 @@ namespace Robust.Client.Graphics
public override int GetDescent(float scale) => Handle.GetDescent(scale);
public override int GetLineHeight(float scale) => Handle.GetLineHeight(scale);
public override float DrawChar(DrawingHandleScreen handle, char chr, Vector2 baseline, float scale, Color color)
public override float DrawChar(DrawingHandleScreen handle, Rune rune, Vector2 baseline, float scale, Color color, bool fallback=true)
{
var metrics = Handle.GetCharMetrics(chr, scale);
var metrics = Handle.GetCharMetrics(rune, scale);
if (!metrics.HasValue)
{
return 0;
if (fallback && !Rune.IsWhiteSpace(rune))
{
rune = new Rune('<27>');
metrics = Handle.GetCharMetrics(rune, scale);
if (!metrics.HasValue)
return 0;
}
else
return 0;
}
var texture = Handle.GetCharTexture(chr, scale);
var texture = Handle.GetCharTexture(rune, scale);
if (texture == null)
{
return metrics.Value.Advance;
@@ -147,9 +141,12 @@ namespace Robust.Client.Graphics
return metrics.Value.Advance;
}
public override CharMetrics? GetCharMetrics(char chr, float scale)
public override CharMetrics? GetCharMetrics(Rune rune, float scale, bool fallback=true)
{
return Handle.GetCharMetrics(chr, scale);
var metrics = Handle.GetCharMetrics(rune, scale);
if (metrics == null && !Rune.IsWhiteSpace(rune) && fallback)
return Handle.GetCharMetrics(new Rune('<27>'), scale);
return metrics;
}
}
@@ -160,13 +157,13 @@ namespace Robust.Client.Graphics
public override int GetDescent(float scale) => default;
public override int GetLineHeight(float scale) => default;
public override float DrawChar(DrawingHandleScreen handle, char chr, Vector2 baseline, float scale, Color color)
public override float DrawChar(DrawingHandleScreen handle, Rune rune, Vector2 baseline, float scale, Color color, bool fallback=true)
{
// Nada, it's a dummy after all.
return 0;
}
public override CharMetrics? GetCharMetrics(char chr, float scale)
public override CharMetrics? GetCharMetrics(Rune rune, float scale, bool fallback=true)
{
// Nada, it's a dummy after all.
return null;

View File

@@ -4,9 +4,6 @@ using System.IO;
using System.Text;
using JetBrains.Annotations;
using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SharpFont;
@@ -20,18 +17,18 @@ namespace Robust.Client.Graphics
private const int SheetWidth = 256;
private const int SheetHeight = 256;
[Dependency] private readonly IConfigurationManager _configuration = default!;
[Dependency] private readonly IClyde _clyde = default!;
private readonly IClyde _clyde;
private uint BaseFontDPI;
private uint _baseFontDpi = 96;
private readonly Library _library;
private readonly Dictionary<(FontFaceHandle, int fontSize), FontInstanceHandle> _loadedInstances =
new();
public FontManager()
public FontManager(IClyde clyde)
{
_clyde = clyde;
_library = new Library();
}
@@ -42,9 +39,9 @@ namespace Robust.Client.Graphics
return handle;
}
void IFontManagerInternal.Initialize()
void IFontManagerInternal.SetFontDpi(uint fontDpi)
{
BaseFontDPI = (uint) _configuration.GetCVar(CVars.DisplayFontDpi);
_baseFontDpi = fontDpi;
}
public IFontInstanceHandle MakeInstance(IFontFaceHandle handle, int size)
@@ -64,7 +61,7 @@ namespace Robust.Client.Graphics
private ScaledFontData _generateScaledDatum(FontInstanceHandle instance, float scale)
{
var ftFace = instance.FaceHandle.Face;
ftFace.SetCharSize(0, instance.Size, 0, (uint) (BaseFontDPI * scale));
ftFace.SetCharSize(0, instance.Size, 0, (uint) (_baseFontDpi * scale));
var ascent = ftFace.Size.Metrics.Ascender.ToInt32();
var descent = -ftFace.Size.Metrics.Descender.ToInt32();
@@ -83,7 +80,7 @@ namespace Robust.Client.Graphics
return;
var face = instance.FaceHandle.Face;
face.SetCharSize(0, instance.Size, 0, (uint) (BaseFontDPI * scale));
face.SetCharSize(0, instance.Size, 0, (uint) (_baseFontDpi * scale));
face.LoadGlyph(glyph, LoadFlags.Default, LoadTarget.Normal);
face.Glyph.RenderGlyph(RenderMode.Normal);
@@ -189,7 +186,7 @@ namespace Robust.Client.Graphics
OwnedTexture GenSheet()
{
var sheet = _clyde.CreateBlankTexture<A8>((SheetWidth, SheetHeight),
$"font-{face.FamilyName}-{instance.Size}-{(uint) (BaseFontDPI * scale)}-sheet{scaled.AtlasTextures.Count}");
$"font-{face.FamilyName}-{instance.Size}-{(uint) (_baseFontDpi * scale)}-sheet{scaled.AtlasTextures.Count}");
scaled.AtlasTextures.Add(sheet);
return sheet;
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Robust.Shared.Maths;
@@ -7,6 +8,8 @@ using SixLabors.ImageSharp.PixelFormats;
namespace Robust.Client.Graphics
{
public delegate void CopyPixelsDelegate<T>(Image<T> pixels) where T : unmanaged, IPixel<T>;
public interface IClyde
{
IRenderWindow MainWindowRenderTarget { get; }
@@ -21,6 +24,7 @@ namespace Robust.Client.Graphics
Vector2 DefaultWindowScale { get; }
void SetWindowTitle(string title);
void SetWindowMonitor(IClydeMonitor monitor);
/// <summary>
/// This is the magic method to make the game window ping you in the task bar.
@@ -31,10 +35,12 @@ namespace Robust.Client.Graphics
event Action<WindowFocusedEventArgs> OnWindowFocused;
Texture LoadTextureFromPNGStream(Stream stream, string? name = null,
event Action OnWindowScaleChanged;
OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParams = null);
Texture LoadTextureFromImage<T>(Image<T> image, string? name = null,
OwnedTexture LoadTextureFromImage<T>(Image<T> image, string? name = null,
TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel<T>;
/// <summary>
@@ -92,9 +98,18 @@ namespace Robust.Client.Graphics
/// </summary>
/// <param name="type">What kind of screenshot to take</param>
/// <param name="callback">The callback to run when the screenshot has been made.</param>
void Screenshot(ScreenshotType type, Action<Image<Rgb24>> callback);
/// <param name="subRegion">
/// The subregion of the framebuffer to copy.
/// If null, the whole framebuffer is copied.
/// </param>
/// <seealso cref="ScreenshotAsync"/>
/// <seealso cref="IRenderTarget.CopyPixelsToMemory{T}"/>
void Screenshot(ScreenshotType type, CopyPixelsDelegate<Rgb24> callback, UIBox2i? subRegion = null);
Task<Image<Rgb24>> ScreenshotAsync(ScreenshotType type)
/// <summary>
/// Async version of <see cref="Screenshot"/>.
/// </summary>
Task<Image<Rgb24>> ScreenshotAsync(ScreenshotType type, UIBox2i? subRegion = null)
{
var tcs = new TaskCompletionSource<Image<Rgb24>>();
@@ -103,7 +118,14 @@ namespace Robust.Client.Graphics
return tcs.Task;
}
IClydeViewport CreateViewport(Vector2i size, string? name = null);
IClydeViewport CreateViewport(Vector2i size, string? name = null)
{
return CreateViewport(size, default, name);
}
IClydeViewport CreateViewport(Vector2i size, TextureSampleParameters? sampleParameters, string? name = null);
IEnumerable<IClydeMonitor> EnumerateMonitors();
}
// TODO: Maybe implement IDisposable for render targets. I got lazy and didn't.

View File

@@ -20,5 +20,6 @@ namespace Robust.Client.Graphics
void SetVolume(float decibels);
void SetOcclusion(float blocks);
void SetPlaybackPosition(float seconds);
void SetVelocity(Vector2 velocity);
}
}

View File

@@ -1,3 +1,4 @@
using System;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Shared.Maths;
@@ -16,6 +17,13 @@ namespace Robust.Client.Graphics
bool Initialize();
void Ready();
event Action<TextEventArgs> TextEntered;
event Action<MouseMoveEventArgs> MouseMove;
event Action<KeyEventArgs> KeyUp;
event Action<KeyEventArgs> KeyDown;
event Action<MouseWheelEventArgs> MouseWheel;
event Action<string> CloseWindow;
ClydeHandle LoadShader(ParsedShader shader, string? name = null);
void ReloadShader(ClydeHandle handle, ParsedShader newShader);

View File

@@ -0,0 +1,18 @@
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
{
/// <summary>
/// Represents a connected monitor on the user's system.
/// </summary>
public interface IClydeMonitor
{
/// <summary>
/// This ID is not consistent between startups of the game.
/// </summary>
int Id { get; }
string Name { get; }
Vector2i Size { get; }
int RefreshRate { get; }
}
}

View File

@@ -1,4 +1,6 @@
using System;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
@@ -16,9 +18,57 @@ namespace Robust.Client.Graphics
IEye? Eye { get; set; }
Vector2i Size { get; }
/// <summary>
/// This is, effectively, a multiplier to the eye's zoom.
/// </summary>
Vector2 RenderScale { get; set; }
/// <summary>
/// If true, <see cref="Render"/> will be automatically called at the start of the frame.
/// </summary>
bool AutomaticRender { get; set; }
/// <summary>
/// Render the state of the world in this viewport, updating the texture inside the render target.
/// </summary>
void Render();
/// <summary>
/// Converts a point in the viewport's screen to world coordinates.
/// </summary>
MapCoordinates LocalToWorld(Vector2 point);
/// <summary>
/// Converts a point in world-space to the viewport's screen coordinates.
/// </summary>
Vector2 WorldToLocal(Vector2 point);
/// <summary>
/// Draw below screen-space overlays for this viewport in UI space.
/// </summary>
/// <param name="handle">The drawing handle to draw with.</param>
/// <param name="control">The control rendering.</param>
/// <param name="viewportBounds">
/// Absolute screen-space bounds to draw the control at.
/// Not relative to the current transform of <see cref="handle"/>.
/// </param>
public void RenderScreenOverlaysBelow(
DrawingHandleScreen handle,
IViewportControl control,
in UIBox2i viewportBounds);
/// <summary>
/// Draw above screen-space overlays for this viewport in UI space.
/// </summary>
/// <param name="handle">The drawing handle to draw with.</param>
/// <param name="control">The control rendering.</param>
/// <param name="viewportBounds">
/// Absolute screen-space bounds to draw the control at.
/// Not relative to the current transform of <see cref="handle"/>.
/// </param>
public void RenderScreenOverlaysAbove(
DrawingHandleScreen handle,
IViewportControl control,
in UIBox2i viewportBounds);
}
}

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